美文网首页
数据结构--二分搜索树

数据结构--二分搜索树

作者: Hayley__ | 来源:发表于2021-01-08 16:38 被阅读0次

    二叉树

    • 二叉树也是一种动态的数据结构。
    • 每个节点只有两个叉,也就是两个孩子节点,分别叫做左孩子,右孩子。
    • 而没有一个孩子的节点叫做叶子节点。
    • 每个节点最多有一个父亲节点,最多有两个孩子节点(也可以没有孩子节点或者只有一个孩子节点)。
    • 二叉树不一定是“满”的。
    • 一个节点/空也是一个二叉树。

    二分搜索树 (Binary Search Tree)

    二分搜索树是一颗二叉树,每个节点的左子树的值都小于该节点的值,每个节点右子树的值都大于该节点的值,任意一个节点的每棵子树都满足二分搜索树的定义。存储的元素必须有可比较性,一般二分搜索树不包含重复元素,二分搜索树天然的具有递归特性。


    二分搜索树

    相应操作

    添加一个元素示例(Java)

    向以node为根节点的树上添加一个元素, 并返回插入新节点后的二分搜索树的根节点

    public class BinarySearchTree <E extends Comparable<E>>{
        private class Node{
            public E e;
            public Node left, right;
            public Node(E e){
                this.e = e;
                left = null;
                right = null;
            }
        }
        private Node root;
        private int size;
    
        public BinarySearchTree(){
            root  = null;
            size = 0;
        }
    
        public int size() {
            return size;
        }
    
        public boolean isEmpty(){
            return size == 0;
        }
    
        //向二分搜索树中添加新的元素e,递归算法
        public void add(E e){
            root = add(root, e);
        }
    
        //向以node为跟的二分搜索树中插入元素e,递归算法
        //返回插入新节点后的二分搜索树的根
        private Node add(Node node,E e) {
            if (node == null) {
                size ++;
                return new Node(e);
            }
    
            //如果相等 则不作处理
            if (e.compareTo(node.e) < 0)
                node.left = add(node.left, e);
            else if (e.compareTo(node.e) > 0)
                node.right = add(node.right, e);
            return node;
        }
    
        // 向二分搜索树中添加新的元素e,非递归写法
        public void add2(E e){
    
            // 对二分搜索树是空的情况特殊处理
            // 此时,直接让 root 指向新的节点即可
            if(root == null){
                root = new Node(e);
                size ++;
                return;
            }
    
            // 用 p 来跟踪待插入节点的上一个节点
            // p 的作用相当于链表插入节点时,pre 的作用
            Node p = root;
            while(p != null){
    
                // 如果待插入的值小于当前 p 节点的值
                // 说明新插入的值要放在 p 的左子树
                if(e.compareTo(p.e) < 0){
                    // 如果 p 的左子树为空,则在 p.left 上放入新的节点
                    if(p.left == null){
                        p.left = new Node(e);
                        size ++;
                        return; // 注意这里直接 return
                    }
    
                    // 否则 p = p.left
                    p = p.left;
                }
                // 如果待插入的值大于当前 p 节点的值
                // 说明新插入的值要放在 p 的右子树
                else if(e.compareTo(p.e) > 0){
                    // 如果 p 的右子树为空,则在 p.right 上放入新的节点
                    if(p.right == null){
                        p.right = new Node(e);
                        size ++;
                        return; // 注意这里直接 return
                    }
    
                    // 否则 p = p.right
                    p = p.right;
                }
                // 如果待插入的值等于当前 p 节点的值,说明二分搜索树中已经有这个值了
                // 直接 return
                else return;
            }
        }
    }
    

    查询一个元素示例

        //看二分搜索树中是否包含元素e
        public boolean contains(E e){
            return contains(root, e);
        }
    
        //看以node为根的二分搜索树中是否包含元素e, 递归算法
        private boolean contains(Node node, E e){
            if (node == null)
                return false;
    
            if (e.compareTo(node.e) == 0)
                return true;
            else if (e.compareTo(node.e) < 0)
                return contains(node.left, e);
            else // e.compareTo(node.e) > 0
                return contains(node.right, e);
        }
    

    遍历操作

    1. 深度优先遍历 :
    • 前序遍历 : 对当前节点的遍历在对左右孩子节点的遍历之前, 遍历顺序 : 当前节点->左孩子->右孩子
    • 中序遍历 : 对当前节点的遍历在对左右孩子节点的遍历中间, 遍历顺序 : 左孩子->当前节点->右孩子
    • 后序遍历 : 对当前节点的遍历在对左右孩子节点的遍历之后, 遍历顺序 : 左孩子->右孩子->当前节点
    1. 广度优先遍历 :
    • 层序遍历 : 按层从左到右进行遍历

    前序遍历示例

        //前序遍历 先访问节点 后访问左右子树
        //二分搜索树的前序遍历
        public void preOrder(){
            preOrder(root);
        }
    
        //前序遍历以node为根的二分搜索树,递归算法
        private void preOrder(Node node) {
            if (node == null)
                return;
    
            System.out.println(node.e);
            preOrder(node.left);
            preOrder(node.right);
        }
    
        //二分搜索树的非递归前序遍历
        public void preOrederNR() {
            Stack<Node> stack = new Stack<>();
            stack.push(root);
    
            while (!stack.isEmpty()) {
                Node cur = stack.pop();
                System.out.println(cur.e);
    
                // 由于栈是后入先出, 前序遍历是先左孩子, 再右孩子, 所以这里需要先将右孩子压入栈
                if (cur.right != null)
                    stack.push(cur.right);
                if (cur.left != null)
                    stack.push(cur.left);
            }
        }
    
    前序遍历结果

    中序遍历示例

        //中序遍历 先访左子树 在访问节点 最后访问右子树
        //二分搜索树的中序遍历 结果就是排序后得到的顺序
        public void inOrder() {
            inOrder(root);
        }
    
        //中序遍历以node为根的二分搜索树,递归算法
        private void inOrder(Node node) {
            if(node == null)
                return;
            inOrder(node.left);
            System.out.println(node.e);
            inOrder(node.right);
        }
    
    中序遍历结果

    后序遍历示例

        //后序遍历 先访左子树 在访问右子树 最后访问节点
        //二分搜索树的后序遍历
        public void postOrder() {
            postOrder(root);
        }
    
        //后序遍历以node为根的二分搜索树,递归算法
        private void postOrder(Node node) {
            if (node == null)
                return;
            postOrder(node.left);
            postOrder(node.right);
            System.out.println(node.e);
        }
    
    后序遍历结果

    层序遍历示例

    广度优先遍历可以更快的找到问题的解,解决最短路径为题。

        //二分搜索树的层序遍历 使用队列 广度遍历
        //从左到右一层一层遍历
        public void levelOrder() {
            Queue<Node> q = new LinkedList<>();
            q.add(root);
    
            // 1. 首先根节点入队
            // 2. 每次出队时, 都将当前节点的左右孩子先后入队
            // 3. 如果队列为空的话, 则表示层序遍历结束
    
            while (!q.isEmpty()) {
                Node cur = q.remove();
                System.out.println(cur.e);
    
                if (cur.left != null)
                    q.add(cur.left);
                if (cur.right != null)
                    q.add(cur.right);
            }
        }
    照出队的顺序演示的遍历结果为 : 28 16 30 13 22 29 42
    

    寻找最大最小元素示例

        //寻找二分搜索树的最小元素
        public E minimum() {
            if (size == 0)
                throw new IllegalArgumentException("BTS is empty");
            return minimum(root).e;
        }
    
        //返回以node为根的二分搜索树的最小值所在的节点
        private Node minimum(Node node) {
            if (node.left == null)
                return node;
            return minimum(node.left);
        }
    
        //寻找二分搜索树的最大元素
        public E maximum() {
            if (size == 0)
                throw new IllegalArgumentException("BTS is empty");
            return maximum(root).e;
        }
    
        //返回以node为根的二分搜索树的最大值所在的节点
        private Node maximum(Node node) {
            if (node.right == null)
                return node;
            return maximum(node.right);
        }
    

    删除最小元素示例

    • 如果最小值就是一个叶子节点, 直接删除该节点即可
    • 如果最小值所在的节点还有右子树, 则用右子树的根节点替换当前节点即可
        //从二分搜索树中删除最小值所在节点,返回最小值
        public E removeMin(){
            E ret = minimum();
            root = removeMin(root);
            return ret;
        }
    
        //删除掉以node为根的二分搜索树中的最小节点
        //返回删除节点后新的二分搜索树的根
        private Node removeMin(Node node){
            if (node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }
    
            node.left = removeMin(node.left);
            return node;
        }
    

    删除最大元素示例

    • 如果最大值为叶子节点时, 直接删除该节点即可
    • 如果最大值所在节点还有左子树时, 则使用最大值左子树的根节点替换当前节点即可
        //从二分搜索树中删除最大值所在节点,返回最大值
        public E removeMax() {
            E ret = maximum();
            root =  removeMax(root);
            return ret;
        }
    
        //删除掉以node为根的二分搜索树中的最大节点
        //返回删除节点后新的二分搜索树的根
        private Node removeMax(Node node) {
            if (node.right == null){
                Node leftNode = node.left;
                node.left = null;
                size --;
                return leftNode;
            }
            node.right = removeMax(node.right);
            return node;
        }
    

    删除任意节点

    • 删除叶子节点, 直接删除即可
    • 删除只有右子树的节点, 逻辑同删除最小值
    • 删除只有左子树的节点, 逻辑同删除最大值
    • 删除同时具有左右子树的节点
      首先找到要删除的节点, 然后找到对应节点的前驱或者后继节点, 前驱就是指当前节点的左子树中最大的元素节点(<当前节点的最大节点), 后继就是指当前节点右子树中最小的元素节点(>当前节点的最小节点), 替换当前节点, 然后再删除要删除的节点,以下用后继节点为例
      找到后继节点
      后继节点左右子树赋值
      后继节点替换要删除的节点,并删除原节点

    代码示例

        //二分搜索树中删除元素为e的节点
        public void remove(E e){
            root = remove(root, e);
        }
    
        //删除以node为根的二分搜索树中值为e的节点 递归算法
        //返回删除节点后新的二分搜索树的根
        private Node remove(Node node, E e){
            if (node == null)
                return null;
    
            if (e.compareTo(node.e) < 0 ){
                node.left = remove(node.left, e);
                return node;
            } else if (e.compareTo(node.e) > 0 ){
                node.right = remove(node.right, e);
                return node;
            } else {
                if (node.left == null) {
                    Node right = node.right;
                    node.right = null;
                    size --;
                    return right;
                }
    
                if (node.right == null) {
                    Node left = node.left;
                    node.left = null;
                    size --;
                    return left;
                }
    
                //待删除节点左右子树均不为空的情况
                //找到比待删除节点大的最小元素,即待删除节点右子树的最小节点
                //用这个节点顶替待删除节点的位置
                Node successor = minimum(node.right);
                successor.right = removeMin(node.right);
                successor.left = node.left;
                node.left = node.right = null;
                return successor;
            }
        }
    

    二分搜索树的时间复杂度

    二分搜索树时间复杂度

    集合(Set)

    • 不能放重复元素

    用链表实现集合示例

    //用链表实现集合
    public class LinkedListSet<E> implements Set<E> {
        private LinkedList<E> list;
        public LinkedListSet() {
            list = new LinkedList<>();
        }
    
        @Override
        public int getSize() {
            return list.getSize();
        }
    
        @Override
        public boolean isEmpty() {
            return list.isEmpty();
        }
    
        @Override
        public boolean contains(E e) {
            return list.contains(e);
        }
    
        @Override
        public void add(E e) {
            if (!list.contains(e))
                list.addFirst(e);
        }
    
        @Override
        public void remove(E e) {
            list.removeElement(e);
        }
    }
    

    用二分搜索树实现集合示例

    //使用二分搜索树实现集合
    //性能优于使用链表实现的集合
    public class BSTSet<E extends Comparable<E>> implements Set<E>{
        private BinarySearchTree<E> bst;
        public BSTSet() {
            bst = new BinarySearchTree<>();
        }
    
        @Override
        public int getSize() {
            return bst.size();
        }
    
        @Override
        public boolean isEmpty() {
            return bst.isEmpty();
        }
    
        @Override
        public void add(E e) {
            bst.add(e);
        }
    
        @Override
        public boolean contains(E e) {
            return bst.contains(e);
        }
    
        @Override
        public void remove(E e) {
            bst.remove(e);
        }
    }
    

    集合的时间复杂度

    集合的时间复杂度度

    映射(Map)

    • 存储(件,值)数据对的数据结构(key, value)
    • 根据键(key),寻找值(value)。
    • 容易使用链表或者二分搜索树实现。

    用链表实现映射

    //用链表实现映射
    public class LinkedListMap<K, V> implements Map<K, V> {
    
        private class Node{
            public K key;
            public V value;
            public Node next;
    
            public Node(K key, V value, Node next) {
                this.key = key;
                this.value = value;
                this.next = next;
            }
    
            public Node(K key){
                this(key, null, null);
            }
    
            public Node() {
                this(null, null, null);
            }
    
            @Override
            public String toString() {
                return key.toString() + ":" + value.toString();
            }
        }
    
        private Node dummyHead;
        private int size;
    
        public LinkedListMap() {
            dummyHead = new Node();
            size = 0;
        }
    
        @Override
        public int getSize() {
            return size;
        }
    
        @Override
        public boolean isEmpty() {
            return size == 0;
        }
    
        private Node getNode(K key){
            Node cur = dummyHead.next;
            while (cur != null){
                if (cur.key.equals(key))
                    return cur;
                cur = cur.next;
            }
            return null;
        }
    
        @Override
        public boolean contains(K key) {
            return getNode(key) != null;
        }
    
        @Override
        public V get(K key) {
            Node cur = getNode(key);
            return cur == null? null: cur.value;
        }
    
        @Override
        public void add(K key, V value) {
            Node node = getNode(key);
            if (node == null){
                dummyHead.next = new Node(key, value, dummyHead.next);
                size ++;
            } else {
                //key相同时更新值
                node.value = value;
            }
        }
    
        @Override
        public void set(K key, V newValue) {
            Node node = getNode(key);
            if (node == null)
                throw new IllegalArgumentException(key + "doesn`t exist");
            node.value = newValue;
        }
    
        @Override
        public V remove(K key) {
            Node prev = dummyHead;
            while (prev.next != null) {
                if (prev.next.key.equals(key))
                    break;
                prev = prev.next;
            }
    
            if (prev.next != null){
                Node delNode = prev.next;
                prev.next = delNode.next;
                delNode.next = null;
                size --;
                return delNode.value;
            }
            return null;
        }
    }
    

    用二分搜索树实现映射

    import java.security.Key;
    //使用二分搜索树实现映射
    public class BSTMap <K extends Comparable<K>, V> implements Map<K, V>{
        private class Node{
            public K key;
            public V value;
            public Node left, right;
            public Node(K key, V value){
                this.key = key;
                this.value = value;
                left = null;
                right = null;
            }
        }
    
        private Node root;
        private int size;
    
        public BSTMap(){
            root = null;
            size = 0;
        }
    
        @Override
        public int getSize() {
            return size;
        }
    
        @Override
        public boolean isEmpty() {
            return size == 0;
        }
    
        @Override
        public void add(K key, V value) {
            root = add(root, key, value);
        }
    
        private Node add(Node node, K key, V value) {
            if (node == null) {
                size ++;
                return new Node(key, value);
            }
    
            //如果相等 则不作处理
            if (key.compareTo(node.key) < 0)
                node.left = add(node.left, key, value);
            else if (key.compareTo(node.key) > 0)
                node.right = add(node.right, key, value);
            else // ==
                node.value = value;
            return node;
        }
    
        private Node getNode(Node node, K key){
            if (node == null)
                return  null;
            if (key.compareTo(node.key) == 0)
                return node;
            else if (key.compareTo(node.key) < 0)
                return getNode(node.left, key);
            else
                return getNode(node.right, key);
        }
    
        @Override
        public boolean contains(K key) {
            return getNode(root, key) != null;
        }
    
        @Override
        public V get(K key) {
            Node node = getNode(root, key);
            return node == null? null : node.value;
        }
    
        @Override
        public void set(K key, V newValue) {
            Node node = getNode(root, key);
            if (node == null)
                throw new IllegalArgumentException(key + "doesn`t exist");
            node.value = newValue;
        }
    
        @Override
        public V remove(K key) {
            Node node = getNode(root, key);
            if (node != null){
                root = remove(root, key);
                return node.value;
            }
            return null;
        }
    
        //返回以node为根的二分搜索树的最小值所在的节点
        private Node minimum(Node node) {
            if (node.left == null)
                return node;
            return minimum(node.left);
        }
    
        //删除掉以node为根的二分搜索树中的最小节点
        //返回删除节点后新的二分搜索树的根
        private Node removeMin(Node node){
            if (node.left == null){
                Node rightNode = node.right;
                node.right = null;
                size --;
                return rightNode;
            }
    
            node.left = removeMin(node.left);
            return node;
        }
    
        //删除以node为根的二分搜索树中值键为Key的节点 递归算法
        //返回删除节点后新的二分搜索树的根
        private Node remove(Node node, K key){
            if (node == null)
                return null;
    
            if (key.compareTo(node.key) < 0 ){
                node.left = remove(node.left, key);
                return node;
            } else if (key.compareTo(node.key) > 0 ){
                node.right = remove(node.right, key);
                return node;
            } else {
                if (node.left == null) {
                    Node right = node.right;
                    node.right = null;
                    size --;
                    return right;
                }
    
                if (node.right == null) {
                    Node left = node.left;
                    node.left = null;
                    size --;
                    return left;
                }
    
                //待删除节点左右子树均不为空的情况
                //找到比待删除节点大的最小元素,即待删除节点右子树的最小节点
                //用这个节点顶替待删除节点的位置
                Node successor = minimum(node.right);
                successor.right = removeMin(node.right);
                successor.left = node.left;
                node.left = node.right = null;
                return successor;
            }
        }
    }
    

    映射的时间复杂度

    映射的时间复杂度

    相关文章

      网友评论

          本文标题:数据结构--二分搜索树

          本文链接:https://www.haomeiwen.com/subject/qfixaktx.html