二叉查找树

定义: 一棵二叉查找树是一棵二叉树,其中每个结点都含有一个Comparable的键(以及相关联的值)且每个结点的键都大于其左子树中的任意结点的键而小于右子树中的任意结点的键。

public class BST<Key extends Comparable<Key>, Value>{
    private Node root;        //二叉查找树的根结点
    private class Node{
        private Key key;              //键
        private Value val;            //值
        private Node left, right;    //指向子树的链接
        private int N;               // 以该结点为根结点的子树中的结点总数
        public Node(Key key, Value val, int N){
            this.key = key;  this.val = val; this.N = N;
        }
    }

    public int size(){
        return size(root);
    }
    private int size(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){
        if( x == null)  return null;
        int cmp = key.compareTo(x.key);
        if( cmp < 0)  return get(x.left,key);
        else if(cmp > 0)  return get(x.right, key);
        else   return x.val;
    }
    public void put(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);
        else x.val = val;
        x.N = size(x.left) + size(x.right) +1;
        return x;
    }
}

我们嵌套了一个私有类来表示二叉树上的一个结点,每个结点都含有一个键,一个值,一个左链接,一个右链接和一个统计结点计数器。算法中的size()方法将空链接的值当做0,从而保证有size(x) = size(x.left) + size(x.right) + 1
使用二叉查找树的算法的运行时间取决于树的形状。最好的情况下,一棵含有N个结点的树是完全平衡的,每条空链接和根结点的距离均为~lgN。一般情况下,树的情况与此相接近。

在N个随机键构造的二叉查找树中,查找命中平均比较的次数为2lgN;查找未命中和插入操作的平均比较次数为2lgN。


二叉查找树中max(), min(), floor(), ceiling()方法的实现:

  • 最大键和最小键
    如果根结点的左链接为空,那么一棵二叉查找树的最小的键就是根结点;如果左链接非空,那么树中最小的键就是左子树的最小键。
  • 向上取整与向下取整
    如果给定的键key小于二叉查找树的根节点的键,那么小于等于key的最大键floor(key)一定在根结点的左子树;如果给定的键key大于二叉查找树的根结点,那么只有当右子树中存在小于等于key的键,小于等于key的最大键才会在右子树,否则根结点就是小于等于key的最大键。
public Key min(){
    return min(root).key;
}
private Node min(Node x){
    if ( x.left == null) return x;
    return min(x.left);
}
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;
    else if(cmp <0)  return floor(x.left, key);
    Node t = floor(x.right, key);
    if (t != null)  return t;
    else  return x;
}

二叉查找树中select()和rank()方法的实现:

  • 查找
    假设我们想找到排名为k的键。如果左子树中的结点数t等于k,则返回根结点的键;如果t大于k,则继续在左子树中查找;如果t小于k,则在右子树中查找排名为k-t-1的键。
  • 排名
    rank()是select()的逆方法。如果给定的键与根结点的键值相等,则左子树的结点数t;如果小于根结点的键值,则返回该键在左子树中的排名;如果大于根结点的键值,设根结点的键值为t,则返回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 = size(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(key, root);
}
private int rank(Key key, Node x){
    if( x == null) return 0;
    int cmp = key.compareTo(x.key);
    if  (cmp < 0)  return rank(key, x.left);
    else if(cmp >0)  return 1+size(x.left)+rank(key, x.right);
    else  return size(x.left);
}

二叉查找树中delete()方法的实现:

  • 删除最大键和最小键
    对于删除最小键deleteMin(),我们要不断深入根结点的左子树中直到遇见一个空链接,然后将指向该结点的链接指向该结点的右子树。因为没有任何链接指向被删除的结点,所以它会被垃圾处理器清理掉。同时应注意更新它到根结点的路径上所有结点的计数器的值。
  • 删除操作
    删除具有一个或没有子结点的结点操作类似于之前,但对于删除具有两个子结点的结点的操作,为了处理删除后多的两棵子树,一种方法是选取右子树的最小结点来填补被删除的结点的位置。简单的步骤是先将指向即将被删除的结点的链接保存为t,将x指向它的后继结点min(t.right),再将x的右链接指向deleteMin(t.right),最后将x的左链接设为t.left。
public void deleteMin(){
    root = deleteMin(root);
}
private Node deleteMin(Node x){
    if (x == null)  return x.right;
    x.left = deleteMin(x.left);
    x.N = size(x.left)+size(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(x.key);
    if( cmp < 0)  x.left = delete(x.left, key);
    else if( cmp > 0) x.right = delete(x.right, key);
    else{
        Node t = x;
        x = min(t.right);
        x.right = deleteMin(t.right);
        x.left  = t.left;
    }
    x.N = size(x.left) + size(x.right) + 1;
    return x;
}

二叉查找树的范围查找操作

  • 范围查找
    为了实现返回给定键值范围内的keys()方法,我们首先需要遍历二叉查找树——中序遍历,即先打印出根结点的左子树的所有键,然后打印出根结点的键,最后打印出根结点右子树的所有键。代码如下:
    private void print(Node x){
    if(x == null) return;
    print(x.left);
    StdOut.println(x.key);
    print(x.right);
    }
    为了实现接受两个参数并能够将给定范围的键返回给用例的keys()方法,我们可以将所有落在给定范围内的键加入一个队列Queue并跳过那些不可能含有所查找键的子树。
public Iterable<Key> keys(){
    return keys(min(), max());
}
public Iterable<Key> keys(){
    Queue<Key> queue = new Queue<Key>();
    keys(root, queue, lo, hi);
    return queue;
}
private void keys(Node x, Queue<Key> queue, Key lo, Key hi){
    if( x = null)  return;
    int cmplo = lo.compareTo(x.key);
    int cmphi = hi.compareTo(x.key);
    if( cmplo < 0) keys(x.left, queue, lo, hi);
    if( cmp <=0 && cmphi >= 0) queue.enqueue(x.key);
    if(cmphi > 0) keys(x.right, queue, lo, hi);
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 数据结构与算法--二叉查找树 上节中学习了基于链表的顺序查找和有序数组的二分查找,其中前者在插入删除时更有优势,而...
    sunhaiyu阅读 1,820评论 0 9
  • 一丶定义 一颗二叉查找树是一颗二叉树,其中每个结点的键都大于其任左子树任意结点的键而小于右子树任意结点的键。如标题...
    丶legend阅读 487评论 0 1
  • 数据结构与算法--从平衡二叉树(AVL)到红黑树 上节学习了二叉查找树。算法的性能取决于树的形状,而树的形状取决于...
    sunhaiyu阅读 7,637评论 4 32
  • 最近在闲看博客时看到一篇专门写红黑树的实现原理,以Java的TreeMap为例讲解,写的很不错,仔细看下来发现很多...
    locoder阅读 3,787评论 0 11
  • 开发IOS的朋友都知道IOS 贝塞尔曲线的重要性,由于经常会用到这样的东西,索性抽时间就把相应所有的属性,方法做一...
    Demonboy阅读 3,558评论 2 11