java實現-資料結構之二叉樹(三):線索化二叉樹

程式小邱兒發表於2020-10-14

資料結構之二叉樹(三):線索化二叉樹

基本介紹:
線索化二叉樹(Threaded BinaryTree):
有n個節點的二叉連結串列中含有n+1【公式2n-(n-1)=n+1】個空指標域。利用二叉連結串列中的空指標與, 存放指向節點在某種遍歷次序下的前驅和後繼節點的指標(這種附加的指標成為“線索")
這種加上了線索的二叉連結串列稱為線索連結串列,相應的二叉樹成為線索二叉樹。根據線索性質的不同,線索二叉樹可分為前序線索二叉樹,中序線索二叉樹和後序線索二叉樹三種。

一個節點的前一個節點,稱為前驅節點
一個節點的後一個節點,成為後繼節點
官方的解釋太過板正,請看下圖:
在這裡插入圖片描述
通過上圖我們可發現,這是一顆完全二叉樹,但是,葉子節點8,10,14,和6,它們的左右節點不存在(6只有左節點),也就是說,它們的節點上都存在著指標沒有被利用。
所謂線索化二叉樹,便是將這些未被利用起來的指標使用起來,讓各個節點可以指向自己的前後節點.
而為什麼又會分為前序前序線索二叉樹,中序線索二叉樹和後序線索二叉樹三種情況呢?這是因為我們在進行遍歷線索化二叉樹時,由於遍歷的方式不同而導致的線索指標的指向不同。

說到這裡如果還是不太明白,請看如下的圖文解釋:

線索化之前的二叉樹:
在這裡插入圖片描述
它的中序遍歷結果為 [8,3,10,1,14,6];
通過中序遍歷的結果去線索化二叉樹得到如下:
在這裡插入圖片描述
由上圖可知,
8節點中序遍歷時排在首位,所以它的前驅節點並不存在,後繼節點為3,於是它的空閒指標便被利用起來,使它的後繼節點指向了3。
10節點中序遍歷是排在第三位,它的左右節點指標皆為空閒,所以我們可將它的左節點指向它的前驅節點3,右節點指向它的後繼節點1.
同樣,14節點的左右指標同上。

問題分析:
由上面的圖文想必大家都對於線索化二叉樹已經有了個明確的認知。
但是,採用這種方式構建線索化二叉樹在程式碼實現的時候勢必會有一些難點。·
當線索化二叉樹後,Node節點的left和right會有如下的情況:
left指向的是左子樹,也可能是指向的是前驅節點。比如圖中1節點的左子樹指向的是它的左子樹,而10節點指向的則是它的前驅節點。
right同理。
所以,這就為我們程式碼實現提供了一定的難度。

參考程式碼如下:


public class ThreadedBinaryTreeDemo {
	public static void main(String[] args) {
		BinaryTree threadBinaryTree=new BinaryTree();
		HeroNode root=new HeroNode("孫悟空");
		HeroNode node1=new HeroNode("豬八戒");
		HeroNode node2=new HeroNode("唐三藏");
		HeroNode node3=new HeroNode("沙和尚");
		HeroNode node4=new HeroNode("牛魔王");
		HeroNode node5=new HeroNode("紅孩兒");
		HeroNode node6=new HeroNode("白骨精");
		threadBinaryTree.setRoot(root);
		root.setLeftNode(node1);
		root.setRightNode(node2);
		node1.setLeftNode(node3);
		node1.setRightNode(node4);
		node2.setLeftNode(node5);
		node2.setRightNode(node6);
		
		threadBinaryTree.threadNodes(); 
		threadBinaryTree.threadshow();
	}
}

class BinaryTree{
	HeroNode root;
	//指向前驅節點的指標
	//在遞迴進行線索化時,pre總是保留前一個節點
	HeroNode pre=null;
	public void setRoot(HeroNode root) {
		this.root=root;
	}
	//過載threadNodes方法,使node首先為root
	public void  threadNodes() {
		threadNodes(root);
	}
	//線索化二叉樹的方法(中序遍歷方式)
	public void threadNodes(HeroNode node) {
		//先判斷node是否為空,為空則不再線索化
		if(node==null) {
			return;
		}
		//先線索化左子樹
		threadNodes(node.getLeftNode());
		//線索化當前節點
		//先處理前驅節點
		if(node.leftNode==null) {
			//讓當前節點額左指標指向前驅節點
			node.setLeftNode(pre);
			//設定當前節點的type為1
			node.setLeftType(1);
		}
		//再處理後繼節點
		if(pre!=null&&pre.getRightNode()==null) {
			//讓前驅節點的右指標指向當前節點
			pre.setRightNode(node);
			//修改前驅節點的type為1
			pre.setRightType(1);
		}
		//每處理一個節點後,讓當前節點時下一個節點的前驅節點
		pre=node;
		
		
		//再線索化右子樹
		threadNodes(node.getRightNode());
	}
	
	//迴圈遍歷線索化二叉樹(中序遍歷順序)
	public void threadshow() {
		//定義一個變數,儲存當前遍歷的節點,從root開始
		 HeroNode node=root;
		 while(node!=null) {
			 while(node.getLeftType()==0) {
				 node=node.getLeftNode();
			 }
			 
			 System.out.println(node.value);
			 
			 while(node.getRightType()==1) {
				 node=node.getRightNode();
				 System.out.println(node.value);
			 }
			 node=node.getRightNode();
		 }
	}
	
	
	public void frontShow() {
		if(this.root!=null) {
			this.root.frontShow();
		}else {
			System.out.println("二叉樹為空,無法遍歷");
		}
	}
	public void middleShow() {
		if(this.root!=null) {
			this.root.middleShow();
		}else {
			System.out.println("二叉樹為空,無法遍歷");
		}
	}
	public void endShow() {
		if(this.root!=null) {
			this.root.endShow();
		}else {
			System.out.println("二叉樹為空,無法遍歷");
		}
	}
	public void deleteNode(String value) {
		if(this.root!=null) {
			if(this.root.value.equals(value)) {
				this.root=null;
			}else {
				root.deleteNode(value);
			}
		}else { 
			System.out.println("空樹,無法刪除");
		}
	}
}

class HeroNode{
	HeroNode leftNode;
	HeroNode rightNode;
	String value;
	
	//兩個引數解決左右子樹和前後節點的的混亂問題
	//如果leftType或rightType==0,表示指向的是左右子樹,如果等於1,表示指向的是前後節點
	private int leftType;
	private int rightType;
	
	
	public HeroNode(String value) {
		this.value=value;
	}
	
	
	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 getLeftNode() {
		return leftNode;
	}
	public void setLeftNode(HeroNode leftNode) {
		this.leftNode=leftNode;
	}
	
	public HeroNode getRightNode() {
		return rightNode;
	}
	
	public void setRightNode(HeroNode rightNode) {
		this.rightNode=rightNode;
	}
	
	@Override
	public String toString() {
		return "HeroNode [leftNode=" + leftNode + ", rightNode=" + rightNode + ", value=" + value + "]";
	}
	
	/**
	 * 三種遍歷方法
	 */
	//前序遍歷:
	public void frontShow() {
			System.out.println(this.value);
		if(this.getLeftNode()!=null) {
			this.getLeftNode().frontShow();
		}
		if(this.getRightNode()!=null) {
			this.getRightNode().frontShow();
		}
	}
	//中序遍歷
	public void middleShow() {
		if(this.getLeftNode()!=null) {
			this.getLeftNode().middleShow();
		}
		System.out.println(this.value);
		
		if(this.getRightNode()!=null) {
			this.getRightNode().middleShow();
		}
	}
	//後序遍歷
	public void endShow() {
		if(this.getLeftNode()!=null) {
			this.getLeftNode().endShow();
		}
		if(this.getRightNode()!=null) {
			this.getRightNode().endShow();
		}
		System.out.println(this.value);
	}
	
	public void deleteNode(String value) {
		if(this.leftNode!=null&&this.leftNode.value.equals(value)) {
			this.leftNode=null;
			return;
		}
		if(this.rightNode!=null&&this.rightNode.value.equals(value)) {
			this.rightNode=null;
			return;
		}
		if(this.leftNode!=null) {
			this.leftNode.deleteNode(value);
		}
		if(this.rightNode!=null) {
			this.rightNode.deleteNode(value);
		}
	}
}

輸出結果:
在這裡插入圖片描述

相關文章