二叉查找树深度讲解

二叉查找树(BST)(自己动手写API系列三)*

这次的教学可能会比较长一点 所以也希望大家可以耐心看

下面的代码里面会贴一些讲解 觉得烦也没事 最下面我会把全部的代码贴出来 你可以拿纯净的

然后这次的语言我还是 选择用java 如果不会Java的 不着急要代码 你也可以耐心看学习 用你学过的语言去实现以下

毕竟语言只是工具

好了 先看一下类名

public class BinarySearchTree  <Key extends Comparable</Key>, Value>

类名 : BinarySearchTree

参数 : Key 要求 继承了 Comparable 还有 一个Value 说白了 就是键值对

然后先简单的了解一下 二叉树

二叉树
什么是二叉树? 看图
《二叉查找树深度讲解》
你会发现这是什么啊? 别着急
《二叉查找树深度讲解》
这会应该会比较像了吧
我们这里 data是数据域 你可以存一大堆乱七八糟的

二叉树 :

二叉树其实就是一颗长的像树的东西 它是每个结点有两个分支 左分支 和 右分支 也可以叫左儿子和右儿子
而他本身的这个结点叫做父结点
二叉树我们可以规定一个约定
比如 : 每个结点的 左儿子 < 父结点 <右儿子 也就是左边的最小 自己在中间 右儿子最大

基本的二叉树就介绍到这里

让我们看一下这次教学的方法(函数)

getSize() 方法 返回这颗树有多少结点

get(Key key) 方法 通过键拿值 返回 Value

put(Key key, Value val) 方法 存储一个结点 存储他们的键值对

getMin() 方法 获得最小的那个值 返回一个 Value

getMax() 方法 获得最大的那个值 返回一个 Value

floor(Key key) 方法 向下取整 返回一个Key 返回比你传入key 最接近并且小于等于的键

ceiling(Key key) 方法 向上取整 返回一个Key 返回比你传入key 最接近并且大于等于的键

select(int k) 方法 返回一个 Key 返回排名第k个键

rank(Key key) 方法 返回一个int值 返回这个键排名多少

deleteMin() 方法 删除最小的那个结点

deleteMax() 方法 删除最大的那个结点

delete(Key key) 方法 删除一个值 返回一个Value 返回你要删除的那个键对应的值

方法就这么多 其实要想加还有 让我们一个 一个看

因为是二叉树,所以我们用到了结点 结点是一个内部类
内部类其实可以很好的隐藏自己 可以用到外部类 又可以很好的隐藏自己看一下内部类的定义

private class Node { //内部类 用private 修饰不暴露内部细节
        private Key key; // 内部类的键
        private Value val; //内部类的值
        private Node left; //结点左儿子
        private Node right; // 结点右儿子
        private int N; //子树的个数

        public Node(Key key, Value val, int N) {
            this.key = key; this.val = val; this.N = N;
        }
    }

然后看一下成员 其实成员就只有一个 : private Node root; 就是树根

接下来讲方法 因为根据开闭原则 我不希望暴露内部的细节 因为有内部类而我每个方法基本都要用到树根
所以每个方法都应该有一个private方法 和 public 方法 这里听不懂没事 往下慢慢看

1. int getSize() 方法

这个比较简单, 因为内部类中有个N也就是个数 所以 返回树根的个数就好
所以 这个就直接贴了

public int getSize() {
        return getSize(root);
    }

    private int getSize(Node x) {
        if(x == null) return 0;
        else return x.N;
    }

共有的方法提供给外部 但是 私有的方法才是真正起主导作用的
这样的重载方法就可以很好的起到封闭原则 没理解继续往下

2. get(Key key) 方法

记得一开始我们的那个约定 左儿子最小 右儿子最大 类似于下图《二叉查找树深度讲解》
这是百度找的图 懒得自己画
其实你会发现 如果把8当成父结点 左边的都比8小 而 右边的都比8大
重!!: 每一个结点其实都可以当成一个子树
把3当成父结点 也满足那个约束

所以你发现查找起来就会非常方便
你只要判断一下 当前的这个结点比你要查找的大还是小
你要查找的比当前的大 那么肯定你要查找的在右边
同理 如果小了 肯定在左边 如果刚刚好 命中返回

比如你要查找6
《二叉查找树深度讲解》

发现6 比 8 小 你是不是只用看左边就OK

《二叉查找树深度讲解》

然后树直接变成了这样 直接淘汰了一半接下来 比较6 和3
6比3大对吗? 走右边 又淘汰了一半 所以现在变成了这样

《二叉查找树深度讲解》

然后比较6和6 发现命中 返回就OK
思想是不是很简单 其实代码也不难 只要从树根开始
循环中当前结点不是空 就往下跑 比较查找的数 和当前结点谁大
数大了 代表在右边 那就让当前结点等于当前结点的右儿子

贴一下代码

 public Value get(Key key) {
        return get(root,key);
    }

    private Value get(Node x, Key key) {
        while (x != null) {
            int cmp = key.compareTo(x.key);
            if(cmp > 0) x = x.right;
            else if(cmp < 0) x = x.left;
            else return x.val;
        }
        return null;
    }

值得一提的是 如果跑到空了 也就整颗树都跑完了 他会跳出循环 返回一个null

3. put(Key key, Value val) 方法

这个方法和刚才的那个方法也就是这个的核心了 当然后面还有一个删除不过那个比较难一些
我们这里 其实你可能心里有一个结 怎么去统计根的个数 也就是那个N getSize() 方法拿到的那个值
其实就只有在创建结点或者删除结点的时候才会统计
你想想怎么统计 肯定从最下面 自下向上 逐层统计 你可以用笔试试 怎么统计以我这个结点为一颗树我下面有几个结点
从上向下肯定是不行的 所以 必须用到 递归! 递归不熟悉的话 可以先去学一下 不过你也可以跟着看

这里创建一个结点其实是有点麻烦的 我画一张图吧 一开始是没有结点的 然后来了一个5

《二叉查找树深度讲解》

因为树根都是空的 所以直接建 现在树根就是5 继续 来了一个8

《二叉查找树深度讲解》

因为8比5大 所以成了右儿子 然后来个 7
《二叉查找树深度讲解》

因为7比5大 应该要成右儿子对不对 发现右儿子有人了 那继续走呗 然后发现7比8小 7要成为8的左儿子 然后又来了个3
《二叉查找树深度讲解》

因为3比5大 所以3成了5的左儿子 然后来了4

《二叉查找树深度讲解》

可能画的有点丑 4比5小 走左边 4又比3大走右边
所以现在创建应该明白了吧 原理

 public void put(Key key, Value val) {
        root = put(root, key, val);
    }

    private Node put(Node x, Key key, Value val) {
        if(x == null) return new Node(key, val, 1); //其实你一直递归的走下去总会发现空结点发现空的就创建
											        //就和我们刚才的那个是一样的 x代表当前结点 
											        //你发现 当前结点没有东西 是不是要创建
        int cmp = key.compareTo(x.key);// 这里是比较key 也就是你要新创建的值 和当前结点比较一下
								        //如果大了 返回一个大于0的值 如果小了返回小于0的值
        if(cmp < 0) x.left = put(x.left, key, val); 
        else if(cmp > 0) x.right = put(x.right, key, val);
        eles x.val = val;
        x.N = getSize(x.left) + getSize(x.right) + 1; //这里就统计结点的大小了
													 //如果你实在看不懂这个递归的过程你可以推一下
        return x;                                   //别担心那个共有方法root会改变 他最后还是会返回root的
    }

4. Value getMin()方法

消化一下刚put() 方法吧 不会递归的话可能有些难以理解
也请如果递归不好的话 好好去推一下 因为后面还是有很多用到了递归 包括以后的学习过程

好了现在这个方法比较简单 其实按照我们的约定 你会发现 最左边的就是那个最小的

**你一路头铁 见左儿子进进去 只需要判断他还有没有左儿子 没有的话 那就是他了 **

public Value getMin() {
        return getMin(root).val;
    }

    private Node getMin(Node x) {
        if(x.left == null) return x;
        return getMin(x.left);
    }

5. Value getMax()

如果你能理解上面讲的 那么这个 呵呵… 怼右儿子就行

public Value getMax() {
        return getMax(root).val;
    }

    private Node getMax(Node x) {
        if(x.right == null) return x;
        return getMax(x.right);
    }

6. Key floor(Key key) 方法

这个可能也比较难理解,如果看不懂的话 就先跳过也是可以 毕竟这方法不是必要的
不过还是建议努力看懂
向下取整 不知道你听过这个没 比如现在有 3 4 5 7 9 你要取8 发现没有 则其实取到的是7
你要取 2 其实根本取不到对吗

所以你取一个数 小于当前的这个结点 那么 小于等于这个数一定在当前结点的左儿子们中
如果是你这个数大于了当前结点的数 那么必然在右儿子们中

《二叉查找树深度讲解》

对于这样的一颗二叉树 我们的目标是查找7的向下取整
一开始 因为 7大于13 所以 查找的结点肯定在13这个结点的左子树中

《二叉查找树深度讲解》

然后你发现 其实 7 比 5大 因为是向下取整 所以 有可能就是5 当然也有可能还在右子树中
所以你应该再去跑一趟右子树 因为 右子树中可能存在 7 或者 比7小 比5大这样的数
这里我们发现没有 所以应该得到的结果就是5 否则的话 如果 右子树中有6 或者 7 就应该命中的是6 或者 7

public Key floor(Key key) {
        Node x = floor(root, key); 
        if (x == null) return null; //如果空的返回空 保证不抛出空指针异常
        return x.key;
    }

    private Node floor(Node x, Key key) {
        if(x == null) return null; //因为用到递归 所以这里递归的出口
        int cmp = key.compareTo(x.key); //比较下key 和当前结点的key
        if(cmp == 0) return x; //如果正好命中 返回就好了
        if(cmp < 0) return floor(x.left, key); //如果小的话去左子树
        Node t = floor(x.right, key); //如果 你比他大 也就是7比5大就会到这 你还要去判断一下右子树中有没有
        if (t != null) return t; // 如果有的话就不是null 就把这个t返回 
        else return x;          // 否则的话 返回x就好
    }

7. Key ceiling(Key key) 方法

向上取整 如果上面的思想你能听懂 这个其实就是把 左右对换 大小对换 就OK

 public Key ceiling(Key key) {
        Node x = ceiling(root, key);
        if(x == null) return null;
        return x.key;
    }

    private Node ceiling(Node x, Key key) {
        if(x == null) return null;
        int cmp = key.compareTo(x.key);
        if(cmp == 0) return x;
        if(cmp > 0) return ceiling(x.right, key);
        Node t = ceiling(x.left, key);
        if(t != null) return t;
        else return x;
    }

8. Key select(int k) 方法##

首先这个方法 其实也不是必须的 但是不是太难 那你可以好好听

返回排名第k的键 这里其实要用到他子树中N这个成员变量了

我们需要递归的去计算 他的排名

首先第一步 你要得到你左子树中的个数

如果当前的size 比 k大了 说明应该去左子树对嘛

如果k大了怎么办 发现说明不在你的左边 而在你的右边 但是你左边的都抛弃了 你要相应的把你的k也减少到你抛弃的个数

比如你左边拿到的是t个 你抛弃了t个 所以是 k-t 但是 本身的结点也要抛弃 所以就是 k-t-1 再去递归验证右边 返回到第一步

如果正好相同呢 就直接返回当前结点就可以了

 public Key select(int k) {
        return select(root,k).key;
    }

    private Node select(Node x, int k) {
        if(x == null) return null;
        int t = getSize(x.left); //获得左子树的数量
        if(t > k) return select(x.left, k); //左边的都比你要找的大了 你就得去左边
        else if(t < k) return select(x.right,k-t-1); //把k减去你抛弃的个数
        else return x;
    }

9. int rank(Key key)

这个方法和上面那个其实是一种逆方法

就是你得判断一下 你的这个key和我当前的key 那个大?

传入的这个key 小了 肯定要命中的就在左边

要是大了我得传入右边 但是 左边的都比我小 所以 左边的数量我都要加上 并且 父结点也比我小 所以父结点的也要加上

所以就应该是右边找的个数 加上 左边的全部 加上1
否则的话 直接返回我自己左子树的个数就好了

public int rank(Key key) {
        return rank(root, key);
    }

    private int rank(Node x, Key key) {
        if(x == null) return 0;
        int cmp = key.compareTo(x.key);
        if(cmp < 0) return rank(x.left,key);
        else if(cmp > 0) return 1 + getSize(x.left) + rank(x.right, key);
        else return getSize(x.left);
    }

10. void deleteMin()

终于到了比较难的删除部分了

现在讲两个简单的 删除最大 和 最小的方法

其实和获得最小值一样 死命怼左儿子 但是不同的是 我们要把他删除了

但是 其实也很简单 只要把删除的那个左儿子的右儿子把他挤掉就可以

《二叉查找树深度讲解》

比如这样 你要删除的就是1 然后 把右儿子放过来 左儿子就没办法访问到了 这样左儿子就是个游离的
《二叉查找树深度讲解》

然后你就发现 其实 1 这个结点已经没办法访问到了 所以 垃圾回收机制就会搞掉他 当然 你用别的语言也可以手动释放

 public void deleteMin() {
        root = deleteMin(root);
    }

    private Node deleteMin(Node x) {
        if(x.left == null) return x.right;
        x.left = deleteMin(x.left);
        x.N = getSize(x.left) + getSize(x.right) + 1;
        return x;
    }

11. void deleteMax()

懂了上面的 这个也就是雷同的了

 public void deleteMax() {
        root = deleteMax(root);
    }

    private Node deleteMax(Node x) {
        if(x.right == null) return x.left;
        x.right = deleteMax(x.right);
        x.N = getSize(x.left) + getSize(x.right) + 1;
        return x;
    }

12. void delete(Key key)

接下来就是 比较难的删除了

还是看图理解吧

《二叉查找树深度讲解》

比如你要删除5这个结点

**你可以先试着想一想 其实就是让 11 这个子树从他开始去删除他的最小结点 按照我们之前的方式 但是不让他游离 **

《二叉查找树深度讲解》

但是我们的目的不是去删除 8 而是 5删除

你在看一下 是不是 5的左儿子成为8的左儿子 5的右儿子成为8的右儿子 13连向 8 就删除成功了?

《二叉查找树深度讲解》

这里 你发现 只要把5拿掉就完成了

《二叉查找树深度讲解》

思想是这样 不过看懂代码也会费事点 最后一个方法了 学完他 你就基本成了 所以 耐心一下吧

public void delete(Key key) {
        root = delete(root, key);
    }

    private Node delete(Node x, Key key) {
        if(x == null) return null;
        int cmp = key.compareTo(key); //这里还是得到一个比较值
        if(cmp < 0) x.left = delete(x.left, key); //这里是比较要删除的数在哪里
        else if(cmp > 0) x.right = delete(x.right, key);//比较
        else { //发现命中了
            if(x.right == null) return x.left; //现在x是那个结点 如果没有右儿子的话 就直接把左边接过来就好
                                               // 这个是比较轻松的情况 上图(最后一张图)
                                               //删除11的话 把 9 接过来就行了
            if(x.left == null) return x.right; // 这个也比较轻松 删除1的话3接过来就好
            Node t = x;    //下面就是删除5的情况了 我们一开始说的情况 用t先暂时保存这个删除的东西
            x = getMin(x.right);  //这个地方其实就是拿到8 也就是右子树最小的其实就是8覆盖掉5
            x.right = deleteMin(t.right); // 8这个结点连接之前5的右儿子
            x.left = t.left; // 5的左儿子们 编程了 8的右儿子们
        }
        x.N = getSize(x.left) + getSize(x.right) + 1; //改变一下结点的个数
        return x; 
    }

好了来一发完整代码

public class BinarySearchTree<Key extends Comparable<Key>, Value> {

    private Node root;

    private class Node {
        private Key key;
        private Value val;
        private Node left;
        private Node right;
        private int N;

        public Node(Key key, Value val, int N) {
            this.key = key; this.val = val; this.N = N;
        }
    }

    public int getSize() {
        return getSize(root);
    }

    private int getSize(Node x) {
        if(x == null) return 0;
        else return x.N;
    }

    public Value get(Key key) {
        return get(root,key);
    }

    private Value get(Node x, Key key) {
        while (x != null) {
            int cmp = key.compareTo(x.key);
            if(cmp > 0) x = x.right;
            else if(cmp < 0) x = x.left;
            else return x.val;
        }
        return null;
    }

    public void put(Key key, Value val) {
        root = put(root, key, val);
    }

    private Node put(Node x, Key key, Value val) {
        if(x == null) return new Node(key, val, 1);
        int cmp = key.compareTo(x.key);
        if(cmp < 0) x.left = put(x.left, key, val);
        else if(cmp > 0) x.right = put(x.right, key, val);
        eles x.val = val;
        x.N = getSize(x.left) + getSize(x.right) + 1;
        return x;
    }

    public Value getMin() {
        return getMin(root).val;
    }

    private Node getMin(Node x) {
        if(x.left == null) return x;
        return getMin(x.left);
    }

    public Value getMax() {
        return getMax(root).val;
    }

    private Node getMax(Node x) {
        if(x.right == null) return x;
        return getMax(x.right);
    }

    public Key floor(Key key) {
        Node x = floor(root, key);
        if (x == null) return null;
        return x.key;
    }

    private Node floor(Node x, Key key) {
        if(x == null) return null;
        int cmp = key.compareTo(x.key);
        if(cmp == 0) return x;
        if(cmp < 0) return floor(x.left, key);
        Node t = floor(x.right, key);
        if (t != null) return t;
        else return x;
    }

    public Key ceiling(Key key) {
        Node x = ceiling(root, key);
        if(x == null) return null;
        return x.key;
    }

    private Node ceiling(Node x, Key key) {
        if(x == null) return null;
        int cmp = key.compareTo(x.key);
        if(cmp == 0) return x;
        if(cmp > 0) return ceiling(x.right, key);
        Node t = ceiling(x.left, key);
        if(t != null) return t;
        else return x;
    }

    public Key select(int k) {
        return select(root,k).key;
    }

    private Node select(Node x, int k) {
        if(x == null) return null;
        int t = getSize(x.left);
        if(t > k) return select(x.left, k);
        else if(t < k) return select(x.right,k-t-1);
        else return x;
    }

    public int rank(Key key) {
        return rank(root, key);
    }

    private int rank(Node x, Key key) {
        if(x == null) return 0;
        int cmp = key.compareTo(x.key);
        if(cmp < 0) return rank(x.left,key);
        else if(cmp > 0) return 1 + getSize(x.left) + rank(x.right, key);
        else return getSize(x.left);
    }

    public void deleteMin() {
        root = deleteMin(root);
    }

    private Node deleteMin(Node x) {
        if(x.left == null) return x.right;
        x.left = deleteMin(x.left);
        x.N = getSize(x.left) + getSize(x.right) + 1;
        return x;
    }

    public void deleteMax() {
        root = deleteMax(root);
    }

    private Node deleteMax(Node x) {
        if(x.right == null) return x.left;
        x.right = deleteMax(x.right);
        x.N = getSize(x.left) + getSize(x.right) + 1;
        return x;
    }

    public void delete(Key key) {
        root = delete(root, key);
    }

    private Node delete(Node x, Key key) {
        if(x == null) return null;
        int cmp = key.compareTo(key);
        if(cmp < 0) x.left = delete(x.left, key);
        else if(cmp > 0) x.right = delete(x.right, key);
        else {
            if(x.right == null) return x.left;
            if(x.left == null) return x.right;
            Node t = x;
            x = getMin(x.right);
            x.right = deleteMin(t.right);
            x.left = t.left;
        }
        x.N = getSize(x.left) + getSize(x.right) + 1;
        return x;
    }
}

自己动手写API系列之三 结束 !##

OKK 谢谢大家观看 也希望可以点一下关注 有意见欢迎提出

    原文作者:二叉查找树
    原文地址: https://blog.csdn.net/qq_42011541/article/details/81159853
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞