《 常见算法与数据结构》符号表ST(3)——二叉查找树 (附动画)

符号表(3)——二叉查找树

本系列文章主要介绍常用的算法和数据结构的知识,记录的是《Algorithms I/II》课程的内容,采用的是“算法(第4版)”这本红宝书作为学习教材的,语言是java。这本书的名气我不用多说吧?豆瓣评分9.4,我自己也认为是极好的学习算法的书籍。

通过这系列文章,可以加深对数据结构和基本算法的理解(个人认为比学校讲的清晰多了),并加深对java的理解。

    • 符号表3二叉查找树
  • 二叉查找树
    • 1 代码框架
    • 2 节点表示
    • 3 取得操作
    • 4 插入操作
    • 5 动画演示
    • 6 二叉树的形状
  • 有序操作
    • 1 最大最小
    • 2 floor和ceiling
    • 21 floor操作递归操作
    • 22 ceiling操作递归操作
    • 3 子树统计
    • 4 rank
    • 5 有序遍历
  • 总结

1 二叉查找树

二叉查找树是一个对称有序的二叉树

即:

每个节点有key,有2个孩子,左孩子的key小于自己的key,右孩子的key大于自己的key。(和堆不一样)

1.1 代码框架

我们可以根据定义设计出一个如下的二叉查找树基本框架,然后去完善它

public class BST<Key extends Comparable<Key>, Value>
{
    private Node root;
    private class Node{}
    public void put(Key key, Value val){}
    public Value get(Key key){}
    public void delete(Key key){}
    public Iterable<Key> iterator(){}
}

1.2 节点表示

作为二叉查找树的节点,它应该包含键-值,还有左右孩子

 private class Node {
        private Key key;
        private Value val;
        private Node left, right;
        public Node(Key key, Value val) {
            this.key = key;
            this.val = val;
        }
    }

1.3 取得操作

根据二叉树的性质,可以从root开始搜索:

  • 如果 小于 ,左孩子
  • 如果 大于,右孩子
    • 如果找到,返回
    • 如果为null,则取得失败
    public Value get(Key key)
    {
        Node x = root;
        while(x != null)
        {
            int cmp = key.compareTo(x.key);
            if(cmp < 0)
                x = x.left;
            else if(cmp > 0)
                x = x.right;
            else if(cmp == 0)
                return x.val;
        }
        return null;
    }

1.4 插入操作

插入操作可以用递归来做。(凡是涉及链表的插入操作,都可以考虑用递归,用非递归相对来说麻烦点,因为插入操作是在父亲节点上插入一个左右孩子,如果root为空还得单独判断)

不断的递归搜索左右孩子,直到找到一个null节点,就把新<键,值>插入。如果键本身在树中,则更新值。

这里比较巧妙的是:root = put(root, key, val);

把root也结合进去了。

    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);
        }
        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 if (cmp == 0) {
            x.val = val;
        }
        return x;
    }

也可以使用非递归的代码:

     public void put(Key key,Value val)
    {
        Node x = root;
        if(x == null)
            x = new Node(key,val);
        while(true)
        {
            int cmp = key.compareTo(x.key);
            if(cmp < 0)
            {
                if(x.left != null)
                    x = x.left;
                x.left = new Node(key,val);
                return ;
            }
            if(cmp > 0)
            {
                if(x.right != null)
                    x = x.right;
                x.right = new Node(key,val);
                return ;
            }
            if(cmp == 0){
                x.val = val;
                return ;
            }
        }
    }

1.5 动画演示

动画演示查找和插入

1.6 二叉树的形状

根据不同的插入顺序,二叉树会呈现出不同的形状,最好的状态当然是完全平衡的状态,这个时候搜索效率最高,最坏的形式就是插入的序列有序,这个时候二叉树的搜索效率就是N了。

我们看看随机插入的效果:

研究表明,二叉树的平均深度都不深,如果插入是随机的情况下。所以通常情况下,二叉树的效率还是很高的。

2. 有序操作

2.1 最大最小

这个很简单,最大的话直接搜索到最右的孩子

最小的话直接搜索到最左的孩子

2.2 floor和ceiling

floor:找到比key小的最大key

ceiling:找到比key大的最小key

2.2.1 floor操作:(递归操作)

由于floor操作是找比key小的最大key,这个概念看起来特别绕,所以必须要理清楚。

- 条件一:首先前提条件是比给定的Key

- 条件二:然后再在这些值中找最大的一个。

我们先考虑如果给定你一个节点,如何找它的floor值??很明显,它的左子树(条件1)的最右节点(条件2),所以下面的思路也是这样的,先满足条件1,再满足条件2.

  • 如果k == root.key

    floor = root.key

  • 如果k < root.key左子树继续查找(杂没比我小的)
    • 如果k > root.key 记录root(终于找到一个比我小的了,赶紧记下来)

      • 如果右边子树遇到一个小于k的,就继续(看还有么有更大的值)
      • 否则,就是root (看来没有比你大的了)
    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 x;
        int cmp = key.compareTo(x.key);
        if (cmp < 0)   return floor(x.left, key);
        if (cmp == 0)   return x;
        Node f = floor(x.right, key);
        if (f == null)
            return x;
        else
            return f;
    }

2.2.2 ceiling操作:(递归操作)

和floor操作相反

  • 如果k == root.key

    floor = root.key

  • 如果k > root.key右子树继续查找(杂没比我大的?)
    • 如果k < root.key 记录root(终于找到一个比我大的了,赶紧记下来)

      • 如果左边子树遇到一个大于k的,就继续(看还有么有更小的值)
      • 否则,就是root(看来没有比你小的了)
    public Key ceil(Key key) {
        Node x = ceil(root, key);
        if (x == null) {
            return null;
        }
        return x.key;
    }

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

2.3 子树统计

子树统计有很多应用的地方,我们经常需要查找比key小的有多少个,或者找第几大的元素,比如rank()操作,select()操作。

首先,我们需要在Node类中加入一个用来统计的变量count。并且修改size()和put函数,使得树在建立的时候,就使得count填充好。

private class Node
{
   private Key key;
   private Value val;
   private Node left;
   private Node right;
   private int count;  //加这里
}
  public int size()
  {
      return size(root);
  }
  private int size(Node x)
  {
     if (x == null) return 0;
     return x.count;  //加这里
  }
 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 if (cmp == 0) x.val = val;
    x.count = 1 + size(x.left) + size(x.right);//加这里
    return x;
 }

2.4 rank()

rank的功能就是返回键小于key的个数。有三种情况:

  • root == key

    左孩子count(因为只有左孩子小于它)

  • root < key

    左孩子count+1(自己)+ 右孩子继续找

  • root > key

    左孩子继续找(给定key还没找到比自己小的呢)

比如这个图中:

rank(S) = 6

rank(A) = 0 (左孩子为null)

rank(D) = 0+1+0+1 = 2 (1是A和C自己,0是A和C的左孩子)

    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 1 + size(x.left) + rank(x.right,key);
        else if(cmp == 0)
            return size(x.right);
        return rank(x.left,key);
    }

2.5 有序遍历

根据二叉树的结构,很简单了:

  • 遍历左孩子
  • 把自己key插入队列
  • 遍历右孩子
    public Iterable<Key> keys()
    {
        Queue<Key> q = new Queue<Key>();
        inorder(root, q);
        return q;
    }
    private void inorder(Node x,Queue<Key> q)
    {
        if(x == null)
            return ;
        inorder(x.left, q);
        q.enqueue(x.key);
        inorder(x.right,q);
    }

3 总结

用二叉排序树实现的符号表操作,其时间复杂度要大大小于其他方式。

细心的小伙伴可能发现了,我们并没有讲删除操作,因为删除操作涉及的东西比较多,我们在后面单独讲它。

时间: 2024-12-08 02:09:14

《 常见算法与数据结构》符号表ST(3)——二叉查找树 (附动画)的相关文章

C算法与数据结构-线性表的应用,多项式求和---ShinePans

/*---上机作业作业,二项式加法---*/ /*---By 潘尚 ---*/ /*---日期: 2014-5-8 . ---*/ /*---题目:---*/ //假设有两个稀疏多项式A和B,设计算法完成下列任务 //1.输入并建立多项式A和B; //2.求两个多项式的和多项式C; //3.求两个多项式的积多项式D; //输出4个多项式A,B,C,D; #include <stdio.h> #include <stdlib.h> #include <string.h>

《 常见算法与数据结构》平衡查找树(2)——红黑树(附动画)

本系列文章主要介绍常用的算法和数据结构的知识,记录的是<Algorithms I/II>课程的内容,采用的是"算法(第4版)"这本红宝书作为学习教材的,语言是java.这本书的名气我不用多说吧?豆瓣评分9.4,我自己也认为是极好的学习算法的书籍. 通过这系列文章,可以加深对数据结构和基本算法的理解(个人认为比学校讲的清晰多了),并加深对java的理解. 红黑树介绍 红黑树是一种简单的实现2-3树的数据结构,它方便的把我们之前实现的二叉搜索树改造成了一棵2-3树.它的核心思想

《 常见算法与数据结构》平衡查找树(1)—— 2-3查找树(附动画)

本系列文章主要介绍常用的算法和数据结构的知识,记录的是<Algorithms I/II>课程的内容,采用的是"算法(第4版)"这本红宝书作为学习教材的,语言是java.这本书的名气我不用多说吧?豆瓣评分9.4,我自己也认为是极好的学习算法的书籍. 通过这系列文章,可以加深对数据结构和基本算法的理解(个人认为比学校讲的清晰多了),并加深对java的理解. 2-3树介绍 我们上回说到二叉查找树已经很接近我们的目标了,在很多情况下性能都很不错,但是唯独在删除上不行,一旦删除操作做

《Algorithms 4th Edition》读书笔记——3.1 符号表(Elementary Symbol Tables)-Ⅰ

3.1符号表 符号表最主要的目的就是将一个键和一个值联系起来.用例能够将一个键值对插入符号表并希望在之后能够从符号表的所有键值对中按照键值姐找到对应的值.要实现符号表,我们首先要定义其背后的数据结构,并指明创建并操作这种数据结构以实现插入.查找操作所需要的算法. 查找在大多数应用程序中都至关重要,许多编程环境也因此将符号表实现为高级的抽象数据结构,包括Java——我们会在3.5节中讨论Java的符号表实现.下标给出的例子是在一些典型的应用场景中可能出项的键和值.我们马上会看到一些参考性的用例.3

浅谈算法和数据结构: 六 符号表及其基本实现

http://www.cnblogs.com/yangecnu/p/Introduce-Symbol-Table-and-Elementary-Implementations.html 浅谈算法和数据结构: 六 符号表及其基本实现 前面几篇文章介绍了基本的排序算法,排序通常是查找的前奏操作.从本文开始介绍基本的查找算法. 在介绍查找算法,首先需要了解符号表这一抽象数据结构,本文首先介绍了什么是符号表,以及这一抽象数据结构的的API,然后介绍了两种简单的符号表的实现方式. 一符号表 在开始介绍查找

浅谈算法和数据结构: 十一 哈希表

在前面的系列文章中,依次介绍了基于无序列表的顺序查找,基于有序数组的二分查找,平衡查找树,以及红黑树,下图是他们在平均以及最差情况下的时间复杂度: 可以看到在时间复杂度上,红黑树在平均情况下插入,查找以及删除上都达到了lgN的时间复杂度. 那么有没有查找效率更高的数据结构呢,答案就是本文接下来要介绍了散列表,也叫哈希表(Hash Table) 什么是哈希表 哈希表就是一种以 键-值(key-indexed) 存储数据的结构,我们只要输入待查找的值即key,即可查找到其对应的值. 哈希的思路很简单

算法-符号表的实现(顺序查找和二分查找)

符号表是一种存储键值对的数据结构,支持两种操作插入和查找,就是将一组新的键值对存入表中然后根据给定的键得到对应的值,在编程语言中常用Dictionary原理类似.符号表是一种典型的抽象数据结构,在生活之中应用的场景也很多,可以根据钥匙开门,域名解析的时候的IP地址查询,字典和图书的简介和页数,key和value是密不可分的,通过key我们可以很快找到我们需要的value. 无序链表的顺序查找 主要通过Node节点存储数据,之前的博客中有关于链表的实现,详情可参考之前的博客,代码有注释就解释太多了

【算法与数据结构】哈希表-链地址法

哈希表的链地址法来解决冲突问题 将所有关键字为同义词的记录存储在同一个线性链表中,假设某哈希函数产生的哈希地址在区间[0, m - 1]上,则设立一个至振兴向量 Chain  ChainHash[m]; 数据结构 //链表结点 typedef struct _tagNode { int data; //元素值(关键字) struct _tagNode* next; //下一个结点 }Node, *PNode; //哈希表结点 typedef struct _tagHashTable { //这里

Java数据结构和算法之哈希表

五.哈希表 一般的线性表.树中,记录在结构中的相对位置是随机的即和记录的关键字之间不存在确定的关系,在结构中查找记录时需进行一系列和关键字的比较.这一类查找方法建立在"比较"的基础上,查找的效率与比较次数密切相关.理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立一确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应.因而查找时,只需根据这个对应关系f找到给定值K的像f(K).若结构中存在关键字和K相等的记录,则必定在f(K)的存储位置上,由此不需