二叉搜索树
二叉搜索树定义
先来说说这个二叉排序树的定义和性质:
定义:二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:
(1)若左子树不空,则左子树上所有结点的键值均小于或等于它的根结点的键值;
(2)若右子树不空,则右子树上所有结点的键值均大于或等于它的根结点的键值;
(3)左、右子树也分别为二叉排序树;
(4)节点的Key唯一
二叉搜索树BST的优点以及缺点
根据BST的定义和中序遍历(左根右节点遍历顺序)可得出,对于二叉搜索树的中序遍历可以使得节点有序。
BST的优点对于dictionary来说,主要优点就是使得查找速度大大提升,不用再进行遍历操作。时间复杂度一般情况下是O(logn),但是最坏的情况下会出现“链表”的形式,复杂度退化到O(N),比如下所示
11
\
13
\
15
\
17
当然也有基于BST优化出的各种平衡树,来保证时间复杂度O(logn)的稳定性。
二叉搜索树的Java实现
下面的代码将用java实现,并且全部基于递归实现(非递归算法复杂一些且效率高)。主要讨论BST的如下操作:
查找、插入、最大键、最小键、向上取整、向下取整、排名k的键、获取键key的排名、删除最大最小键、删除操作、范围查找。
1.结点的数据结构的定义
下面是BST(后文都用BST表示二叉排序树)中结点的数据结构的定义。
private class Node{
private Key key;//键
private Value value;//值
private Node left, right;//指向子树的链接:包括左子树和右子树.
private int N;//以当前节点为根的子树的结点总数
//构造器
public Node(Key key, Value value, int N) {
this.key = key;
this.value = value;
this.N = N;
}
}
此外,对于整个二叉查找树来说,有一个根节点,所以在BST类中定义了一个根结点:
private Node root;//二叉查找树的根节点
2. 计算二叉排序树的size
思想: 根据我们数据结构Node中的定义,里面有一个属性 N 表示的就是以当前节点为根的子树的结点总数。所以源码如下:
/**
* 获取整个二叉查找树的大小
* @return
*/
public int size(){
return size(root);
}
/**
* 获取某一个结点为根结点的二叉查找树的大小
* @param x
* @return
*/
private int size(Node x){
if(x == null){
return 0;
} else {
return x.N;
}
}
3. 查找和插入
在实现BST类的时候,BST继承自Comparable接口的,实现compareTo()函数。因为我们知道二叉查找树的键值是有有序的,左子树小于根节点,右子树大于根节点。所以实现Comparable接口,那么我们就很容易根据key找到插入的位置,而且对于BTS来说,插入的位置都是在叶子节点处。对于插入和查找都是基于键值的比较。下面是源码:
/**
* 查找:通过key获取value
* @param key
* @return
*/
public Value get(Key key){
return get(root, key);
}
/**
* 在以 x 为根节点的子树中查找并返回Key所对应的值,如果找不到就返回null
* 递归实现
* @param x
* @param key
* @return
*/
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.value;
}
}
/**
* 插入:设置键值对
* @param key
* @param value
*/
public void put(Key key, Value value){
root = put(root, key, value);
}
/**
* key如果存在以 x 为根节点的子树中,则更新它的值;
* 否则将key与value键值对插入并创建一个新的结点.
* @param x
* @param key
* @param value
* @return
*/
private Node put(Node x, Key key, Value value){
if( x==null ){
x = new Node(key, value, 1);
return x;
}
int cmp = key.compareTo(x.key);
if(cmp<0){
x.left = put(x.left, key, value);
}else if(cmp>0){
x.right = put(x.right, key, value);
} else{
x.value=value;//更新value的值
}
//设置根节点的N属性
x.N = size(x.left) + size(x.right) + 1;
return x;
}
4. 最大键和最小键的实现
求BST中的最大键值和最小键值。根据BSt的特性,其实原理很简单:最小值就是最左下边的一个节点。最大键值就是最右下边的结点。源码如下:
/**
* 最小键
*/
public Key min(){
return min(root).key;
}
/**
* 返回结点x为root的二叉排序树中最小key值的Node
* @param x
* @return 返回树的最小key的结点
*/
private Node min(Node x){
if(x.left == null){
return x;
}else{
return min(x.left);
}
}
/**
* 最大键
*/
public Key max(){
return max(root).key;
}
/**
* 返回结点x为root的二叉排序树中最大key值的Node
* @param x
* @return
*/
private Node max(Node x){
if(x.right == null){
//右子树为空,则根节点是最大的
return x;
}else{
return max(x.right);
}
}
5. Key值向下取整和向上取整
所谓的向下取整和向上取整,就是给定键值key,向下取整的意思就是找出小于等于当前key的的Key值。向上取整的意思就是找出大于等于当前key的的Key值。实现也是基于Comparable接口的,具体的源码如下:
/**
* key向下取整
*/
public Key floor(Key key){
Node x = floor(root, key);
if(x == null){
return null;
}
return x.key;
}
/**
* 以x 为根节点的二叉排序树,查找以参数key的向下取整的Node
* @param x
* @param key
* @return
*/
private Node floor(Node x