【查找结构 2】二叉查找树 [BST]

当所有的静态查找结构添加和删除一个数据的时候,整个结构都需要重建。这对于常常需要在查找过程中动态改变数据而言,是灾难性的。因此人们就必须去寻找高效的动态查找结构,我们在这讨论一个非常常用的动态查找树——二叉查找树

二叉查找树的特点

下面的图就是两棵二叉查找树,我们可以总结一下他的特点:

(1) 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值

(2) 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值
(3) 它的左、右子树也分别为二叉查找树

我们中序遍历这两棵树发现一个有序的数据序列: 【1 2 3 4 5 6 7 8 】

二叉查找树的操作

插入操作:

现在我们要查找一个数9,如果不存在则,添加进a图。我们看看二叉查找树动态添加的过程:

1). 数9和根节点4比较(9>4),则9放在节点4的右子树中。

2). 接着,9和节点5比较(9>5),则9放在节点5的右子树中。

3). 依次类推:直到9和节点8比较(9>8),则9放在节点8的右子树中,成为节点8的右孩子。

这个过程我们能够发现,动态添加任何一个数据,都会加在原树结构的叶子节点上,而不会重新建树。 由此可见,动态查找结构确实在这方面有巨大的优势。

删除操作:

如果二叉查找树中需要删除的结点左、右子树都存在,则删除的时候需要改变一些子树结构,但所需要付出的代价很小。

具体的插入,删除算法请参加《数据结构算法与应用——搜索树》P5-8。[该章节已经上传到《查找结构专题(6):动态查找树比较 》中]。

二叉查找树的效率分析

那么我们再来看看二叉查找树的效率问题

很显然,在a,b两图的二叉查找树结构中查找一个数据,并不需要遍历全部的节点元素,查找效率确实提高了。但是有一个很严重的问题:我们在a图中查找8需要比较5次数据,而在B图中只需要比较3次。更为严重的是:如果按有序序列[1 2 3 4 5 6 7 8]建立一颗二叉查找树,整棵树就退化成了一个线性结构(如c输入图:单支树),此时查找8需要比较8次数据,和顺序查找没有什么不同。

总结一下:最坏情况下,构成的二叉排序树蜕变为单支树,树的深度为n,其查找时间复杂度与顺序查找一样O(N)。最好的情况是二叉排序树的形态和折半查找的判定树相同,其平均查找长度和log2(N)成正比(O(log2(n)))。

这说明:同样一组数据集合,不同的添加顺序会导致查找树的结构完全不一样,直接影响了查找效率。

那么如何解决这个问题呢? 我们会在下面的专题中:《平衡二叉树》 中来解决。

下面是java实现的二叉查找树(说句老实话,没有指针的java实现数据结构真是复杂):

Java代码 复制代码 收藏代码
  1. packagenet.hr.algorithm.search;
  2. importjava.util.ArrayList;
  3. /**
  4. *二叉树节点结构
  5. *@authorheartraid
  6. */
  7. classBSTNode<EextendsComparable<E>>{
  8. /**结点关键字*/
  9. Ekey=null;
  10. /**直接父亲结点*/
  11. BSTNode<E>parent=null;
  12. /**结点左子树的根节点*/
  13. BSTNode<E>lchild=null;
  14. /**结点右子树的根节点*/
  15. BSTNode<E>rchild=null;
  16. BSTNode(Ek){
  17. this.key=k;
  18. }
  19. }
  20. /**
  21. *二叉查找树BinarySearchTree(BST)
  22. *@authorheartraid
  23. *
  24. */
  25. publicclassBST<EextendsComparable<E>>{
  26. /**树根*/
  27. privateBSTNode<E>root=null;
  28. publicBST(){
  29. }
  30. /**
  31. *BST查询关键字
  32. *@paramkey关键字
  33. *@return查询成功/true,查询失败/false
  34. */
  35. publicbooleansearch(Ekey){
  36. System.out.print("搜索关键字["+key+"]:");
  37. if(key==null||root==null){
  38. System.out.println("搜索失败");
  39. returnfalse;
  40. }
  41. else{
  42. System.out.print("搜索路径[");
  43. if(searchBST(root,key)==null){
  44. returnfalse;
  45. }
  46. elsereturntrue;
  47. }
  48. }
  49. /**
  50. *BST插入关键字
  51. *@paramkey关键字
  52. *@return插入成功/true,插入失败/false
  53. */
  54. publicbooleaninsert(Ekey){
  55. System.out.print("插入关键字["+key+"]:");
  56. if(key==null)returnfalse;
  57. if(root==null){
  58. System.out.println("插入到树根。");
  59. root=newBSTNode<E>(key);
  60. returntrue;
  61. }
  62. else{
  63. System.out.print("搜索路径[");
  64. returninsertBST(root,key);
  65. }
  66. }
  67. publicbooleandelete(Ekey){
  68. System.out.print("删除关键字["+key+"]:");
  69. if(key==null||root==null){
  70. System.out.println("删除失败");
  71. returnfalse;
  72. }
  73. else{
  74. System.out.print("搜索路径[");
  75. //定位到树中待删除的结点
  76. BSTNode<E>nodeDel=searchBST(root,key);
  77. if(nodeDel==null){
  78. returnfalse;
  79. }
  80. else{
  81. //nodeDel的右子树为空,则只需要重接它的左子树
  82. if(nodeDel.rchild==null){
  83. BSTNode<E>parent=nodeDel.parent;
  84. if(parent.lchild.key.compareTo(nodeDel.key)==0)
  85. parent.lchild=nodeDel.lchild;
  86. else
  87. parent.rchild=nodeDel.lchild;
  88. }
  89. //左子树为空,则重接它的右子树
  90. elseif(nodeDel.lchild==null){
  91. BSTNode<E>parent=nodeDel.parent;
  92. if(parent.lchild.key.compareTo(nodeDel.key)==0)
  93. parent.lchild=nodeDel.rchild;
  94. else
  95. parent.rchild=nodeDel.rchild;
  96. }
  97. //左右子树均不空
  98. else{
  99. BSTNode<E>q=nodeDel;
  100. //先找nodeDel的左结点s
  101. BSTNode<E>s=nodeDel.lchild;
  102. //然后再向s的右尽头定位(这个结点将替代nodeDel),其中q一直定位在s的直接父亲结点
  103. while(s.rchild!=null){
  104. q=s;
  105. s=s.rchild;
  106. }
  107. //换掉nodeDel的关键字为s的关键字
  108. nodeDel.key=s.key;
  109. //重新设置s的左子树
  110. if(q!=nodeDel)
  111. q.rchild=s.lchild;
  112. else
  113. q.lchild=s.lchild;
  114. }
  115. returntrue;
  116. }
  117. }
  118. }
  119. /**
  120. *递归查找关键子
  121. *@paramnode树结点
  122. *@paramkey关键字
  123. *@return查找成功,返回该结点,否则返回null。
  124. */
  125. privateBSTNode<E>searchBST(BSTNode<E>node,Ekey){
  126. if(node==null){
  127. System.out.println("].搜索失败");
  128. returnnull;
  129. }
  130. System.out.print(node.key+"—>");
  131. //搜索到关键字
  132. if(node.key.compareTo(key)==0){
  133. System.out.println("].搜索成功");
  134. returnnode;
  135. }
  136. //在左子树搜索
  137. elseif(node.key.compareTo(key)>0){
  138. returnsearchBST(node.lchild,key);
  139. }
  140. //在右子树搜索
  141. else{
  142. returnsearchBST(node.rchild,key);
  143. }
  144. }
  145. /**
  146. *递归插入关键字
  147. *@paramnode树结点
  148. *@paramkey树关键字
  149. *@returntrue/插入成功,false/插入失败
  150. */
  151. privatebooleaninsertBST(BSTNode<E>node,Ekey){
  152. System.out.print(node.key+"—>");
  153. //在原树中找到相同的关键字,无需插入。
  154. if(node.key.compareTo(key)==0)
  155. {
  156. System.out.println("].搜索有相同关键字,插入失败");
  157. returnfalse;
  158. }
  159. else{
  160. //搜索node的左子树
  161. if(node.key.compareTo(key)>0){
  162. //如果当前node的左子树为空,则将新结点keynode插入到左孩子处
  163. if(node.lchild==null){
  164. System.out.println("].插入到"+node.key+"的左孩子");
  165. BSTNode<E>newNode=newBSTNode<E>(key);
  166. node.lchild=newNode;
  167. newNode.parent=node;
  168. returntrue;
  169. }
  170. //如果当前node的左子树存在,则继续递归左子树
  171. elsereturninsertBST(node.lchild,key);
  172. }
  173. //搜索node的右子树
  174. else{
  175. if(node.rchild==null){
  176. System.out.println("].插入到"+node.key+"的右孩子");
  177. BSTNode<E>newNode=newBSTNode<E>(key);
  178. node.rchild=newNode;
  179. newNode.parent=node;
  180. returntrue;
  181. }
  182. elsereturninsertBST(node.rchild,key);
  183. }
  184. }
  185. }
  186. /**
  187. *得到BST根节点
  188. *@returnBST根节点f
  189. */
  190. publicBSTNode<E>getRoot(){
  191. returnthis.root;
  192. }
  193. /**
  194. *非递归中序遍历BST
  195. */
  196. publicvoidInOrderTraverse(){
  197. if(root==null)
  198. return;
  199. BSTNode<E>node=root;
  200. ArrayList<BSTNode<E>>stack=newArrayList<BSTNode<E>>();
  201. stack.add(node);
  202. while(!stack.isEmpty()){
  203. while(node.lchild!=null){
  204. node=node.lchild;
  205. stack.add(node);
  206. }
  207. if(!stack.isEmpty()){
  208. BSTNode<E>topNode=stack.get(stack.size()-1);
  209. System.out.print(topNode.key+"");
  210. stack.remove(stack.size()-1);
  211. if(topNode.rchild!=null){
  212. node=topNode.rchild;
  213. stack.add(node);
  214. }
  215. }
  216. }
  217. }
  218. /**
  219. *测试
  220. */
  221. publicstaticvoidmain(String[]args){
  222. BST<Integer>tree=newBST<Integer>();
  223. tree.insert(newInteger(100));
  224. tree.insert(newInteger(52));
  225. tree.insert(newInteger(166));
  226. tree.insert(newInteger(74));
  227. tree.insert(newInteger(11));
  228. tree.insert(newInteger(13));
  229. tree.insert(newInteger(66));
  230. tree.insert(newInteger(121));
  231. tree.search(newInteger(11));
  232. tree.InOrderTraverse();
  233. tree.delete(newInteger(11));
  234. tree.InOrderTraverse();
  235. }
  236. }
package net.hr.algorithm.search;

import java.util.ArrayList;

/**
 * 二叉树节点结构
 * @author heartraid
 */
class BSTNode<E extends Comparable<E>>{
	/**结点关键字*/
	E key=null;
	/**直接父亲结点*/
	BSTNode<E> parent=null;
	/**结点左子树的根节点*/
	BSTNode<E> lchild=null;
	/**结点右子树的根节点*/
	BSTNode<E> rchild=null;
	
	BSTNode(E k){
		this.key=k;
	}

}
/**
 * 二叉查找树 Binary Search Tree(BST)
 * @author heartraid
 *
 */
public class BST<E extends Comparable<E>> {
	/**树根*/
	private BSTNode<E> root=null;
	
	public BST(){
	}
	
	/**
	 * BST 查询关键字
	 * @param key 关键字
	 * @return 查询成功/true, 查询失败/false
	 */
	public boolean search(E key){
		System.out.print("搜索关键字["+key+"]:");
		if(key==null||root==null){
			System.out.println("搜索失败");
			return false;
		}
		else{
			System.out.print("搜索路径[");
			if(searchBST(root,key)==null){
				return false;
			}
			else return true;
				
		}
	}
	/**
	 * BST插入关键字
	 * @param key 关键字
	 * @return 插入成功/true, 插入失败/false
	 */
	public boolean insert(E key){
		System.out.print("插入关键字["+key+"]:");
		if(key==null) return false;
		if(root==null){
			System.out.println("插入到树根。");
			root=new BSTNode<E>(key);
			return true;
		}
		else{
			System.out.print("搜索路径[");
			return insertBST(root,key);
		}
	}
	
	public boolean delete(E key){
		System.out.print("删除关键字["+key+"]:");
		if(key==null||root==null){
			System.out.println("删除失败");
			return false;
		}
		else{
			System.out.print("搜索路径[");
			
			//定位到树中待删除的结点
			BSTNode<E> nodeDel=searchBST(root,key);
			if(nodeDel==null){
				return false;
			}
			else{
				//nodeDel的右子树为空,则只需要重接它的左子树
				if(nodeDel.rchild==null){
					
					BSTNode<E> parent=nodeDel.parent;
					if(parent.lchild.key.compareTo(nodeDel.key)==0)
						parent.lchild=nodeDel.lchild;
					else
						parent.rchild=nodeDel.lchild;
				}
				//左子树为空,则重接它的右子树
				else if(nodeDel.lchild==null){
					BSTNode<E> parent=nodeDel.parent;
					if(parent.lchild.key.compareTo(nodeDel.key)==0)
						parent.lchild=nodeDel.rchild;
					else
						parent.rchild=nodeDel.rchild;
				}
				//左右子树均不空
				else{
					BSTNode<E> q=nodeDel;
					//先找nodeDel的左结点s
					BSTNode<E> s=nodeDel.lchild;
					//然后再向s的右尽头定位(这个结点将替代nodeDel),其中q一直定位在s的直接父亲结点
					while(s.rchild!=null){ 
						q=s;
						s=s.rchild;
					}
					//换掉nodeDel的关键字为s的关键字
					nodeDel.key=s.key;
					//重新设置s的左子树
					if(q!=nodeDel) 
						q.rchild=s.lchild;
					else
						q.lchild=s.lchild;
				}
				return true;
			}
		}
	}
	
	/**
	 * 递归查找关键子
	 * @param node 树结点
	 * @param key 关键字
	 * @return 查找成功,返回该结点,否则返回null。
	 */
	private BSTNode<E> searchBST(BSTNode<E> node, E key){
		if(node==null){
			System.out.println("].  搜索失败");
			return null;
		}
		System.out.print(node.key+" —>");
		//搜索到关键字
		if(node.key.compareTo(key)==0){
			System.out.println("].  搜索成功");
			return node;
		}
		//在左子树搜索
		else if(node.key.compareTo(key)>0){
				return searchBST(node.lchild,key);
		}
		//在右子树搜索
		else{
			return searchBST(node.rchild,key);
		}
	}
	
	/**
	 * 递归插入关键字
	 * @param node 树结点
	 * @param key 树关键字
	 * @return true/插入成功,false/插入失败
	 */
	private boolean insertBST(BSTNode<E> node, E key){
		System.out.print(node.key+" —>");
		//在原树中找到相同的关键字,无需插入。
		if(node.key.compareTo(key)==0) 
		{
			System.out.println("].  搜索有相同关键字,插入失败");
			return false;
		}
		else{
			//搜索node的左子树
			if(node.key.compareTo(key)>0){
				//如果当前node的左子树为空,则将新结点key node插入到左孩子处
				if(node.lchild==null) {
					System.out.println("].  插入到"+node.key+"的左孩子");
					BSTNode<E> newNode=new BSTNode<E>(key); 
					node.lchild=newNode;
					newNode.parent=node;
					return true;
				}
				//如果当前node的左子树存在,则继续递归左子树
				else return insertBST(node.lchild, key);
			}
			//搜索node的右子树
			else{
				if(node.rchild==null){
					System.out.println("].  插入到"+node.key+"的右孩子");
					BSTNode<E> newNode=new BSTNode<E>(key); 
					node.rchild=newNode;
					newNode.parent=node;
					return true;
				}
				else return insertBST(node.rchild,key);
			}
		}
		
	}
	/**
	 * 得到BST根节点
	 * @return BST根节点f
	 */
    public BSTNode<E> getRoot(){
    	return this.root;
    }
    /**
     * 非递归中序遍历BST
     */
    public void InOrderTraverse(){
    	if(root==null)
    		return;
    	BSTNode<E> node=root;
    	ArrayList<BSTNode<E>> stack=new ArrayList<BSTNode<E>>();   	
    	stack.add(node);
    	while(!stack.isEmpty()){
    		while(node.lchild!=null){
    			node=node.lchild;
    			stack.add(node);
    		}
    		if(!stack.isEmpty()){
    			BSTNode<E> topNode=stack.get(stack.size()-1);
    			System.out.print(topNode.key+" ");
    			stack.remove(stack.size()-1);
    			if(topNode.rchild!=null){
    				node=topNode.rchild;
    				stack.add(node);
    			}
    		}
    	}
    	
    	
    }
    
    /**
     * 测试
     */
	public static void main(String[] args) {
		BST<Integer> tree=new BST<Integer>();
		tree.insert(new Integer(100));
		tree.insert(new Integer(52));
		tree.insert(new Integer(166));
		tree.insert(new Integer(74));
		tree.insert(new Integer(11));
		tree.insert(new Integer(13));
		tree.insert(new Integer(66));
		tree.insert(new Integer(121));
		
                tree.search(new Integer(11));
                tree.InOrderTraverse();
		
                tree.delete(new Integer(11));
		tree.InOrderTraverse();

	}

}
5
0
分享到:
评论
3 楼 liuzdcq 2011-03-30
不好意思,我理解错了!二叉查找树中任一结点x,其左(右)子树中任一结点y(若存在)的关键字必小(大)于x的关键字。
关于parent.rchild=nodeDel.lchild;这点是没有任何问题的!
2 楼 liuzdcq 2011-03-29
直接用 == 比较地址就可以了。
当nodeDel右子树为空时,如果nodeDel是其parent的右子节点,那么 parent.rchild=nodeDel.lchild;不能保证二叉树结构吧,因为nodeDel.lchild.key不一定大于parent.key
1 楼 baiweiyll 2011-03-15
楼主分析的真的是很清晰啊,但是这个二叉查找树的删除好像有问题
Java代码 复制代码 收藏代码
  1. //nodeDel的右子树为空,则只需要重接它的左子树
  2. if(nodeDel.rchild==null){
  3. BSTNode<E>parent=nodeDel.parent;
  4. if(parent.lchild.key.compareTo(nodeDel.key)==0)
  5. parent.lchild=nodeDel.lchild;
  6. else
  7. parent.rchild=nodeDel.lchild;
  8. }
  
                //nodeDel的右子树为空,则只需要重接它的左子树  
                if(nodeDel.rchild==null){  
                      
                    BSTNode<E> parent=nodeDel.parent;  
                    if(parent.lchild.key.compareTo(nodeDel.key)==0)  
                        parent.lchild=nodeDel.lchild;  
                    else  
                        parent.rchild=nodeDel.lchild;  
                }  

满足nodeDel.rchild==null的情况下,parent.lchild可能也为null,所以parent.lchild.key.compareTo(nodeDel.key)==0肯定会报java.lang.NullPointerException的
例如删除下图a的2这个叶子节点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值