Java基础 - 排序二叉树(二叉查找树)

package com.yc.tree;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

/**
 * 
 * @author wb
 * 
 * 排序二叉树是一种特殊的二叉树,通过它可以非常方便地对树中的所有的节点进行排序和检索。
 * 
 * 排序二叉树要么是一棵空树,要么就是满足以下几个性质:
 * (1)若它的左子树不为空,则左子树上所有的节点的值均小于它的根节点的值。
 * (2)若它的右子树不为空,则右子树上所有的节点的值均大于它的根节点的值。
 * (3)它的左、右子树也为排序二叉树。
 * 
 * 		如下图显示了一棵排序二叉树:
 * 						 10
 * 						 ││
 * 				   3 ────┘└─────18
 * 				  ││			││
 * 		   2 ─────┘└─── 4  13 ──┘└────21
 *						│
 *						└───9
 *			 				││
 *						8───┘└─── 9
 *					图11.21 排序二叉树
 *
 *		对于排序二叉树而言,按照中序遍历即可得到有小到大的有序序列,比如上图,中序遍历的:
 *				[2, 3, 4, 8, 9, 9, 10, 13, 18, 21]
 *
 * 	创建排序二叉树的步骤,就是不断地向排序二叉树添加新节点的过程,具体如下:
 * 	①以根节点为当前节点开始搜索。
 * 	②拿新节点的值和当前节点的值比较。
 *  ③如果新节点的值更大,则以当前节点的右子节点作为新的当前节点;如果新节点的值更小,则以当前节点的左子节点作为新的当前节点。
 *  ④重复第二和第三两个步骤,知道搜索到合适的叶子节点。
 *  ⑤将新节点添加为第四步找到的叶子节点的子节点,如果新节点更大,则添加为右子节点;否则,添加为左子节点。
 *  
 *  当程序从排序二叉树中删除一个节点之后,为了让他依然保持为排序二叉树,必须对该排序二叉树进行维护。维护可以分为如下几种情况:
 *  (1)被删除的节点是叶子节点,只需将它从父节点中删除。
 *  (2)被删除的节点p只有左子树或者只有右子树。如果p是它的父节点的左子节点,则将p的左子树或者右子树添加成p节点的父节点的左子节点即可;
 *  如果p是它的父节点的右子节点,则将p的左子树或者右子树添加成p节点的父节点的右子节点即可。简单来说,如果要删除的节点只有一个子节点,
 *  即可用它的子节点来代替要删除的节点即可。
 *  	如下图要删除的节点只有左子树的情况:
 *  			18							 18
 *  			││		  ↓p				 ││
 * 	   12 ──────┘└────── 30			12 ──────┘└────── 23
 * 	    │				  │	--->	 │				   │
 * 		└───14		  23──┘			 └───14			   └──26
 * 			 │		   │				 │
 * 		 13──┘		   └──26		 13──┘
 * 					图11.22 被删除节点只有左子树
 * 
 *  			18							 18
 *     ↓p		││		     				 ││
 * 	   12 ──────┘└────── 30			14 ──────┘└────── 30
 * 	    │				  │	--->	 │				   │
 * 		└───14		  23──┘		 13──┘  		   23──┘
 * 			 │		   │						  	│
 * 		 13──┘		   └──26				    	└──26
 * 					图11.23 被删除节点只有右子树
 * 
 *  (3)若被删除节点p的左、右子树均非空,则有以下两种做法:
 *  	①将pL设为p的父节点q的左或右子节点(取决于p是其父节点q左、右子节点),将pR设为p节点的中序前趋节点s的右子节点
 *  	(s节点是pL最右下的节点,也就是pL子树中最大的节点)。采用这种方法删除节点的示意图如下:
 *  
 *  			  q→ 5									   q→ 5
 *  				││	   ↓p								 ││
 *  	   	3 ──────┘└─────20							3────┘└─────10
 *  					   ││			----->						││
 *  			  	 10 ───┘└─────30							8───┘└─────15
 *  				 ││				将p左子节点设为q的子节点				  		│
 *  		   8 ────┘└─────15		将p右子节点设为pL子树中最大节点的右子节点       			└────30
 *  							图11.24 被删除的节点既有左子树,又有右子树
 * 		②以p节点的中序前趋或后继代替p所指节点,然后从排序二叉树中删除前趋或后继节点。简单来讲,就是用大于
 *  	p节点的最小节点或小于p节点的最大节点代替p节点。采用这种方式删除节点的示意图如下:
 *  	
 *  											  q→ 5
 *  												││		↓p
 *  							------>		   3────┘└─────15
 *														   ││
 *  	q→ 5									  	 10 ───┘└─────30
 *  		││	   ↓p								 │
 *  3 ──────┘└─────20						 	8────┘
 *  			   ││							用被删除节点的前驱来代替被删除节点		
 *  		 10 ───┘└─────30						   
 *  		 ││								 	  q→ 5	
 *     8 ────┘└─────15		 						││	   ↓p	
 *  										3 ──────┘└─────30	
 *  														│
 *  												  10 ───┘
 *     							------>				  ││
 *     											8 ────┘└─────15	
 *     											用被删除节点的后继来代替被删除节点
 *  					图11.25被删除的节点既有左子树,又有右子树 
 *  
 *  掌握了上面的理论,使用如下Java程序来实现排序二叉树。实现排序二叉树的删除时采用图11.25中的用被删除节点左子树中
 *  的最大节点与被删除节点交换的方式维护。
 *  
 */		
public class SortedBinTree <T extends Comparable>{
	public class Node{
		Object data;
		Node parent;
		Node left;
		Node right;
		public Node(Object data, Node parent, Node left, Node right){
			this.data = data;
			this.parent = parent;
			this.left = left;
			this.right = right;
		}
		public String toString(){
			return "[data:" + data + "]";
		}
		@SuppressWarnings("unchecked")
		public boolean equals(Object obj) {
			if (this == obj){
				return true;
			}
			if(getClass() == obj.getClass()){
				Node target = (Node)obj;
				return data.equals(target.data) &&
						parent == target.parent &&
						left == target.left &&
						right == target.right;
			}
			return false;
			
		}
	}
	
	private Node root;
	
	public SortedBinTree(){
		root = null;
	}
	public SortedBinTree(T data){
		root = new Node(data, null, null, null);
	}
	
	/**
	 * 向这棵排序二叉树添加节点
	 * @param data
	 */
	@SuppressWarnings("unchecked")
	public void add(T data){
		//如果根节点为null,那么新添加的节点就作为根节点
		if(root == null){
			root = new Node(data, null, null, null);
		}
		else{
			//从根节点开始搜索
			Node current = root;
			
			Node parent = null;
			int cmp = 0;
			do{
				parent = current;
				cmp = data.compareTo(current.data);
				
				if(cmp > 0){//若干新添加的节点只大于当前节点的值
					current = current.right;
				}else{
					current = current.left;
				}
				
			}while(current != null);
			
			Node newNode = new Node(data, parent, null, null);
			
			if(cmp > 0){
				parent.right = newNode;
			}else{
				parent.left = newNode;
			}
		}
	}
	
	/**
	 * 删除指定元素的节点
	 * @param data
	 */
	public void remove(T data){
		Node target = getNode(data);
		
		if(target == null){//如果为找到
			return;
		}
		
		if(target.left == null && target.right == null){//如果要删除节点的左、右子树为空(叶子节点)
			if(target == root){
				root = null;
			}else{
				if(target == target.parent.left){
					target.parent.left = null;
				}else{
					target.parent.right = null;
				}
				//向上引用也赋空
				target.parent = null;  
			}
		}else if(target.left != null && target.right == null){ //如果要删除的节点只有左子树
			//如果要删除的节点是根节点
			if(root == target){
				root = target.left;
				target.left.parent = null; //不能让它指回去
			}
			else{
				//如果要删除节点是其父节点的左子节点
				if(target == target.parent.left){
					target.parent.left = target.left;
				}else{
					target.parent.right = target.left;
				}
				target.left.parent = target.parent; //指回去
			}
		}else if(target.right != null && target.left == null){ //如果要删除的节点只有右子树
			if(root == target){
				root = target.right;
			}
			else{
				if(target == target.parent.left){
					target.parent.left = target.right;
				}else{
					target.parent.right = target.right;
				}
				target.right.parent = target.parent;
			}
		}else if(target.left != null && target.right != null){ //如果要删除元素的节点既有右子树,也有左子树
			if(root == target){
				Node leftMaxNode = target.left;
				
				while(leftMaxNode.right != null){
					leftMaxNode = leftMaxNode.right;
				}
				root = leftMaxNode;
				
				if(target.left.data.equals(leftMaxNode.data)){
					leftMaxNode.right = target.right;
					leftMaxNode.parent = null;
					target.right = target.parent = target.left = null;
				}else{
					leftMaxNode.parent.right = null;
					leftMaxNode.parent = null;
					
					leftMaxNode.left = target.left;
					target.right = target.parent = target.left = null;
					
					target.left = target.right = null;
				}
				
			}else{
				
				Node leftMaxNode = target.left;
				
				while(leftMaxNode.right != null){
					leftMaxNode = leftMaxNode.right;
				}
				
				if(target.left.data.equals(leftMaxNode.data)){
					leftMaxNode.right = target.right;
					leftMaxNode.parent = target.parent;
					
					if(target == target.parent.left){
						target.parent.left = leftMaxNode;
					}else{
						target.parent.right = leftMaxNode;
					}
					
					target.right = target.parent = target.left = null;
				}else{
					leftMaxNode.parent.right = null;
					
					if(target == target.parent.left){
						target.parent.left = leftMaxNode;
					}else{
						target.parent.right = leftMaxNode;
					}
					
					leftMaxNode.parent = target.parent;
					leftMaxNode.left = target.left;
					leftMaxNode.right = target.right;
					
					target.right = target.parent = target.left = null;
				}
			}
		}
	}
	//在排序二叉树中查找指定元素的节点
	@SuppressWarnings({ "unchecked" })
	private Node getNode(T data){
		Node node = root;
		
		while(node != null){
			int tmp = data.compareTo(node.data);
			
			if(tmp > 0){ //指定元素节点的值大于当前节点的值
				node = node.right;
			}else if( tmp < 0){
				node = node.left;
			}else{
				return node;
			}
		}
		return null;
	}
	
	/**
	 * 广度优先遍历排序二叉树
	 * @return
	 */
	public List<Node> breadthFirstSearch(){
		List<Node> nodes = new ArrayList<Node>();
		Deque<Node> deque = new ArrayDeque<>();
		
		deque.offer(root);
		while( !deque.isEmpty()){
			Node tmp = deque.poll();
			nodes.add(tmp);
			if(tmp.left != null){
				deque.offer(tmp.left);
			}
			if(tmp.right != null){
				deque.offer(tmp.right);
			}
		}
		return nodes;
	}
}


测试代码如下:

   

package com.yc.test;

import com.yc.tree.SortedBinTree;

public class SortedBinTreeTest {
	public static void main(String[] args) {
		SortedBinTree<Integer> tree = new SortedBinTree<Integer>();
		//添加节点
		tree.add(5);
		tree.add(20);
		tree.add(10);
		tree.add(3);
		tree.add(8);
		tree.add(15);
		tree.add(30);
		System.out.println( tree.breadthFirstSearch());
		
		/*tree.remove(5);
		System.out.println( tree.breadthFirstSearch());*/
		
		tree.add(5);
		tree.add(9);
		System.out.println(tree.breadthFirstSearch());
		
		tree.remove(10);
		System.out.println( tree.breadthFirstSearch());
	}

}

测试结果如下:

[[data:5], [data:3], [data:20], [data:10], [data:30], [data:8], [data:15]]
[[data:5], [data:3], [data:20], [data:5], [data:10], [data:30], [data:8], [data:15], [data:9]]
[[data:5], [data:3], [data:20], [data:5], [data:9], [data:30], [data:8], [data:15]]



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值