二叉查找树( Binary Search Tree)学习小记

近几日学习了二叉查找树( Binary Search Tree),既BST。在算法(Algorithm 4thEdtion)中是为了实现更高性能的ST(符号表)而进行的深入讲解的。

 

这是我第一次明显的感受到因为算法改进而带来大幅度的性能提升。由于在日常数据处理中,符号表不仅需要根据Key索引,还要进行大量的新节点插入。而这两点都对性能提出了要求

 

当我们采用无序链式存储结构时,未命中的查找和插入操作都需要N次比较。命中的查找在最坏的情况下也需要N次比较。特别的,向一个空表中插入N个不同的键插入需要~N2/2次比较。如此高的存储代价,显然不是我们想要的。

 

为此,一个稍好的解决方案是采取二分查找。而一般的二分查找是基于有序数组。然而,向大小为N的有序数组中插入一个新的元素在最坏的情况下需要访问~2N次数组。

插入元素的代价依然十分庞大。

 

所以,我们需要一种能够将链表插入的灵活性和有序数组查找高效性结合起来的符号表。那边是二叉查找树。

 

BST: 一个二叉树,每个节点都有一个可以比较(Comparable)的Key,大于其左子树且小于其右子树。

 

基本节点以及API实现较为简单,代码如下。

 

节点:

 1 private class Node {
 2         private Node left, right;
 3         private Key key;  //键值
 4         private Value val;    //关联值
 5         private int N; //存储子树与该节点的总个数
 6         
 7         public Node(Key key, Value val, int N) {
 8             this.key = key;
 9             this.val = val;
10             this.N = N;
11         }
12     }

size():

1 public int size(Node x) {
2     if(x == null) 
3             return 0;
4     else return x.N;
5 }        

get():

 1         public Value get(Node x, Key key) {
 2         if(x == null)    
 3             return null;
 4         int cmp = key.compareTo(x.key);
 5         if(cmp < 0) 
 6             return get(x.left, key);
 7         else if(cmp > 0) 
 8             return get(x.right, key);
 9         else return x.val;
10     }    

put()方法以及接下来的min() floor()等等方法中,都是用了一个在类封装中的常用方法,就是公有方法搭配调用私有方法,此法很好的实现了封装,并且便于日后代码的修改。

 

 1 public void put(Key key, Value val) {
 2     put(root, key, val); //调用同名私有方法,从根节点开始搜索
 3 }
 4 
 5 private Node put(Node x, Key key, Value val) {
 6     if(x == null)      //搜索到null,说明并不存在key该节点 创建新节点
 7         return new Node(key, val, 1);
 8     int cmp = key.compareTo(x.key);
 9     if(cmp < 0)
10         x.left = put(x.left, key, val);  //利用递归更新节点
11     else if(cmp > 0)
12         x.right = put(x.right, key, val);
13     else {
14         x.val = val;
15     }
16     x.N = size(x.left) + size(x.right) + 1; //逐层更新节点数量
17     return x;
18 }

 

至于与min() max()同理:

 1 public Key min() {
 2     return  min(root).key;
 3 }
 4 private Node min(Node x) {
 5     if(x.left == null) {
 6         return x;
 7     }
 8     return min(x.left);
 9 }
10 
11 public Key max() {
12     return max(root).key;
13 }
14 private Node max(Node x) {
15     if(x.right == null)
16         return x;
17     return max(x.right);
18 }

 接下来的floor(Key Key)(求比Key小的最大键)设计的还是比较巧妙的。

 当找到x.key键比key小的时候 存在两种可能 一种是 所求键在x.key的右子树内 或者就是x.key本身 这个值得注意

 1 public Key floor(Key key) {
 2     Node x = floor(root, key);
 3     if(x == null)           // 有可能找不到
 4         return null;
 5     return x.key;
 6 }
 7 private Node floor(Node x, Key key) {
 8     if(x == null)
 9         return null;
10     int cmp = key.compareTo(x.key);
11     if(cmp == 0)
12         return x;
13     if(cmp < 0)
14         return floor(x.left, key);  // 若key没有x.key大 则肯定在x.left内
15     Node t = floor(x.right, key);   // 查找是否在比x.key大且合适的键值
16     if(t != null)
17         return t;
18     else
19         return x;   //不存在 返回x
20 }

 select() rank() 互为逆操作 代码不难懂

 1 public Key select(int n) {
 2     return select(root, n).key;
 3 }
 4 private Node select(Node x, int k) {
 5     if(x == null)
 6         return null;
 7     int t = size(x.left);
 8     if(t > k)
 9         return select(x.left, k);
10     else if(t < k)
11         return select(x.right, k-t-1);
12     else
13         return x;
14 }
15 public int rank(Key key) {
16     return rank(key, root);
17 }
18 private int rank(Key key, Node x) {
19     if(x == null) 
20         return 0;
21     int cmp = key.compareTo(x.key);
22     if(cmp < 0)
23         return rank(key, x.left);
24     else if(cmp > 0)
25         return 1 + size(x.left) + rank(key, x.right);
26     else 
27         return size(x.left);
28 }

最为精彩的部分还是delete()的操作,在这之前 先演示deleteMin().

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 = size(x.left) + size(x.right) + 1;
    return x;
}

delete(Key key)最难处理的情况是 该键左右子树均不是null的情况

此时要将Key的ceiling()值(大于他的最小值)替换到他的位置上。 利用其右子树的deleteMin()可实现此步骤 然后再将路径上都节点size()大小依次更新

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(x.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 = min(t.right);        //求得比他大的最小键 替换到被删除键的位置上
        x.right = deleteMin(t.right);  //将那个最小键从原位置删除 更新size
        x.left = t.left; //接管被删节点左子树 块结束 t被删除
    }
    x.N = size(x.left) + size(x.right) + 1; //更新节点数
    return x;
}
原文地址:https://www.cnblogs.com/slimjerk/p/3784933.html