我们所使用的数据结构有结点组成,结点所办韩的链接可以指向空(null)或者其他节点。在二叉树中,每个节点只能有一个父节点指向自己(只有一个例外,也就是根结点,它没有父结点),而且每个结点都只有左右两个链接,分别指向自己的左子结点和右子结点。尽管我们指向的是结点,但我们可以将每个链接看作指向了另一颗二叉树,而这棵树的根结点就是被指向的结点。因此我们可以将二叉树定义为一个空链接,或者是一个有左右两个链接的结点,每个链接都指向一颗子二叉树。
一颗二叉查找树每一个结点都含有一个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;
}
return x.N;
}
}
上面的代码定义了二叉查找树的数据结构。和定义链表一样我们嵌套定义了一个私有类来表示二叉查找树的一个结点。每个结点都含有一个键,一个值,一条左链接,一条右链接和一个结点计数器。左链接指向一颗由小于该结点的所有键组成的二叉查找树,右连接指向一颗由大于该结点的所有键组成的二叉查找树。变量N给出了以该结点为根的子树的结点总数。
私有方法size()会将空链接的值当做0,这样我们就能保证一下公式对于二叉树中的任意结点x总是成立:
size(x) = size(x.left) + size(x.right) + 1
一颗二叉查找树代表了一组键(及其相应的值)的集合,而同一个集合可以用多颗不同的二叉查找树来表示。如果我们将一颗而查找树的所有键投影到一条直线上,保证一个结点的左子树中的键出现在他的左边,右子树中的键出现在他的右边,那么我们一定可以得到一条有序的键列。我们会利用二叉查找树的这种天生的灵活性,用多颗二叉查找树表示同一组有序的键来实现构建和使用二叉查找树的高效算法。
查找与插入
一般来说,查找有两种结构。如果包含该键的结点存在,我们的查找就命中了,然后返回相应的值。否则查找未命中。根据数据表示的递归结构我们马上就能得到,在二叉查找树中查找一个键的递归算法:如果树是空的,则查找未命中;如果被查找的键和根结点的键相等,查找命中,否则我们就(递归地)在适当的子树中继续查找。如果被查找的键较小就选择左子树,较大则选择右子树。
二叉树查找树的查找及插入实现如下:
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);
}
return x.val;
}
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, 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;
}
上述代码实现了二叉查找树中的put()和get()方法,他们的实现都是通过递归完成的。
插入的逻辑和查找的逻辑很相似:如果树是空的,就返回一个含有该结点键值对的新结点;如果被查找的键小于根结点的键,我们会继续在左子树中插入该键,否则在右子树中插入该键。
分析
使用二叉查找树的运行时间取决于树的形状,而树的形状又取决于键被插入的先后顺序。在最好的情况下,一颗含有N个节点的树是完全平衡的,每条空链接和根结点的距离都是lgN。在最坏的情况下,搜索路径上可能有N个节点。
假设键的分布是随机的,可以看出:二叉查找树和快速排序几乎是“双胞胎”。树的根结点就是快速排序中的第一个切分元素(左侧的键都比他小,右侧的键都比他大),以此类推,这和快速排序算法中队子数组的递归排序完全对应。
最大键和最小键
如果根结点的左链接为空,那么一颗二叉查找树中的最小键就是根结点;如果左链接不为空,那么树中的最小键就是左子树中的最小键。找到最大键的方法也是类似的,只是变成查找右子树而已。
获取最小值方法实现如下:
public Key min() {
return min(root).key;
}
private Node min(Node x) {
if (x.left == null) {
return x;
}
return min(x.left);
}
删除最大键和最小键
对于deleteMin(),我们要不断深入根结点的左子树中直到遇到一个空链接,然后将纸箱该结点的链接指向他的右子树(只需在递归中调用返回它的右链接即可)。此时已经没有任何链接指向要被删除的结点,因此他会被垃圾处理器清理掉。deleteMax()方法的实现和deleteMin()方法完全类似。
来源:https://blog.csdn.net/u013049016/article/details/99650392