算法(3.2 二叉查找树)

定义。一棵二叉查找树(BST)是一棵二叉树,其中每个结点都含有一个Comparable的键(以及相关联的值) 且每个结点的键都大于其左子树中的任意结点的键而小于右子树的任意结点的键。

package serach;


import java.util.LinkedList;
import java.util.Queue;

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);
	}
	public int size(Node x){
		if (x == null){
			return 0;
		}
		return x.N;
	}
	
	public Value get(Key key){
		return get(root, key);
	}
	private Value get(Node x, Key key){
		//	在以x为根节点的子树中查找并返回key所对应的的值
		//	如果找不到则返回null
		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.val;
		}
	}

	public void put(Key key, Value val){
		//	查找key,找到则更新它的值, 否则为它创建一个新的结点
		root = put(root, key, val);
	}
	private Node put(Node x, Key key, Value val){
			//	如果key存在于以x为结点的子树中则更新它的值
			//	否则将以key和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);
		}
		x.val = val;
		x.N = size(x.left) + size(x.right) + 1;
		return x;
	}
	
	public Key min(){
		return min(root).key;
	}
	private Node min(Node x){
		if (x.left == null){
			return x;
		}
		return min(x.left);
	}
	
	
	public Key floor(Key key){
		Node x = floor(root, key);
		if (x == null){
			return null;
		}
		return x.key;
	}
	
//	3.2.3.2 向上取整合向下取整
//	如果给定的键key小于二叉查找树的根结点的
//	那么小于等于key 的最大键floor(key)一定
//	在左根结点的左子树中; 如果给定的键key 大于二叉
//	查找树的根结点,那么只有当根结点右子树中存在
//	小于等于key 的结点时,小于等于key 的最大键才
//	会出现在右子树中,否则根结点就是小于等于key
//	的最大键。这段描述说明了f1oorO 方法的递归实
//	现,同时也递推地证明了它能够计算出预期的结果。
//	将“左”变为“右”(同时将小于变为大于)就能
//	够得到cei1ing() 的算法。
	private Node floor(Node x, Key key){
		if (x == null){
			return null;
		}
		int cmp = key.compareTo(x.key);
		if (cmp == 0){
			return x;
		}else if (cmp < 0){
			return floor(x.left, key);
		}
		Node t = floor(x.right, key);
		if (t == null){
			return x;
		}else{
			return t;
		}
	}
	
//	3.2.3.4 排名
//	rank()是se1ect() 的逆方法,它会返回给定键的排名。它的实现和select()类似,如果给
//	定的键和根结点的键相等,我们返回左子树中的结点总数1; 如果给定的键小于根结点,我们会返
//	回该键在左子树中的排名(递归计算》;如果给定的键大于根结点,我们会返回t1(根结点)加
//	上它在右子树中的排名(递归计算)。
	public Key select(int k){
		return select(root, k).key;
	}
	private Node select(Node x, int k){
		// 返回排名为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 rank(Key key){
		return rank(key, root);
	}
	public int rank(Key key, Node x){
		// 返回以x为根结点的子树中小于x.key的键的数量
		if (x == null){
			return 0;
		}
		int cmp = key.compareTo(x.key);
		if (cmp < 0){
			return rank(key, x.left);
		}else if (cmp > 0){
			return 1 + size(x.left) + rank(key, x.right);
		}else{
			return size(x.left);
		}
	}
	
//	3 2.3.5 删除最大键和删除最小键
//	
//	二叉查找树中最难实现的方法就是de1ete()
//	方法,即从符号表中删除一个键值对。作为热身运动,
//	我们先考虑deleteMin()方法(删除最小键	所对应的键值对),和put()
//	一样,我们的递归方法接受一个指向结点的链接,并
//	返回一个指向结点的链接。这样我们就能够方便地
//	改变树的结构,将返回的链接赋给作为参数的链接。
//	对于deleteMinO,我们要不断深人根结点的左子
//	树中直至遇见一个空链接,然后将指向该结点的链
//	接指向该结点的右子树(只需要在递归调用中返回
//	它的右链接即可)。此时已经没有任何链接指向要
//	被删除的结点,因此它会被垃圾收集器清理掉。我
//	们给出的标准递归代码在删除结点后会正确地设置
//	它的父结点的链接并更新它到根结点的路径上的所
//	有结点的计数器的值。deleteMax() 方法的实现和
//	de1eteMin()完全类似。
	
	public void deleteMin(){
		root = deleteMin(root);
	}
	public Node deleteMin(Node x){
		if (x.left == null){
			return x.right;
		}
		x.left = deleteMin(x.left);
		x.N = size(x.left) + size(x.right) + 1;
		return x;
	}
	
	
	
//	3.2.3.6 删除操作
//	
//	我们可以用类似的方式删除任意只有一个子结
//	点(或者没有子结点)的结点,但应该怎样删除一
//	个拥有两个子结点的结点呢? 删除之后我们要处理
//	两棵子树,但被删除结点的父结点只有一条空出来
//	的链接。T.Hibbard 在1962 年提出了解决这个难题
//	的第一个方法,在删除结点x 后用它的后继结点填
//	补它的位置。因为x 有一个右子结点,因此它的后继结点就是其右子树中的最小结点。
//	这样的替换	仍然能够保证树的有序性,
//	因为x.key 和它的后继结点的键之间不存在其他的键。
//	我们能够用4个	简单的步骤完成将x 替换为它的后继结点的任务
//	1.将指向即将被删除的结点的链接保存为t;
//	2.将x 指向它的后继结点min(t.right);
//	3.将x 的右链接(原本指向一棵所有结点都大于x.key的二叉查找树)指向deleteMin(t.right),
//		也就是在删除后所有结点仍然都大于x.key的子二叉查找树;
//	4.将x的左链接(本为空)设为t.1eft (其下所有的键都小于被删除的结点和它的后继
//	结点)。

	public void delete(Key key){
		root = delete(root, key);
	}
	public Node delete(Node x, Key key){
		if (x == null){
			return null;
		}
		int cmp = key.compareTo(x.key);
		if (cmp < 0){
			x.left = delete(x.left, key);
		}else if (cmp > 0){
			x.right = delete(x.right, key);
		}else{
			if (x.left == null){
				return x.right;
			}else if (x.right == null){
				return x.left;
			}
			Node t = x;
			x = min(t.right);
			x.right = deleteMin(t.right);
			x.left = t.left;
		}
		x.N = size(x.left) + size(x.right) + 1;
		return x;
	}
	
	
	//	二叉查找树的方位查找操作
	public Iterable<Key> keys(){
		return null;
		
	}
	public Iterable<Key> keys(Key lo, Key hi){
		Queue<Key> queue = new LinkedList<Key>();
		keys(root, queue, lo, hi);
		return queue;
	}
	private void keys(Node x, Queue<Key> queue, Key lo, Key hi){
		if (x == null){
			return;
		}
		int cmplo = lo.compareTo(x.key);
		int cmphi = hi.compareTo(x.key);
		if (cmplo < 0){
			keys(x.left, queue, lo, hi);
		}
		if (cmplo <=0 && cmphi >= 0){
			queue.add(x.key);
		}
		if (cmphi > 0){
			keys(x.right, queue, lo, hi);
		}
	}
}


点赞