一次搞懂各种树之二叉搜索树


树是一种非常常见且非常重要的数据结构。与线性数据结构不同,树是非线性数据结构,因而也比链表、数组等要复杂。但是学好树也不难,学不好的话,那就只能找棵树自挂东南枝了。
树的定义:树是由结点或顶点和边组成的(可能是非线性的)且不存在着任何环的一种数据结构。没有结点的树称为空(null或empty)树。一棵非空的树包括一个根结点,还(很可能)有多个附加结点,所有结点构成一个多级分层结构。

1. 二叉树

1.1定义

二叉树(Binary Tree)是n(n>=0)个节点的有限集合,该集合或者空集(称为空二叉树),或者由一个根节点和两棵互不相交的,分别称为根节点的左子树和右子树的二叉树组成。
满二叉树除最后一层无任何子节点外,每一层上的所有结点都有两个子结点。也可以这样理解,除叶子结点外的所有结点均有两个子结点。节点数达到最大值,所有叶子结点必须在同一层上。
完全二叉树 若设二叉树的深度为h,除第 h 层外,其它各层 (1~(h-1)层) 的结点数都达到最大个数,第h层所有的结点都连续集中在最左边,这就是完全二叉树。

1.2 性质

  • 二叉树性质

    • 在非空二叉树中,第i层的结点总数不超过2i-1, i>=1;
    • 深度为h的二叉树最多有2h-1个结点(h>=1),最少有h个结点;
    • 对于任意一棵二叉树,如果其叶结点数为N0,而度数为2的结点总数为N2,则N0=N2+1;
    • 具有n个结点的完全二叉树的深度为log2(n+1);
    • 有N个结点的完全二叉树各结点如果用顺序方式存储,则结点之间有如下关系:
        若I为结点编号则 如果I>1,则其父结点的编号为I/2;
        如果2I<=N,则其左儿子(即左子树的根结点)的编号为2I;若2I>N,则无左儿子;
        如果2I+1<=N,则其右儿子的结点编号为2I+1;若2I+1>N,则无右儿子。
    • 给定N个节点,能构成h(N)种不同的二叉树,其中h(N)为卡特兰数的第N项,h(n)=C(2*n, n)/(n+1)。
    • 设有i个枝点,I为所有枝点的道路长度总和,J为叶的道路长度总和J=I+2i。
  • 满二叉树的性质

    • 一颗树深度为h,最大层数为k,深度与最大层数相同,k=h;
    • 叶子数为2h;
    • 第k层的结点数是:2k-1;
    • 总结点数是:2k-1,且总节点数一定是奇数
  • 完全二叉树

    • 完全二叉树是效率很高的数据结构,堆是一种完全二叉树或者近似完全二叉树,所以效率极高,像十分常用的排序算法、Dijkstra算法、Prim算法等都要用堆才能优化,二叉排序树的效率也要借助平衡性来提高,而平衡性基于完全二叉树。

2. 二叉查找树

2.1 定义

又称为是二叉排序树(Binary Sort Tree)或二叉搜索树。可能是一棵空树,或者是具有下列性质的二叉树:
  1) 若左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  2) 若右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值;
  3) 左、右子树也分别为二叉排序树;
  4) 没有键值相等的节点。

2.2 性质

  • 对二叉查找树进行中序遍历,即可得到有序的数列。
    这里补充一下二叉树的遍历顺序,首先看图:二叉树
    • 前序遍历:规则是若二叉树为空,则空操作返回,否则先访问根结点,然后前序遍历左子树,再前序遍历右子树。如图所示,遍历的顺序为:ABDECF。
    • 中序遍历:规则是若树为空,则空操作返回,否则从根结点开始(注意并不是先访问根结点),中序遍历根结点的左子树,然后是访问根结点,最后中序遍历右子树。如图所示,遍历的顺序为:DBEAFC。
    • 后序遍历:规则是若树为空,则空操作返回,否则从左到右先叶子后结点的方式遍历访向左右子树,最后是访问根结点。如图所示,遍历的顺序为:DEBFCA。
  • 二叉查找树的时间复杂度:它和二分查找一样,插入和查找的时间复杂度均为O(logn),但是在最坏的情况下仍然会有O(n)的时间复杂度。原因在于插入和删除元素的时候,树没有保持平衡。我们追求的是在最坏的情况下仍然有较好的时间复杂度,这就是平衡查找树设计的初衷。
  • 二叉查找树的高度决定了二叉查找树的查找效率。

2.3二叉查找树的插入过程:

  1. 若当前的二叉查找树为空,则插入的元素为根节点;
  2. 若插入的元素值小于根节点值,则将元素插入到左子树中;
  3. 若插入的元素值不小于根节点值,则将元素插入到右子树中。

2.4 二叉查找树的删除

2.4.1 删除叶子节点

p为叶子节点,直接删除该节点,再修改其父节点的指针(注意分是根节点和不是根节点)
二叉搜索树
删除叶子节点25,则
删除节点

2.4.2 删除单支节点

p为单支节点(即只有左子树或右子树)。让p的子树与p的父亲节点相连,删除p即可(注意分是根节点和不是根节点)
删除上图的值为16节点
删除单支节点

2.4.3 节点完整的点

删除有左右子树的节点,有两个办法,即用左子树的最大节点取代待删除节点,或者用最小右子树的节点取代待删除节点。

  • 用左子树的最大节点取代待删除节点
    delete node- 用右子树的最小节点取代待删除节点
    那就是用36取代33。即
    二叉树
    这两种方法的所产生的二叉树形态看起来似乎不一样,那么为什么有这两种结果呢?其实这只有一种结果,判断其是否正确的依据是删除该节点之后,二叉树的中序遍历结果是否保持相对一致。在删除之前,二叉搜索树的中序遍历结果是
    1 -> 3 -> 5 -> 10 -> 12 -> 15 -> 17 -> 28-> 33-> 36-> 38
    当我们删除33节点时,中序遍历的结果就是28后面是36。对于这两个方法产生的中序遍历结果,都是这样的。

3.代码实现

alk is cheap, show me the code.

Node.java

public class Node<T extends Comparable> {
    public T val;
    public Node left;
    public Node right;

    public Node(T val){
        this.val = val;
    }

    @Override
    public String toString() {
        return "Node{" +
                "val=" + val +
                '}';
    }
}

BInarySearchTree.java

public class BinarySearchTree {
    //二叉搜索树根节点
    public static Node root;

    public BinarySearchTree() {
        this.root = null;
    }

    /**
     * 查找
     * * 树深(N) O(lgN)
     * * 1. 从root节点开始
     * * 2. 比当前节点值小,则找其左节点
     * * 3. 比当前节点值大,则找其右节点
     * * 4. 与当前节点值相等,查找到返回TRUE
     * * 5. 查找完毕未找到,     *     *
     *
     * @param key * @return
     */
    public Node search(Node key) {
        Node current = root;
        int res = 0;
        while (current != null && ((res
                = key.val.compareTo(current.val)) != 0)) {
            if (res < 0) current = current.left;
            else current = current.right;
        }
        return current;
    }

    /**
     * 插入节点
     * @param key
     * @return
     */
    public Node insert(Node key) {
        //当前节点
        Node current = root;
        //上个节点
        Node parent = null;
        //如果根节点为空,那么插入的节点为跟节点
        if (current == null) {
            root = key;
            return key;
        }
        while (true) {
            parent = current;
            int res = key.val.compareTo(current.val);
            if (res < 0) {
                current = current.left;
                if (current == null) {
                    parent.left = key;
                    return key;
                }
            } else {
                current = current.right;
                if (current == null) {
                    parent.right = key;
                    return key;
                }
            }
        }
    }

    /**
     * 1.找到删除节点
     * 2.如果删除节点左节点为空 , 右节点也为空;
     * 3.如果删除节点只有一个子节点 右节点 或者 左节点
     * 4.如果删除节点左右子节点都不为空
     * @param key
     * @return
     */
    public Node delete(Node key) {
        Node parent = root;
        Node current = root;
        boolean isLeft = false;
        //找到待删除节点,以及是否是左子树
        while (!key.val.equals(current.val)) {
            parent = current;
            int res = key.val.compareTo(current.val);
            if (res < 0) {
                isLeft = true;
                current = current.left;
            } else {
                isLeft = false;
                current = current.right;
            }
            if (current == null) return null;
        }        //如果刪除的左右节点都为空
        if (current.left == null && current.right == null) {
            if (current == root)
                root = null;
            //该节点在左子树
            if (isLeft) parent.left = null;
            else parent.right = null;
        } else if (current.left == null) {
            // 该节点为右子树
            if (current == root) root = current.right;
            else if (isLeft) {
                parent.left = current.right;
            } else {
                parent.right = current.right;
            }
        } else if (current.right == null) {
            if (current == root) root = current.left;
            else if (isLeft) parent.right = current.left;
            else parent.left = current.left;
        } else if (current.right != null && current.left != null) {
            Node successor = getDeleteSuccessor(current);
            if (current == root) {
                root = successor;
            } else if (isLeft) {
                parent.left = successor;
            } else {
                parent.right = successor;
            }
            successor.left = current.left;
        }
        return current;
    }

    private Node getDeleteSuccessor(Node deleteNode) {
        Node successor = null;
        Node successorParent = null;
        Node current = deleteNode.right;
        while (current != null) {
            successorParent = successor;
            successor = current;
            current = current.left;
        }
        // 检查后继者(不可能有左节点树)是否有右节点树
        // 如果它有右节点树,则替换后继者位置,加到后继者父亲节点的左节点.
        if (!successor.equals(deleteNode.right)) {
            successorParent.left = successor.right;
            successor.right = deleteNode.right;
        }
        return successor;
    }

    public void toString(Node root) {
        if (root != null) {
            toString(root.left);
            System.out.print("value = " + root.val + " -> ");
            toString(root.right);
        }
    }

}

测试类,首先是
Person.java

public class Person implements Comparable<Person> {
    public int age;
    public String name;

    public Person() {
    }

    public Person(int age,String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age;
    }

    @Override
    public int hashCode() {
        return Objects.hash(age);
    }

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }
}

BSTTest.java

public class BSTTest {

    public static void main(String[] args) {
        BinarySearchTree b = new BinarySearchTree();
        b.insert(new Node(new Person(3, "zhangsan")));
        b.insert(new Node(new Person(8, "lisi")));
        b.insert(new Node(new Person(1, "wangwu")));
        b.insert(new Node(new Person(4, "zhaoer")));
        b.insert(new Node(new Person(6, "qianba")));
        b.insert(new Node(new Person(2, "sunjiu")));
        b.insert(new Node(new Person(10, "guyi")));
        b.insert(new Node(new Person(9, "chenqi")));
        b.insert(new Node(new Person(20, "wuda")));
        b.insert(new Node(new Person(25, "qiao")));
        // 打印二叉树
        b.toString(b.root);
        System.out.println();
        // 是否存在节点值10
        Node node01 = b.search(new Node(new Person(10, "guyi")));
        System.out.println("是否存在节点值为10 => " + node01.val);
        // 是否存在节点值11
        Node node02 = b.search(new Node(new Person(11, "ergou")));
        System.out.println("是否存在节点值为11 => " + node02);
        // 删除节点8
        Node node03 = b.delete(new Node(new Person(8, "lisi")));
        //Node node03 = b.delete1(b.root, new Node(new Person(8,"lisi")));
        System.out.println("删除节点8 => " + node03.val);
        b.toString(b.root);
    }
}

运行结果如图
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值