1. 程式人生 > >二叉樹、平衡二叉樹原理及例項(一)

二叉樹、平衡二叉樹原理及例項(一)

最近閒來無事,研究了一下二叉樹。怪了,非平衡二叉樹,兩三個小時就搞定了生成方法,以及幾個相關的小方法。但是到了平衡二叉樹,愣是把我折磨的兩天,都卡在左旋轉和右旋轉那裡了。不過因禍得福啊,兩天後,正在為了旋轉抓頭撓腮的我,靈光一閃,半個小時就把旋轉那一塊完成了。畢竟折磨了兩天多,不能讓成果浪費,現在分享出來。

樹的概念

樹是一種資料結構,是由多個節點物件按照一定順序組成的資料結構。

如上圖,

1、節點A是根節點

2、節點B是節點A的左子節點

3、節點C是節點A的右子節點

4、節點A是節點B和節點C的父節點

另外,根據上圖這個滿二叉樹,還可以看出二叉樹的規律還有:

1、每個節點最多有2個子節點

2、每個節點最多隻有1個父節點(根節點沒有父節點)。

3、每層的最大節點數為:(2^(L-1))個(L代表第幾層)。

4、一個二叉樹的最大節點數為:(2^T - 1)(T代表最大層數)

樹的作用

要說樹的作用,還是要用上圖的滿二叉樹來舉例。

圖中共有15個節點物件,如果放在集合List(陣列)中,那麼我要隨機查詢一個物件(14節點),需要從list(0)開始,逐個遍歷list中的物件,那麼最大遍歷次數是15;如果按照上圖滿二叉樹存放,那麼最大遍歷次數是4(順序是:根節點1→右子節點2→右子節點7→左子節點14。如何確定順序,在後面的平衡二叉樹中會講解)。

由此可見,樹結構用來查詢,效率很高。而且用來新增、刪除操作,也很簡單,只要找到對應的節點,修改子節點或者父節點即可。

如下圖的刪除邏輯:如果要刪除B節點,只要將A節點的左子節點指向D,將D節點的父節點指向A即可。

下面給出二叉樹的生成邏輯。

MyNode節點物件:

/**
 * 節點物件類
 * @author 劉
 * @date 2018年8月5日
 */
public class MyNode implements Serializable{

	private static final long serialVersionUID = 1L;
	
	public MyNode() {
	}
	
	public MyNode(int value) {
		this.value = value;
	}
	
	private int value;//根據value值對節點進行排序
	private MyNode leftNode;//左子節點
	private MyNode rightNode;//右子節點
	private MyNode parentNode;//父節點
	public int getValue() {
		return value;
	}
	public void setValue(int value) {
		this.value = value;
	}
	public MyNode getLeftNode() {
		return leftNode;
	}
	public void setLeftNode(MyNode leftNode) {
		this.leftNode = leftNode;
	}
	public MyNode getRightNode() {
		return rightNode;
	}
	public void setRightNode(MyNode rightNode) {
		this.rightNode = rightNode;
	}
	public MyNode getParentNode() {
		return parentNode;
	}
	public void setParentNode(MyNode parentNode) {
		this.parentNode = parentNode;
	}

}

BTreeDemo.java

/**
 * 二叉樹demo
 * @author 劉
 * @date 2018年8月5日
 */
public class BTreeDemo {
	
	public int maxDepth = 0;
	public MyNode[] inits = {new MyNode(6),new MyNode(7),new MyNode(4),new MyNode(2),new MyNode(5),new MyNode(1)};

	public static void main(String[] args) {
		BTreeDemo demo = new BTreeDemo();
		MyNode node = demo.sorting2Tree();
		demo.printNode(node);
	}
	
	/**
	 * 獲取一個二叉樹的深度
	* @author 劉
	* @author 2018年8月5日 
	* @parameter 
	* @return
	 */
	public int getTreeDepth(MyNode node){
		int depth = 0;//定義二叉樹的深度
		if(node == null){
			return 0;
		}
		
		List<MyNode> list = new ArrayList<MyNode>();
		List<MyNode> tmpList = new ArrayList<MyNode>();
		list.add(node);
		
		//如果第L層有節點,則depth++,繼續獲取L+1層的所有子節點
		while(true){
			if(list == null || list.size() <= 0){
				break;
			}
			depth++;
			for(int j = 0; j < list.size(); j++){
				if(list.get(j).getLeftNode() != null){
					tmpList.add(list.get(j).getLeftNode());
				}
				if(list.get(j).getRightNode() != null){
					tmpList.add(list.get(j).getRightNode());
				}
			}
			list.clear();
			list.addAll(tmpList);
			tmpList.clear();
		}
		return depth;
	}
	
	/**
	 * 獲取二叉樹某一層有效節點數
	 * @param node 二叉樹
	 * @param level 第幾層
	 * @return List<MyNode>	
	 */
	public List<MyNode> getLevelNodeList(MyNode node, int level){
		List<MyNode> list = new ArrayList<MyNode>();
		if(level <= 0 || node == null){
			return list;
		}
		list.add(node);
		List<MyNode> tmpList = new ArrayList<MyNode>();
		//因為層數是從1開始計算,所以這裡需要將level-1。
		for(int i = 0; i < level - 1; i++){
			for(int j = 0; j < list.size(); j++){
				if(list.get(j).getLeftNode() != null){
					tmpList.add(list.get(j).getLeftNode());
				}
				if(list.get(j).getRightNode() != null){
					tmpList.add(list.get(j).getRightNode());
				}
			}
			list.clear();
			list.addAll(tmpList);
			tmpList.clear();
		}
		return list;
	}
	
	/**
	 * 將目標二叉樹結構以value值的形式打印出來,不存在節點用#代替,列印的結果放到txt檔案,然後改成csv,會看到二叉樹的結構,如下
	 * @param node	目標二叉樹
	 * ,,,,,,,6,,,,,,,
	   ,,,4,,,,,,,,7,,,
	   ,2,,,,5,,,,#,,,,#,
	   1,,3,,#,,#,,#,,#,,#,,#
	 */
	public void printNode(MyNode node){
		StringBuffer sb = new StringBuffer();
		int maxDepth = getTreeDepth(node);
		if(maxDepth > 0){
			for(int i = 0; i < maxDepth; i++){
				
				//i層第一個節點的左邊/最後一個節點的右邊需要幾個逗號分隔符
				int maxJ = (int)Math.pow(2, maxDepth - i - 1) - 1;
				for(int j = 0; j < maxJ; j++){
					sb.append(",");
				}
				
				//i層的最大節點數
				int levelCount = (int)Math.pow(2, i);
				
				//i層相鄰兩個節點中間的間隔數
				int gapCount = (int)(Math.pow(2, maxDepth - i) - 1);
				
				for(int j = 0; j < levelCount; j++){
					sb.append(getNodeValue(node, i + 1, j + 1, maxDepth));
					if(levelCount > 1 && j < levelCount - 1){
						for(int k = 0; k <= gapCount; k++){
							sb.append(",");
						}
					}
				}
				for(int j = 0; j < maxJ; j++){
					sb.append(",");
				}
				sb.append("\n");
			}
			System.out.println(sb.toString());
		}
	}
	
	
	/**
	 * 將一組MyNode排序成二叉樹(非平衡)
	 * @return
	 */
	public MyNode sorting2Tree(){
		MyNode node = inits[0];
		for(int i = 1; i < inits.length; i++){
			interateCreateNode(node,inits[i]);
		}
		return node;
	}
	
	/**
	 * 遞迴方法生成有序二叉樹(非平衡)
	 * @param node	節點
	 * @param newNode	新節點
	 */
	public void interateCreateNode(MyNode node, MyNode newNode){
		
		/**
		 * 如果新節點的value<=原節點的value值:
		 * ①:如果原節點的左子節點不為空,則用原節點的【左子節】點和【newNode】進行比較(遞迴呼叫本方法)
		 * ②:如果原節點的左子節點為空,則將newNode設定為原節點的左子節點
		 */
		if(newNode.getValue() <= node.getValue()){
			if(node.getLeftNode() != null){
				interateCreateNode(node.getLeftNode(), newNode);
			}else{
				newNode.setParentNode(node);
				node.setLeftNode(newNode);
			}
		}else{
			//原理同上
			if(node.getRightNode() != null){
				interateCreateNode(node.getRightNode(), newNode);
			}else{
				newNode.setParentNode(node);
				node.setRightNode(newNode);
			}
		}
	}
	
	/**
	 * 根據層數和序號,獲取二叉樹對應節點的value值
	 * @param node	二叉樹
	 * @param level	層數(根節點level=1)
	 * @param index	序號(從左到右,從1開始)
	 * @return	對應節點的value值,如果不存在該節點,則返回#
	 */
	public String getNodeValue(MyNode node, int level, int index, int maxDepth){
		if(maxDepth == 0){
			return "請先計算深度";
		}
		if(level > maxDepth){
			return "超出最大層數!";
		}
		if(index < 1 || index > Math.pow(2, level - 1)){
			return "超出序號範圍!";
		}
		
		if(node == null){
			return "樹不能為空!";
		}
		
		return this.interateGetNodeValue(node, level, index);
	}
	
	/**
	 * 根據層數和序號,獲取二叉樹對應節點的value值
	 * @param node	二叉樹
	 * @param level	層數(根節點level=1)
	 * @param index	序號(從左到右,從1開始)
	 * @return	對應節點的value值,如果不存在該節點,則返回#
	 */
	private String interateGetNodeValue(MyNode node, int level, int index){
		//如果只有一層,則直接返回node的value值
		if(level == 1){
			return String.valueOf(node == null ? "#" : node.getValue());
		}
		
		//如果是取第二層的節點,則判斷是否為空後,直接返回value值。
		if(level == 2){
			if(index == 1){
				return String.valueOf(node.getLeftNode() == null ? "#" : node.getLeftNode().getValue());
			}else{
				return String.valueOf(node.getRightNode() == null ? "#" : node.getRightNode().getValue());
			}
		}
		
		/**
		 * 當層數>=3時,需要迭代執行本方法,將層數執行到第二層,用level=2返回value值
		 * ①:先判斷目標節點在二叉樹的左邊還是右邊(以根節點位置為豎軸,判斷目標節點在豎軸的左邊還是右邊)
		 * ②:如果在左邊,則將【根節點的左子節點,level-1,newIndex】座位引數,呼叫本方法。
		 */
		//目標層的最大節點數
		int levelCount = (int)(Math.pow(2, level - 1));
		
		//level-1後,新的序號(從左到右,從1開始)
		int newIndex = 0;
		//level-1後,新的根節點
		MyNode newNode = null;
		
		if(index > levelCount / 2){
			newIndex = index - levelCount / 2;
			newNode = node.getRightNode();
		}else{
			newIndex = index;
			newNode = node.getLeftNode();
		}
		if(newNode == null){
			return "#";
		}
		return interateGetNodeValue(newNode, level - 1, newIndex);
	}
}

節點新增的步驟如下圖: