【算法】二叉查找树(BST)实现字典API
二叉查找树的定义
一颗二叉查找树对应一个有序序列
本文的字典API
int size() 获取字典中键值对的总数量
void put(int key, int val) 将键值对存入字典中
int get(int key) 获取键key对应的值
void delete(int key) 从字典中删去对应键(以及对应的值)
int min() 字典中最小的键
int max() 字典中最大的键
int rank(int key) key在键中的排名(小于key的键的数量)
int select(int k) 获取排名为k的键
BST类的基本结构
public class BST {
Node root; // 根结点
private class Node { // 匿名内部类Node
int key; // 存储字典的键
int val; // 存储字典的值
Node left,right; // 分别表示左链接和右链接
int N; // 以该结点为根的子树中的结点总数
public Node (int key,int val,int N) {
this.key = key;
this.val = val;
this.N = N;
}
}
public int get (int key) { }
public void put (int key,int val) { }
// 其他方法 ... ...
}
Node内部类中成员变量N的作用
- 如果你不需要rank/select方法, 那么N完全可以设为BST的成员变量, 表示的是整棵树的结点总数, 维护N的代码编写很简单:在调用put方法时候使其加1, 在调用delete方法时使其减1。
- 如果你需要rank/select方法,则需对每个结点单独设N,代表的是该结点为根的子树中的结点总数,维护N的代码编写将会复杂很多,但这是必要的。(具体往下看)
方法设计的共同点
// 针对某个结点设计的递归处理方法
private int get(Node x, int key) {
// 递归调用get方法
}
// 将root作为上面方法的参数,从根结点开始处理整颗二叉树
public int get(int key) {
return get(root, key)
}
size方法
private int size (Node x) {
if(x == null) return 0;
return x.N;
}
public int size () {
return size(root);
}
- 当结点存在的时候,返回结点所在子树的结点总数(包括自身)
- 当结点不存在的时候,即x为null时,返回0
get方法
- key小于当前结点的键,说明key在左子树,向左儿子递归调用get
- key大于当前结点的键,说明key在右子树,向右儿子递归调用get
- key等于当前结点的键,查找成功并返回对应的值
- 查找到给定的key,返回对应的值
- x迭代至最下方的结点也没有查找到key,因为x.left=x.right=null,在下一次调用get返回-1,结束递归
private int get (Node x,int key) {
if(x == null) return -1; // 结点为空, 未查找到
if(key<x.key) {
return get(x.left,key); // 键在左子树,向左子树查找
}else if(key>x.key) {
return get(x.right, key); // 键在右子树,向右子树查找
}else{
return x.val; // 查找成功,返回值
}
}
public int get (int key) {
return get(root,key);
}
put方法
- key小于当前结点的键,向左子树插入
- key大于当前结点的键,向右子树插入
- key等于当前结点的键,则将值替换为给定的val
private Node put (Node x, int key, int val) {
if(x == null) return new Node(key,val,1); // 未查找到key,创建新结点,并插入树中
if(key<x.key){
x.left = put(x.left,key,val); // 向左子树插入
}else if(key>x.key){
x.right = put(x.right,key,val); // 向右子树插入
}else {
x.val = val; // 查找到给定key, 更新对应val
}
x.N =size(x.left) + size(x.right) + 1; // 更新结点计数器
return x; //
}
public void put (int key,int val) {
if(root == null) root = put(root,key,val); // 向空树中插入第一个结点
put(root,key,val);
}
x.N =size(x.left) + size(x.right) + 1; // 更新结点计数器
- 递归调用前代码先执行, 而递归调用后代码后执行
- 递归调用前代码是一个“沿着树向下走”的过程,即递归层次是由浅到深, 而递归调用后代码是一个“沿着树向上爬”的过程, 即递归层次是由深到浅
- 先“沿着树向下走”, 插入或更新结点
- 再“沿着树向上爬”, 更新结点计数器N
min,max方法
private Node min (Node x) {
if(x.left == null) return x; // 如果左儿子为空,则当前结点键为最小值,返回
return min(x.left); // 如果左儿子不为空,则继续向左递归
}
public int min () {
if(root == null) return -1;
return min(root).key;
}
deleteMin方法
public Node deleteMin (Node x) {
if(x.left==null) return x.right; // 如果当前结点左儿子空,则将右儿子返回给上一层递归的x.left
x.left = deleteMin(x.left);// 向左子树递归, 同时重置搜索路径上每个父结点指向左儿子的链接
x.N = size(x.left) + size(x.right) + 1; // 更新结点计数器N
return x; // 当前结点不是min ###
}
public void deleteMin () {
root = deleteMin(root);
}
- 沿搜索路径重置结点链接
- 更新路径上的结点计数器
- 在递归到最后一个结点前, 下一层递归返回值是x(代码中###处), 这时,对上一层递归来说, x.left = deleteMin(x.left)等同于x.left = x.left
- 当递归到最后一个结点时,下一层递归中x = min, x.left==null判定为true, 返回x.right给上一层递归, 对上一层递归来说,x.left = deleteMin(x.left)等同于x.left = x.left.right;
delete方法
- 相对于父节点(A)而言是有序的。
- 相对于左子树(B)而言是有序的(15原本位于14右子树,所以大于14的左子树)
- 相对于右子树(C)而言是有序的(15是原来14右子树的最小键,移动后也小于C中其他结点)
- 查找到相应的结点
- 将其删除
public Node delete (int key,Node x) {
if(x == null) return null;
if(key<x.key){
x.left = delete(key,x.left); // 向左子树查找键为key的结点 #1
}else if (key>x.key){
x.right = delete(key,x.right); // 向右子树查找键为key的结点 #2
}else{ // 在这个else里结点已经被找到,就是当前的x
// 这里处理的是上述的 第一种情况和第二种情况:左子树为null或右子树为null(或都为null)
if(x.left==null) return x.right; // 如果左子树为空,则将右子树赋给父节点的链接 #3
if(x.right==null) return x.left; // 如果右子树为空,则将左子树赋给父节点的链接 #4
// 这里处理的是上述的第三种情况
Node inherit = min(x.right); // 取得结点x的继承结点
inherit.right = deleteMin(x.right); // 将继承结点从原来位置删除,并重置继承结点右链接
inherit.left = x.left; // 重置继承结点左链接
x = inherit; // 将x替换为继承结点
}
x.N = size(x.left)+ size(x.right) + 1; // 更新结点计数器
return x; // #5
}
public void delete (int key) {
root = delete(key, root);
}
rank方法
public int rank (Node x,int key) {
if(x == null) return 0;
if(key<x.key) {
return rank(x.left,key);
}else if(key>x.key) {
return size(x.left) + 1 + rank(x.right, key);
}else {
return size(x.left);
}
}
public int rank (int key) {
return rank(root,key);
}
select方法
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 select (int k) {
return select(root,k).key;
}
floor、ceiling方法
- 如果递归返回null,说明右子树没有floor值,所以floor值就是当前结点的键,
- 如果递归不为null,说明右子树还有比当前结点键更大的floor值,所以返回递归后的非null的floor值
private Node floor (Node x,int key) {
if(x==null) return null;
if(key<x.key){ // key小于当前结点的键
return floor(x.left,key); // key的floor值在左子树,向左递归
}else if(key==x.key) {
return x; // 和key相等,也是floor值,返回
}else { // 这里排除floor值在左子树,剩下两种可能:floor值是当前结点或在右子树
Node n = floor(x.right, key);
if(n==null) return x; // 右子树没有找到floor值,所以当前结点键就是floor
else return n; // 右子树找到floor值,返回找到的floor值
}
}
public int floor (int key) {
if(root==null) return -1; //树为空, 没有floor值
return floor(root, key).key;
}
【算法】二叉查找树(BST)实现字典API的更多相关文章
- 【算法】二叉查找树实现字典API
参考资料 <算法(java)> — — Robert Sedgewick, Kevin Wayne <数据结构> ...
- 【算法】实现字典API:有序数组和无序链表
参考资料 <算法(java)> — — Robert Sedgewick, Kevin Wayne <数据结构> ...
- 查找系列合集-二叉查找树BST
一. 二叉树 1. 什么是二叉树? 在计算机科学中,二叉树是每个结点最多有两个子树的树结构. 通常子树被称作“左子树”(left subtree)和“右子树”(right subtree). 二叉树常 ...
- 二叉查找树BST 模板
二叉查找树BST 就是二叉搜索树 二叉排序树. 就是满足 左儿子<父节点<右儿子 的一颗树,插入和查询复杂度最好情况都是logN的,写起来很简单. 根据BST的性质可以很好的解决这些东 ...
- 数据结构和算法(Golang实现)(27)查找算法-二叉查找树
二叉查找树 二叉查找树,又叫二叉排序树,二叉搜索树,是一种有特定规则的二叉树,定义如下: 它是一颗二叉树,或者是空树. 左子树所有节点的值都小于它的根节点,右子树所有节点的值都大于它的根节点. 左右子 ...
- 二叉查找树(BST)
二叉查找树(BST):使用中序遍历可以得到一个有序的序列
- [学习笔记] 二叉查找树/BST
平衡树前传之BST 二叉查找树(\(BST\)),是一个类似于堆的数据结构, 并且,它也是平衡树的基础. 因此,让我们来了解一下二叉查找树吧. (其实本篇是作为放在平衡树前的前置知识的,但为了避免重复 ...
- 从一段简单算法题来谈二叉查找树(BST)的基础算法
先给出一道很简单,喜闻乐见的二叉树算法题: 给出一个二叉查找树和一个目标值,如果其中有两个元素的和等于目标值则返回真,否则返回假. 例如: Input: 5 / \ 3 6 / \ \ 2 4 7 T ...
- 【查找结构 2】二叉查找树 [BST]
当所有的静态查找结构添加和删除一个数据的时候,整个结构都需要重建.这对于常常需要在查找过程中动态改变数据而言,是灾难性的.因此人们就必须去寻找高效的动态查找结构,我们在这讨论一个非常常用的动态查找树— ...
随机推荐
- python大法好——异常
---恢复内容开始--- Python 异常处理 python提供了两个非常重要的功能来处理python程序在运行中出现的异常和错误.你可以使用该功能来调试python程序. 异常处理: 本站Pyth ...
- centos 7设置本地yum资源库
前言 同样的,是在这两天安装ambari的时候遇到的问题之一,那就是关于centos的本地yum源的制作,当时是一种是制作iso镜像的yum源,还有一种将rpm软件包打成压缩包上传到centos的某一 ...
- request设置cookies
mycookie = { "PHPSESSID":"56v9clgo1kdfo3q5q8ck0aaaaa" } request.get(url,cookies ...
- Html转成Image
html转图片,原来用了html2image.jar,但是做不到复杂的css js渲染效果. 在网友推荐下,可以用wkhtmltoimage插件,这是用谷歌浏览器的内核webkit,网上搜一下可以下载 ...
- unity编程心得
1. 不要通过public变量 从工程面板 直接 拖 GameObjct 的引用, 当这样的public变量很多 ,子物体很多,又没有做成预制体,,别人重新移植这段功能会很麻烦,,应该用GameOb ...
- DJango 基础 (5)
模板加载静态文件 在settings.py文件中添加STATICFILES_DIRS,设置静态文件目录路径,同templates. # settings.py文件中STATIC_URL = '/st ...
- RESTful API格式 图片验证码接口
之前公司写图片验证码时用的是session 后来写接口时也想用session存验证码 不过领导说RESTful API 写接口 没有session这一说 于是就用了redis 存验证码 还有就是接口 ...
- nlp算法工程师养成记 目标要求
时间规定: 2018.12.07-2018.02.15 能力养成: linux, shell python, c++(会多少算多少) tensorflow, keras, pytorch(tf优先) ...
- input只允许输入正整数
onkeyup="if(this.value.length==1){this.value=this.value.replace(/[^1-9]/g,'')}else{this.value=t ...
- Newtonsoft.Json反序列化(Deserialize)出错:Bad JSON escape sequence
使用Newtonsoft.Json反序列化收到的字串为JObject或其它支持的数据模型,有时错误,提示如下: Bad JSON escape sequence: \c. Path , positio ...