BST 实现
package src.dataStructure;
/**
* BST二叉搜索树,查找效率为1.39lgN
* put()方法的效率仅仅比查找多了一次比较而已
* 增删改查的效率都为对数阶
* 树的最坏情况下的效率与树度高度成正比
* BST的良好性能依赖于键的随机分布性,如果键分布不随机甚至是顺序或者逆序
* 则BST的性能会达到线性阶。但可以使用AVL平衡二叉树保证无论键的分布是否随机化
* 操作的效率都是对数阶
* 方法:size()、put()、get()、max()、min()、floor()、ceiling()、select()、rank()、
* deleteMin()、delete()、Keys()
* @param <Key>
* @param <Value>
*/
public class BST<Key extends Comparable<Key>,Value>
{
private class Node
{
private Key key;
private Value value;
private Node left;
private Node right;
private int N;
public Node(Key key, Value value,int N)
{
this.key = key;
this.value = value;
this.N = N;
}
@Override
public String toString()
{
return "[key:" + key + ",value:" + value + "]";
}
}
private Node root;
/**
* 返回根节点的结点总数
* @return
*/
public int size()
{
return size(root);
}
/**
* 返回以root为根节点的二叉树的节点总数
* @param node
* @return
*/
public int size(Node node)
{
if (node == null)
return 0;
else
return node.N;
}
public Value get(Key key)
{
return get(root,key);
}
/**
* 在以node为根节点的二叉树中,根据key查找结点,找到Value,否则返回null
* 比较key与根节点key的值
* 如果key小于根节点,则递归根节点左子树继续查找
* 如果key等于根节点,则根节点就是要查找的结点,返回value
* 如果key大于根节点,则递归右子树继续查找
* @param node
* @param key
* @return
*/
private Value get(Node node,Key key)
{
if (node == null)
return null;
int com = key.compareTo(node.key);
if (com == 0)
return node.value;
else if (com < 0)
return get(node.left,key);
else
return get(node.right,key);
}
public void put(Key key,Value value)
{
if (this.root == null)
this.root = new Node(key,value,1);
else
put(root,key,value);
}
/**
* 将新的键值对插入BST中,如果键已经存在,则更新value的值,否则将
* 新的键值对插入树中
*操作与get()方法大相径庭,在二叉树中查找key,如果找到更新value,如果
* 没有找到,则当前路径一定遍历到了叶子结点的链接,它是个空链接,且
* 该位置就是要插入结点的位置,则创建一个新的结点,并把叶子结点的链接赋值给它,
* 同时因为创建了一个结点,则遍历路径上的所有结点的结点计数器N+1
*
* @param node
* @param key
* @param value
*/
public Node put(Node node,Key key,Value value)
{
if (node == null)
return new Node(key,value,1);
int com = key.compareTo(node.key);
if (com == 0)
node.value = value;
else if (com < 0)
node.left = put(node.left,key,value);
else
node.right = put(node.right,key,value);
node.N = size(node.left) + size(node.right) + 1;
return node;
}
/**
* 当二叉树为空时,注意空指针异常
* @return
*/
public Key min()
{
Node res = min(root);
if (res != null)
return res.key;
else
return null;
}
/**
* 返回以node为根节点的二叉树的最小键的结点
* 如果根节点的左子树不为空,则最小键一定在左子树中,递归左子树继续查找
* 如果根节点的左子树为空,则最小键就是根节点,返回根节点
* @param node
* @return
*/
private Node min(Node node)
{
if (node.left != null)
return min(node.left);
else
return node;
}
/**
* 当二叉树为空时,注意空指针异常
* @return
*/
public Key max()
{
Node res = max(root);
if (res != null)
return res.key;
else
return null;
}
/**
* 返回以node为根节点的二叉树中的最大键的结点
* 如果根节点的右子树不为空,则最大键一定在右子树中,则递归右子树进行查找
* 如果根节点的右子树为空,则根节点就是最大键,返回根节点
* @param node
* @return
*/
private Node max(Node node)
{
if (node.right != null)
return max(node.right);
else
return node;
}
public Key floor(Key key)
{
Node res = floor(root,key);
if (res != null)
return res.key;
else
return null;
}
/**
* 返回以node为根节点的二叉树中,小于等于key的最大键
* 将key与根节点比较
* 如果key等于根节点,则小于等于key的最大键就是根节点
* 如果key小于根节点,则小于等于key的最大键一定在左子树中,递归左子树进行查找
* 如果key大于根节点,则当右子树中存在小于等于key的结点时,小于等于key的最大键才会在右子树中
* 否则小于等于key的最大键就是根节点,返回根节点
* @param node
* @return
*/
private Node floor(Node node,Key key)
{
if (node == null)
return null;
int com = key.compareTo(node.key);
if (com == 0)
return node;
else if (com < 0)
return floor(node.left,key);
else
{
Node res = floor(node.right,key);
if (res != null)
return res;
else
return node;
}
}
public Key ceiling(Key key)
{
Node res = ceiling(root,key);
if (res != null)
return res.key;
else
return null;
}
/**
* 返回以node为根节点的二叉树中,大于等于key的最小键
* 比较key与root.key的大小
* 如果key等于根节点,则大于等于key的最小键就是root
* 如果key大于根节点,则大于等于key的最小键一定在右子树中,则递归右子树继续查找
* 如果key小于根节点,则只有当左子树中存在大于等于key的结点时,大于等于key的最小键才会在左子树中
* 否则大于等于key的最小键就是根节点
* @param node
* @param key
* @return
*/
private Node ceiling(Node node,Key key)
{
if (node == null)
return null;
int com = key.compareTo(node.key);
if (com == 0)
return node;
else if (com > 0)
return ceiling(node.right,key);
else
{
Node res = ceiling(node.left,key);
if (res != null)
return res;
else
return node;
}
}
public Key select(int k)
{
Node res = select(root,k);
if (res != null)
return res.key;
else
return null;
}
/**
* 返回以node为根节点的二叉树中,排名为k的结点(即有k个结点的键小于它)
* 判断根节点左子树结点个数size,如果左子树结点个数等于k,则根节点就是排名为k的结点,返回根节点
* 如果k小于size,则排名为k的结点一定在左子树中,递归左子树继续查找
* 如果k>size,则排名为k的结点一定在右子树中,递归右子树查找排名为(k-size-1)的结点
* @param node
* @param k
* @return
*/
private Node select(Node node,int k)
{
if (node == null)
return null;
int size = size(node.left);
if (k < size)
return select(node.left,k);
else if (k == size)
return node;
else
return select(node.right,k - size - 1);
}
public int rank(Key key)
{
return rank(root,key);
}
/**
* 返回以node为根节点的二叉树,键key的结点的排名
* 比较key与根节点的 大小
* 如果key等于根节点,则排名为左子树中结点的数量
* 如果key小于根节点,则递归左子树进行查找
* 如果key大于根节点,则排名为左子树中结点的数量+1+在右子树中的排名
* @param node
* @param key
* @return
*/
private int rank(Node node,Key key)
{
if (node == null)
return 0;
int com = key.compareTo(key);
if (com < 0)
return rank(node.left,key);
else if (com == 0)
return size(node.left);
else
return size(node.left) + 1 + rank(node.right,key);
}
public void deleteMin()
{
root = deleteMin(root);
}
/**
* 删除以node为根节点的二叉树中键最小的结点
* 找到键最小的结点,返回它的右子树,并让最小结点的父节点的左链接指向
* 它的右子树
* 当没有链接指向被删除的结点时,被删除的结点会被JVM当作垃圾回收
* @param node
* @return
*/
private Node deleteMin(Node node)
{
if (node.left == null)
//当前结点就是键最小的结点,返回它的右子树
return node.right;
node.left = deleteMin(node.left);
node.N = size(node.left) + size(node.right) + 1;
return node;
}
public void delete(Key key)
{
root = delete(root,key);
}
/**
* 通过key查找要删除的结点,将该节点右子树中键最小的结点替代该位置
* 1.找到要删除的节点后,将结点赋值给引用遍历t
* 2.找到该节点右子树中键最小的结点,并将该节点的引用赋值给x
* 3.调用deleteMin(t.right)删除右子树中最小的结点,方法返回删除后右子树的根节点
* 4.将x替代要删除的结点,即将x左链接指向t的左链接,右链接指向deleteMin(t.right)
* @param node
* @param key
* @return
*/
private Node delete(Node node,Key key)
{
if (node == null)
//要删除的结点不存在
return null;
int com = key.compareTo(node.key);
if (com < 0)
node.left = delete(node.left,key);
else if (com == 0)
{
//1.要删除的结点是叶子节点或者只有一个子结点
//则将该子节点赋值返回,即赋值给它父节点的链接
if (node.left == null)
return node.right;
if (node.right == null)
return node.left;
//2.要删除的结点有两个子结点
Node t = node; //该节点就是要删除的结点
node = min(t.right);
node.left = t.left;
node.right = deleteMin(t.right);
}
else
node.right = delete(node.right,key);
node.N = size(node.left) + size(node.right) + 1;
return node;
}
public void preOrder()
{
if (root != null)
preOrder(root);
else
System.out.println("二叉树为空");
}
public void preOrder(Node node)
{
if (node == null)
return;
preOrder(node.left);
System.out.println(node);
preOrder(node.right);
}
}
测试:
BST<String, String> BST = new BST<>();
BST.put("N","南京");
BST.put("B","北京");
BST.put("S","上海");
BST.put("G","广州");
BST.put("H","杭州");
BST.put("C","重庆");
BST.put("X","厦门");
BST.put("L","拉萨");
System.out.println(BST.get("L"));
System.out.println("****************分界线********************");
BST.preOrder();
System.out.println("****************分界线********************");
System.out.println(BST.max());
System.out.println(BST.min());
System.out.println(BST.floor("D"));
System.out.println(BST.ceiling("R"));
System.out.println("****************分界线********************");
System.out.println(BST.size());
System.out.println(BST.select(3));
System.out.println(BST.rank("N"));
System.out.println("****************分界线*********************");
BST.deleteMin();
BST.delete("S");
BST.preOrder();
System.out.println("****************分界线********************8");
输出:
拉萨
分界线****
[key:B,value:北京]
[key:C,value:重庆]
[key:G,value:广州]
[key:H,value:杭州]
[key:L,value:拉萨]
[key:N,value:南京]
[key:S,value:上海]
[key:X,value:厦门]
分界线****
X
B
C
S
分界线****
8
H
5
分界线*****
[key:C,value:重庆]
[key:G,value:广州]
[key:H,value:杭州]
[key:L,value:拉萨]
[key:N,value:南京]
[key:X,value:厦门]
****************分界线********************8
参考:
《Algorithms Fourth Edition》