数据结构和算法(十)二叉搜索树

243 阅读5分钟

二叉搜索树

二叉搜索树:在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值。

如图:

二叉搜索树示例.PNG

查找

二叉树的查找非常简单。先获取根节点存储的数据,如果相等,则直接返回对应的节点;如果小于data,则遍历右子树;如果大于data,则遍历左子树。代码如下:

public Node find(int data){
    Node head = root;
    while(head != null){
        if(head.data == data){
            return head;
        }else if(head.data > data){
            head = head.right;
        }else {
            head = head.left;
        }
    }
    return null;
}

插入

二叉树的插入也要比较插入数据data与节点Node存储的数据大小。如果:

  1. data > Node节点数据,遍历右子树
  2. data < Node节点数据,遍历左子树
  3. 直到当前Node节点的左子树或者右子树为null,就插入数据到左子树或者右子树。
public void insert(int data){
    Node insertNode = new Node(data);
    if(root == null){
        root = insertNode;
        return;
    }
    Node head = root;
    while(head != null){
        if(head.data == data){
           head.num++;
           break;
        }else if(head.data > data){
            if(head.left == null){
                head.left = insertNode;
                insertNode.parent = head;
                break;
            }
            head = head.left;
        }else {
            if (head.right == null){
                head.right = insertNode;
                insertNode.parent = head;
                break;
            }
            head = head.right;
        }
    }
}

如果插入数据等于Node节点数据,我这里是定义一个num参数,存储相等数据的个数,代码如下:

static class Node{
        int data;//数据
        Node left;
        Node right;
        int num;//个数
        Node parent;//父节点
        public Node(int data) {
            this.data = data;
            num = 1;
        }
    }

删除

删除二叉搜索树有三种情况:

  1. 需要删除的节点没有叶子节点,如图:

删除节点情况1.PNG

当我们需要删除节点3时,只需要节点6的左子树指向null就行了.

  1. 需要删除的节点有一个叶子节点,如图:

删除节点清空2.PNG

当我们需要删除节点11时,我们只需要将节点8的节点指向节点13

  1. 需要删除的节点有两个叶子节点,如图:

删除节点情况3.PNG

当我们需要删除节点8时,可以找到节点8的右子树的最小节点,把该最小节点的值赋给需要删除的节点;然后删除这个右子树的最小节点就行。由于右子树的最小节点一定没有两个叶子节点,所以这个节点的删除可以使用上述两种情况来解决。

    public void delete(int data){
        Node head = root;
        //找到要删除的节点和它的父节点
        while (head != null){
            if(head.data == data){
                head.num--;
                if(head.num != 0){
                    return;
                }
                break;
            }else if(head.data > data){
                head = head.left;
            }else {
                head = head.right;
            }
        }
        if(head == null)return;
        if (head.left != null && head.right != null){
            //当删除节点有两个子节点
            Node minRightNode = head.right;
            while(minRightNode.left != null){
                minRightNode = minRightNode.left;
            }
            head.data = minRightNode.data;
            head.num = minRightNode.num;
            head = minRightNode;
        }
        if(head.left == null && head.right == null){
            Node parent = head.parent;
            head.parent = null;
            if(parent.left == head){
                parent.left = null;
            }else {
                parent.right = null;
            }
        }else if(head.left == null){
            Node parent = head.parent;
            head.parent = null;
            if(parent.left == head){
                parent.left = head.right;
                head.right.parent = parent;
            }else {
                parent.right = head.right;
                head.right.parent = parent;
            }
        }else {
            Node parent = head.parent;
            head.parent = null;
            if(parent.left == head){
                parent.left = head.left;
                head.left.parent = parent;
            }else {
                parent.right = head.left;
                head.left.parent = parent;
            }
        }
    }

获得最大值和最小值

获取二叉搜索树的最大值或者最小值,只需要遍历获取搜索树的右子树或者左子树

public int max(){
    Node head = root;
    if (head == null){
        return -1;
    }
    while (head.right != null){
        head = head.right;
    }
    return head.data;
}


public int min(){
    Node head = root;
    if (head == null){
        return -1;
    }
    while (head.left != null){
        head = head.left;
    }
    return head.data;
}

二叉搜索树的时间复杂度

二叉搜索树1.PNG

如图,在极端情况下二叉搜索树会退化成链表。插入、删除、查找的时间复杂度都是O(n)

二叉搜索树2.PNG

如图,如果二叉搜索树是完全二叉树的时候,它的插入、删除、查找的时间复杂度都是O(logn)

从中可以看出二叉搜索的时间复杂度和它的高度有关,所以它的插入、删除、查找的时间复杂度是O(height)

平衡二叉树

平衡二叉树:二叉树中任意一个节点的左右子树的高度相差不能大于1。

平衡二叉搜索树:平衡二叉查找树不仅满足平衡二叉树的定义,还满足二叉查找树的特点。注意:我们一般说的平衡二叉树就是指平衡二叉搜索树

AVL树

AVL树是最早被发明的平衡二叉搜索树。它严格符合平衡二叉树的定义,即二叉树中任意一个节点的左右子树的高度相差不能大于1.

如下图就是一个AVL树

AVL树.PNG

  • 优点:

    插入、删除、查找的时间复杂度都是O(logn)

  • 缺点:

    插入和删除需要旋转,会消耗固定的时间。当数据量不多,或者插入和删除操作太频繁时,对性能影响大

红黑树

Red-black_tree_example.svg.png

红黑树是每个节点都带有颜色属性的二叉查找树,颜色为红色或黑色。红黑树的特点有:

  1. 节点是红色或黑色。
  2. 根是黑色。
  3. 所有叶子都是黑色(叶子是NIL节点)。
  4. 每个红色节点必须有两个黑色的子节点。(从每个叶子到根的所有路径上不能有两个连续的红色节点。)
  5. 从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。
  • 优点

    红黑树相对于AVL树来说,牺牲了部分平衡性以换取插入/删除操作时少量的旋转操作,整体来说性能要优于AVL树。

  • 缺点

    实现复杂

详情见wiki

伸缩树

伸展树也是平衡二叉搜索树,不同的是在每次查找之后对树进行调整,把被查找的条目搬移到离树根近一些的地方。

  • 优点

    因为频繁访问的节点会被移动到更靠近根节点,进而获得更快的访问速度。

  • 缺点

    1. 伸展树最显著的缺点是它有可能会变成一条链。
    2. 即使以“只读”方式(例如通过查找操作)访问伸展树,其结构也可能会发生变化。这使得伸展树在多线程环境下会变得很复杂。具体而言,如果允许多个线程同时执行查找操作,则需要额外的维护和操作。这也使得它们不适合在纯粹的函数式编程中普遍使用,尽管用于实现优先级队列的方式不多。

详情见wiki

树堆

树堆本身是一棵二叉搜索树,它的左子树和右子树也分别是一个树堆,和一般的二叉搜索树不同的是,树堆纪录一个额外的数据,就是优先级。树堆在以关键码构成二叉搜索树的同时,还满足堆的性质。

详情见wiki

常见算法题