算法與數據結構學習(37)-樹結構(線索化二叉樹)

先看一個問題

將數列 {1, 3, 6, 8, 10, 14 } 構建成一顆二叉樹. n+1=7
在這裏插入圖片描述
問題分析:
當我們對上面的二叉樹進行中序遍歷時,數列爲 {8, 3, 10, 1, 14,6 }
但是 6, 8, 10, 14 這幾個節點的 左右指針,並沒有完全的利用上.
如果我們希望充分的利用 各個節點的左右指針, 讓各個節點可以指向自己的前後節點,怎麼辦?
解決方案-線索二叉樹

線索二叉樹基本介紹

1.n個結點的二叉鏈表中含有n+1 【公式 2n-(n-1)=n+1】 個空指針域。利用二叉鏈表中的空指針域,存放指向該結點在某種遍歷次序下的前驅和後繼結點的指針(這種附加的指針稱爲"線索")

2.這種加上了線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹(Threaded BinaryTree),根據線索性質的不同,線索二叉樹可分爲前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種

3.一個結點的前一個結點,稱爲前驅結點
4.一個結點的後一個結點,稱爲後繼結點

代碼實現

線索二叉樹應用案例
應用案例說明:將下面的二叉樹,進行中序線索二叉樹。中序遍歷的數列爲 {8, 3, 10, 1, 14, 6}
在這裏插入圖片描述

說明: 當線索化二叉樹後,Node節點的 屬性 left 和 right ,有如下情況:
1.left 指向的是左子樹,也可能是指向的前驅節點. 比如 ① 節點 left 指向的左子樹, 而 ⑩ 節點的 left 指向的就是前驅節點.
在這裏插入圖片描述
2.right指向的是右子樹,也可能是指向後繼節點,比如 ① 節點right 指向的是右子樹,而⑩ 節點的right 指向的是後繼節點.

實現代碼

package treeThreadeBinaryTree;

public class ThreadeBinaryTreeDemo {

	public static void main(String[] args) {
		
		//測試中序線索化二叉樹
		
		HeroNode root = new HeroNode(1, "tom");
		HeroNode node2 = new HeroNode(3, "jack");
		HeroNode node3 = new HeroNode(6, "smith");
		HeroNode node4 = new HeroNode(8, "mary");
		HeroNode node5 = new HeroNode(10, "king");
		HeroNode node6 = new HeroNode(14, "dim");
		
		//二叉樹後面我們要遞歸創建,現在手動創建
		root.setLeft(node2);
		root.setRight(node3);
		node2.setLeft(node4);
		node2.setRight(node5);
		node3.setLeft(node6);
		
		
		//測試線索化
		ThreadeBinarTree threadeBinarTree = new ThreadeBinarTree();
		threadeBinarTree.setRoot(root);
		threadeBinarTree.threadeNodes();
		
		//測試,以10號測試
		HeroNode leftNode1=node5.getLeft();
		HeroNode rightNode1=node5.getRight();
		System.out.println("10號結點的前驅結點爲="+leftNode1);
		System.out.println("10號結點的後繼結點爲="+rightNode1);
		

	}

}

//定義ThreadeBinarTree實現了線索化功能的二叉樹
class ThreadeBinarTree{
	private HeroNode root;
	
	//爲了實現線索化,需要創建一個當前結點的前驅結點的指針
	//在遞歸進行線索化的時候,pre總是保留前一個結點
	private HeroNode pre = null;
	
	public void setRoot(HeroNode root) {
		this.root = root;
		
	}
	
	
	//重載線索化的方法
	public void threadeNodes() {
		this.threadeNodes(root);
	}
	
	//編寫對二叉樹中序線索化的方法
	/**
	 * 
	 * @param node	需要線索化的結點
	 */
	public void threadeNodes(HeroNode node) {
		//如果node == null不能線索化
		if(node == null) {
			return ;
		}
		
		//(一),先線索化左子樹
		threadeNodes(node.getLeft());
		//(二)線索化當前結點
		
		//處理當前結點的前驅結點
		if(node.getLeft() == null) {
			//讓當前結點的左指針指向前驅結點
			node.setLeft(pre);
			//修改當前結點的左指針類型
			node.setLeftType(1);
		}
		
		//處理後繼結點
		if(pre !=null && pre.getRight() == null) {
			//讓前驅結點的右指針指向當前結點
			pre.setRight(node);
			pre.setRightType(1);
		}
		
		//!!!!!!重要:每次處理一個結點後,讓當前結點指向下一個結點的前驅結點
		pre = node;
		//(三)線索化右子樹
		threadeNodes(node.getRight());
	}
	//刪除結點
	public void delNode(int no) {
		if(this.root != null) {
			//如果只有一個root,立即判斷root是不是就要刪除的結點
			if(root.getNo() == no) {
				root = null;
			}else {
				//遞歸刪除
				root.delNode(no);
			}
		}else {
			System.out.println("空樹無法刪除");
		}
	}
	
	//前序遍歷
	public void preOrder() {
		if(this.root != null) {
			this.root.preOrder();
		}else {
			System.out.println("二叉樹爲空,無法遍歷");
		}
	}
	
	//中序遍歷
	public void infixOrder() {
		if(this.root != null) {
			this.root.infixOrder();
		}else {
			System.out.println("二叉樹爲空,無法遍歷");
		}
	}
	
	//後序遍歷
	public void postOrder() {
		if(this.root != null) {
			this.root.postOrder();
		}else {
			System.out.println("二叉樹爲空,無法遍歷");
		}
	}
	
	//調用前序遍歷查找
	public HeroNode preOrdersearch(int no) {
		if(root != null) {
			return root.preOrdersearch(no);
		}else {
			return null;
		}
	}
	
	//調用中序遍歷查找
	public HeroNode infixOrdersearch(int no) {
		if(root != null) {
			return root.infixOrdersearch(no);
		}else {
			return null;
		}
	}
	
	//調用後序遍歷查找
		public HeroNode postOrdersearch(int no) {
			if(root != null) {
				return root.postOrdersearch(no);
			}else {
				return null;
			}
		}
}


//創建HeroNode
//先創建HeroNode結點
class HeroNode {
	private int no;
	private String name;
	private HeroNode left;// 默認null
	private HeroNode right;// 默認null
	
	//定義兩個屬性
	//規定:
	//1.如果leftType==0,表示指向左子樹,如果爲1表示指向前驅結點
	//2.如果rightType==0,表示指向右子樹,如果爲1表示指向後繼結點
	private int leftType;
	private int rightType;
	
	

	public int getLeftType() {
		return leftType;
	}

	public void setLeftType(int leftType) {
		this.leftType = leftType;
	}

	public int getRightType() {
		return rightType;
	}

	public void setRightType(int rightType) {
		this.rightType = rightType;
	}

	public HeroNode(int no, String name) {
		super();
		this.no = no;
		this.name = name;
	}

	public int getNo() {
		return no;
	}

	public void setNo(int no) {
		this.no = no;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public HeroNode getLeft() {
		return left;
	}

	public void setLeft(HeroNode left) {
		this.left = left;
	}

	public HeroNode getRight() {
		return right;
	}

	public void setRight(HeroNode right) {
		this.right = right;
	}

	@Override
	public String toString() {
		return "HeroNode [no=" + no + ", name=" + name + "]";
	}

	// 遞歸刪除結點
	// 規定
	// 1.如果刪除的節點是葉子節點,則刪除該節點
	// 2.如果刪除的節點是非葉子節點,則刪除該子樹
	public void delNode(int no) {
		/**
		 * 思路: 1.因爲我們的二叉樹是單向的,所以我們是判斷當前結點的子節點是否是需要刪除的結點,而不能判斷當前的結點是不是需要刪除的結點
		 * 2.如果當前的結點的做子節點不爲空,並且左子節點就是要刪除的結點,就將this.left = null;並且返回(遞歸刪除結束)
		 * 3.如果當前結點的右子節點不爲空,並且右子節點就是要刪除的結點,就愛那個this.right = null;並且就返回(結束遞歸刪除)
		 * 4.如果第2步和第3步沒有刪除結點,那我們就需要向左子樹遞歸刪除 5.如果第4不也沒有 刪除結點,則應當向右子樹進行遞歸刪除。
		 */
		// 2.如果當前的結點的做子節點不爲空,並且左子節點就是要刪除的結點,就將this.left = null;並且返回(遞歸刪除結束)
		if (this.left != null && this.left.no == no) {
			this.left = null;
			return;
		}
		// 3.如果當前結點的右子節點不爲空,並且右子節點就是要刪除的結點,就愛那個this.right = null;並且就返回(結束遞歸刪除)
		if (this.right != null && this.right.no == no) {
			this.right = null;
			return;
		}
		// 4.如果第2步和第3步沒有刪除結點,那我們就需要向左子樹遞歸刪除
		if (this.left != null) {
			this.left.delNode(no);
		}

		// 5.如果第4不也沒有 刪除結點,則應當向右子樹進行遞歸刪除。
		if (this.right != null) {
			this.right.delNode(no);
		}
	}

	// 編寫前序遍歷方法
	public void preOrder() {
		System.out.println(this);// 先輸出父節點
		// 遞歸向左子樹序遍遍歷
		if (this.left != null) {
			this.left.preOrder();
		}
		// 遞歸向右子樹序遍遍歷
		if (this.right != null) {
			this.right.preOrder();
		}
	}

	// 中序遍歷
	public void infixOrder() {
		// 遞歸左子樹中序遍歷
		if (this.left != null) {
			this.left.infixOrder();
		}
		// 輸出父節點
		System.out.println(this);
		// 遞歸向右子樹中序遍歷
		if (this.right != null) {
			this.right.infixOrder();
		}
	}

	// 後序遍歷
	public void postOrder() {
		// 遞歸左子樹後序遍歷
		if (this.left != null) {
			this.left.postOrder();
		}
		// 遞歸向右子樹後序遍歷
		if (this.right != null) {
			this.right.postOrder();
		}
		// 輸出父節點
		System.out.println(this);
	}

	// 前序遍歷查找
	/**
	 * 
	 * @param no 要查找的no
	 * @return 如果找到就返回node,如果找不到就返回null
	 */
	public HeroNode preOrdersearch(int no) {
		System.out.println("前序遍歷ing");
		// 比較當前結點是不是
		if (this.no == no) {
			return this;
		}

		// 判斷當前結點的左節點是否爲空,如果不爲空,則遞歸前序查找
		// 如果左遞歸前序查找找到該結點,則返回
		HeroNode resNode = null;
		if (this.left != null) {
			resNode = this.left.preOrdersearch(no);
		}
		if (resNode != null) {// 說明左子樹找到
			return resNode;
		}

		// 1.如果左遞歸前序查找找到該結點,則返回
		// 2.如果左遞歸前序查找找到該結點,則返回,
		// 否則繼續判斷當前節點的右子節點是否爲空,如果不爲空,則繼續向右遞歸前序查找
		if (this.right != null) {
			resNode = this.right.preOrdersearch(no);
		}
		return resNode;
	}

	// 中序遍歷查找
	public HeroNode infixOrdersearch(int no) {

		// 則判斷當前結點的左節點是否爲空,如果不爲空,則遞歸中序查找
		HeroNode resNode = null;
		if (this.left != null) {
			resNode = this.left.infixOrdersearch(no);
		}
		if (resNode != null) {
			return resNode;
		}
		// 如果找到就返回,如果沒有找到就和當前結點作比較,如果相等則返回當前結點,
		System.out.println("中序遍歷ing");
		if (this.no == no) {
			return this;
		}

		// 否則繼續右遞歸中序
		if (this.right != null) {
			resNode = this.right.infixOrdersearch(no);
		}

		return resNode;

	}

	// 後序遍歷查找
	public HeroNode postOrdersearch(int no) {

		// .判斷當前結點的左節點是否爲空,如果不爲空,則遞歸後序查找
		HeroNode resNode = null;
		if (this.left != null) {
			resNode = this.left.postOrdersearch(no);
		}
		if (resNode != null) {
			return resNode;
		}

		// 如果左子樹沒有找到,繼續向右子樹遞歸後序查找
		if (this.right != null) {
			resNode = this.right.postOrdersearch(no);
		}
		if (resNode != null) {
			return resNode;
		}

		// 如果左右子樹都沒有找到,比較當前結點是不是
		System.out.println("後序遍歷ing");
		if (this.no == no) {
			return this;
		}
		return resNode;

	}

}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章