Java數據結構與算法 day07 樹結構基礎

第九章 樹結構基礎

本章源碼:https://github.com/name365/Java-Data-structure

二叉樹

數組 鏈表 樹存儲方式分析

爲什麼需要樹這種數據結構?

  • 數組存儲方式的分析

    • 優點:通過下標方式訪問元素,速度快。對於有序數組,還可使用二分查找提高檢索速度。
    • 缺點:如果要檢索具體某個值,或者插入值(按一定順序)會整體移動,效率較低 。

在這裏插入圖片描述

  • 鏈式存儲方式的分析

    • 優點:在一定程度上對數組存儲方式有優化(比如:插入一個數值節點,只需要將插入節點,鏈接到鏈表中即可, 刪除效率也很好)。
    • 缺點:在進行檢索時,效率仍然較低,比如(檢索某個值,需要從頭節點開始遍歷) 。

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

  • 樹存儲方式的分析

    • 能提高數據存儲,讀取的效率, 比如利用 二叉排序樹(Binary Sort Tree),既可以保證數據的檢索速度,同時也可以保證數據的插入,刪除,修改的速度。
    • 案例: [7, 3, 10, 1, 5, 9, 12]

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

二叉樹的概念和常用術語

樹示意圖

在這裏插入圖片描述

樹的常用術語(結合示意圖理解):
節點
根節點
父節點
子節點
葉子節點 (沒有子節點的節點)
節點的權(節點值)
路徑(從root節點找到該節點的路線)
層
子樹
樹的高度(最大層數)
森林 :多顆子樹構成森林

二叉樹的概念

1. 樹有很多種,每個節點最多隻能有兩個子節點的一種形式稱爲二叉樹。

2. 二叉樹的子節點分爲左節點和右節點。

在這裏插入圖片描述

3. 如果該二叉樹的所有葉子節點都在最後一層,並且結點總數= 2^n -1 , n 爲層數,則我們稱爲滿二叉樹。
4. 如果該二叉樹的所有葉子節點都在最後一層或者倒數第二層,而且最後一層的葉子節點在左邊連續,倒數第二層的葉子節點在右邊連續,我們稱爲完全二叉樹。

在這裏插入圖片描述

前序中序後序遍歷的二叉樹圖解與實現

使用前序,中序和後序對下面的二叉樹進行遍歷.

前序遍歷: 先輸出父節點,再遍歷左子樹和右子樹
中序遍歷: 先遍歷左子樹,再輸出父節點,再遍歷右子樹
後序遍歷: 先遍歷左子樹,再遍歷右子樹,最後輸出父節點
小結: 看輸出父節點的順序,就確定是前序,中序還是後序

在這裏插入圖片描述

分析二叉樹的前序,中序,後序的遍歷步驟:

1. 創建一顆二叉樹
2. 前序遍歷
    2.1 先輸出當前節點(初始的時候是root節點)
    2.2 如果左子節點不爲空,則遞歸繼續前序遍歷
    2.3 如果右子節點不爲空,則遞歸繼續前序遍歷
3. 中序遍歷
    3.1 如果當前節點的左子節點不爲空,則遞歸中序遍歷,
    3.2 輸出當前節點
    3.3 如果當前節點的右子節點不爲空,則遞歸中序遍歷
4.後序遍歷
    4.1 如果當前節點的左子節點不爲空,則遞歸後序遍歷,
    4.2 如果當前節點的右子節點不爲空,則遞歸後序遍歷
    4.3 輸出當前節點        

代碼實現如下:

public class BinaryTreeTest {

	public static void main(String[] args) {
		//先創建一棵二叉樹
		BinaryTree binaryTree = new BinaryTree();
		//創建需要的節點
		HeroNode root = new HeroNode(1,"宋江");
		HeroNode hero2 = new HeroNode(2,"吳用");
		HeroNode hero3 = new HeroNode(3,"盧俊義");
		HeroNode hero4 = new HeroNode(4,"關勝");
		
		//說明,先手動創建二叉樹,再遞歸方式創建二叉樹
		root.setLeft(hero2);
		root.setRight(hero3);
		hero3.setLeft(hero4);
		binaryTree.setRoot(root);
		
		//測試
		System.out.println("前序遍歷:");	//1,2,3,4
		binaryTree.perOrder();
		
		//測試2
		System.out.println("中序遍歷:");	//2,1,4,3
		binaryTree.infixOrder();
		
		//測試3
		System.out.println("後序遍歷:");	//2,4,3,1
		binaryTree.postOrder();
	}

}

// 創建一個HeroNode結點
class HeroNode {
	private int id;
	private String name;
	private HeroNode left; // 默認爲null,左子節點
	private HeroNode right; // 默認爲null,右子節點

	public HeroNode() {
		super();
	}

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

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	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 [id=" + id + ", name=" + name + "]";
	}

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

	// 編寫中序遍歷的方法
	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);
	}
}

//定義一個二叉樹
class BinaryTree{
	private HeroNode root;

	public void setRoot(HeroNode root) {
		this.root = root;
	}
	
	//前序遍歷
	public void perOrder() {
		if(this.root != null){
			this.root.perOrder();
		}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("二叉樹爲空,無法遍歷");
		}
	}
}

1)前上圖的 3號節點 “盧俊義” , 增加一個左子節點 [5, 林沖]

2)使用前序,中序,後序遍歷,請寫出各自輸出的順序是什麼?

在這裏插入圖片描述

代碼實現如下:

public class BinaryTreeTest {

	public static void main(String[] args) {
		//先創建一棵二叉樹
		BinaryTree binaryTree = new BinaryTree();
		//創建需要的節點
		HeroNode root = new HeroNode(1,"宋江");
		HeroNode hero2 = new HeroNode(2,"吳用");
		HeroNode hero3 = new HeroNode(3,"盧俊義");
		HeroNode hero4 = new HeroNode(4,"關勝");
		HeroNode hero5 = new HeroNode(5,"林沖");
		
		//說明,先手動創建二叉樹,再遞歸方式創建二叉樹
		root.setLeft(hero2);
		root.setRight(hero3);
		hero3.setLeft(hero5);
		hero3.setRight(hero4);		
		binaryTree.setRoot(root);
		
		//測試
		System.out.println("前序遍歷:");	//1,2,3,5,4
		binaryTree.perOrder();
		
		//測試2
		System.out.println("中序遍歷:");	//2,1,5,3,4
		binaryTree.infixOrder();
		
		//測試3
		System.out.println("後序遍歷:");	//2,5,4,3,1
		binaryTree.postOrder();
	}
}

// 創建一個HeroNode結點
class HeroNode {
	private int id;
	private String name;
	private HeroNode left; // 默認爲null,左子節點
	private HeroNode right; // 默認爲null,右子節點

	public HeroNode() {
		super();
	}

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

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	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 [id=" + id + ", name=" + name + "]";
	}

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

	// 編寫中序遍歷的方法
	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);
	}
}

//定義一個二叉樹
class BinaryTree{
	private HeroNode root;

	public void setRoot(HeroNode root) {
		this.root = root;
	}
	
	//前序遍歷
	public void perOrder() {
		if(this.root != null){
			this.root.perOrder();
		}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("二叉樹爲空,無法遍歷");
		}
	}
}

前序中序後序查找思路圖解與實現

二叉樹-查找指定節點

要求

  • 請編寫前序查找,中序查找和後序查找的方法。
  • 並分別使用三種查找方式,查找 heroNo = 5 的節點
  • 並分析各種查找方式,分別比較了多少次
  • 思路分析圖解如下:

在這裏插入圖片描述

public class BinaryTreeTest {

	public static void main(String[] args) {
		//先創建一棵二叉樹
		BinaryTree binaryTree = new BinaryTree();
		//創建需要的節點
		HeroNode root = new HeroNode(1,"宋江");
		HeroNode hero2 = new HeroNode(2,"吳用");
		HeroNode hero3 = new HeroNode(3,"盧俊義");
		HeroNode hero4 = new HeroNode(4,"關勝");
		HeroNode hero5 = new HeroNode(5,"林沖");
		
		//說明,先手動創建二叉樹,再遞歸方式創建二叉樹
		root.setLeft(hero2);
		root.setRight(hero3);
		hero3.setLeft(hero5);
		hero3.setRight(hero4);		
		binaryTree.setRoot(root);
		
		//前序遍歷查找
		//前序遍歷查找的次數 :4 
		System.out.println("前序遍歷查找方式:");
		HeroNode node = binaryTree.preOrderSearch(5);
		if(node != null){
			System.out.printf("找到了,信息爲 id = %d name = %s\n",node.getId(),node.getName());
		} else  {
			System.out.printf("沒有找到,相關信息的人物。\n");
		}
		
		//中序遍歷查找
		//中序遍歷查找次數 :3
		System.out.println("中序遍歷查找方式:");
		HeroNode node2 = binaryTree.infoxSearch(5);
		if(node2 != null){
			System.out.printf("找到了,信息爲 id = %d name = %s\n",node2.getId(),node2.getName());
		} else  {
			System.out.printf("沒有找到,相關信息的人物。\n");
		}
		
		//後序遍歷查找
		//後序遍歷查找次數 :2
		System.out.println("中序遍歷查找方式:");
		HeroNode node3 = binaryTree.postSearch(5);
		if(node3 != null){
			System.out.printf("找到了,信息爲 id = %d name = %s\n",node3.getId(),node3.getName());
		} else  {
			System.out.printf("沒有找到,相關信息的人物。\n");
		}
		
	}
}

// 創建一個HeroNode結點
class HeroNode {
	private int id;
	private String name;
	private HeroNode left; // 默認爲null,左子節點
	private HeroNode right; // 默認爲null,右子節點

	public HeroNode() {
		super();
	}

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

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	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 [id=" + id + ", name=" + name + "]";
	}
	
	//前序遍歷查找
	/**
	  * 
	  * @Description 
	  * @param id 查找的id
	  * @return 如果找到就返回該Node,如果沒有找到返回 null
	 */
	public HeroNode preOrdersearch(int id){
//		System.out.println("進入前序查找:");	//用於測試查找次數
		//比較當前節點
		if(this.id == id){
			return this;
		}
		//1.判斷當前結點的左子節點是否爲空,如果不爲空,則遞歸前序查找
		//2.如果左送歸前序查找,找到結點,則返回
		HeroNode res = null;
		if(this.left != null){
			res = this.left.preOrdersearch(id);
		}
		if(res != null){	//找到了左子樹
			return res;
		}
		//3.否則繼續判斷,當前的結點的右子節點是否爲空
		//4.如果不空,則維續向右遞歸前序查找。
		if(this.right != null){
			res = this.right.preOrdersearch(id);
		}
		return res;
	}
	
	//中序遍歷查找
	public HeroNode infoxSearch(int id){
		//1.判斷當前結點的左子節點是否爲空,如果不爲空,則遞歸中序查找
		HeroNode res = null;
		if(this.left != null){
			res = this.left.infoxSearch(id);
		}
		if(res != null){	//找到了左子樹
			return res;
		}
//		System.out.println("進入中序查找:"); 	//用於測試查找次數
		//2.如果找到,則返回,如果沒有找到,就和當前結點比較,
		//如果是則返回當前結點,否則繼續進行右遞歸的中序查找
		if(this.id == id){
			return this;
		}
		//3.如果右遞歸中序查找,找到就返回,否則返回null
		if(this.right != null){
			res = this.right.infoxSearch(id);
		}
		return res;
	}
	
	//後序遍歷查找
	public HeroNode postSearch(int id){
		//1.判斷當前節點的左子節點是否爲空,如果不爲空,則遞歸後序查找
		HeroNode res = null;
		if(this.left != null){
			res = this.left.postSearch(id);
		}
		if(res != null){	//找到了左子樹
			return res;
		}
		//2.如果左子樹沒有找到,則向右遞歸進行後序查找,如果找到,就返回
		if(this.right != null){
			res = this.right.postSearch(id);
		}
		if(res != null){	//找到了右子樹
			return res;
		}
//		System.out.println("進入後序查找:"); 	//用於測試查找次數
		//3.如果左右子樹都沒有找到,就比較當前節點是不是
		if(this.id == id){
			return this;
		}
		return res;
	}
}

//定義一個二叉樹
class BinaryTree{
	private HeroNode root;

	public void setRoot(HeroNode root) {
		this.root = root;
	}
	
	//前序遍歷
	public HeroNode preOrderSearch(int id){
		if(root != null){
			return root.preOrdersearch(id);
		}else{
			return null;
		}
	}
	
	//中序遍歷
	public HeroNode infoxSearch(int id){
		if(root != null){
			return root.infoxSearch(id);
		}else{
			return null;
		}
	}
	
	//後序遍歷
	public HeroNode postSearch(int id){
		if(root != null){
			return root.postSearch(id);
		}else{
			return null;
		}
	}
}

二叉樹刪除結點思路圖解與實現

要求:
1.如果刪除的節點是葉子節點,則刪除該節點
2.如果刪除的節點是非葉子節點,則刪除該子樹.
3.測試,刪除掉 5號葉子節點 和 3號子樹.
4.相關思路分析如下:    

在這裏插入圖片描述

代碼實現:

public class BinaryTreeTest2 {

	public static void main(String[] args) {
		//先創建一棵二叉樹
		BinaryTree2 binaryTree = new BinaryTree2();
		//創建需要的節點
		HeroNode2 root = new HeroNode2(1,"宋江");
		HeroNode2 hero2 = new HeroNode2(2,"吳用");
		HeroNode2 hero3 = new HeroNode2(3,"盧俊義");
		HeroNode2 hero4 = new HeroNode2(4,"關勝");
		HeroNode2 hero5 = new HeroNode2(5,"林沖");
		
		//說明,先手動創建二叉樹,再遞歸方式創建二叉樹
		root.setLeft(hero2);
		root.setRight(hero3);
		hero3.setLeft(hero5);
		hero3.setRight(hero4);		
		binaryTree.setRoot(root);
		
		//測試刪除
		System.out.println("刪除前,前序遍歷:");
		binaryTree.perOrder(); //  1,2,3,5,4
		binaryTree.delNode(5);
//		binaryTree.delNode(3);
		System.out.println("刪除後,前序遍歷:");
		binaryTree.perOrder(); // 1,2,3,4
		
	}
}

// 創建一個HeroNode結點
class HeroNode2 {
	private int id;
	private String name;
	private HeroNode2 left; // 默認爲null,左子節點
	private HeroNode2 right; // 默認爲null,右子節點

	public HeroNode2() {
		super();
	}

	public HeroNode2(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

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

	public HeroNode2 getLeft() {
		return left;
	}

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

	public HeroNode2 getRight() {
		return right;
	}

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

	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + "]";
	}
	
	//前序遍歷
	public void perOrder() {
		System.out.println(this); // 輸出父結點
		// 遞歸向左子樹前序遍歷
		if (this.left != null) {
			this.left.perOrder();
		}
		// 遞歸向右子樹前序遍歷
		if (this.right != null) {
			this.right.perOrder();
		}
	}

	// 編寫中序遍歷的方法
	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);
	}
	
	//遞歸刪除結點
	//1.如果刪除的節點是葉子節點,則刪除該節點
	//2.如果刪除的節點是非葉子節點,則刪除該子樹
	public void delNode(int id){
		/**
		 * 思路:
		 * 1.因爲我們的二叉樹是單向的,所以我們是判斷當前結點的子結點是否需要刪除結點,而不能去判斷當前這個結點是不是需要刪除結點.
		 * 2.如果當前結點的左子結點不爲空,並且左子結點就是要刪除結點,就將this.left = null;並且就返回(結束遞歸刪除)
		 * 3.如果當前結點的右子結點不爲空,並且右子結點就是要刪除結點,就將this.right = null;並且就返回(結束遞歸刪除)
		 * 4.如果第2和第3步沒有刪除結點,那麼我們就需要向左子樹進行遞歸刪除
		 * 5.如果第4步也沒有刪除結點,則應當向右子樹進行遞歸刪除.
		 * 
		 */
		// 2.如果當前結點的左子結點不爲空,並且左子結點就是要刪除結點,就將this.left = null;並且就返回(結束遞歸刪除)
		if(this.left != null && this.left.id == id){
			this.left = null;
			return;
		}
		//3.如果當前結點的右子結點不爲空,並且右子結點就是要刪除結點,就將this.right = null;並且就返回(結束遞歸刪除)
		if(this.right != null && this.right.id == id){
			this.right = null;
			return;
		}
		//4.如果第2和第3步沒有刪除結點,那麼我們就需要向左子樹進行遞歸刪除
		if(this.left != null){
			this.left.delNode(id);
		}
		//5.如果第4步也沒有刪除結點,則應當向右子樹進行遞歸刪除.
		if(this.right != null){
			this.right.delNode(id);
		}
	}
}

//定義一個二叉樹
class BinaryTree2{
	private HeroNode2 root;

	public void setRoot(HeroNode2 root) {
		this.root = root;
	}
	
	//前序遍歷
	public void perOrder() {
		if(this.root != null){
			this.root.perOrder();
		}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 void delNode(int id){
		if(root != null){
			//如果只有一個root結點,則等價將二叉樹置空
			if(root.getId() == id){
				root = null;
			}else{
				//遞歸刪除
				root.delNode(id);
			}
		}else{
			System.out.println("空樹,不能刪除");
		}
	}
}

思考題(課後練習)
1.如果要刪除的節點是非葉子節點,現在我們不希望將該非葉子節點爲根節點的子樹刪除,需要指定規則, 假如規定如下:
2.如果該非葉子節點A只有一個子節點B,則子節點B替代節點A
3.如果該非葉子節點A有左子節點B和右子節點C,則讓左子節點B替代節點A。

————>具體參考思路及代碼:第11章 二叉排序樹的刪除!!!!

順序存儲二叉樹

基本說明:

在這裏插入圖片描述

從數據存儲來看,數組存儲方式和樹的存儲方式可以相互轉換,即數組可以轉換成樹,樹也可以轉換成數組,
參考上面的示意圖。

要求:
1.上圖的二叉樹的結點,要求以數組的方式來存放 arr : [1, 2, 3, 4, 5, 6, 6]
2.要求在遍歷數組 arr時,仍然可以用前序遍歷,中序遍歷和後序遍歷的方式完成結點的遍歷

順序存儲二叉樹的特點:

在這裏插入圖片描述

1.順序二叉樹通常只考慮完全二叉樹
2.第n個元素的左子節點爲  2 * n + 1 
3.第n個元素的右子節點爲  2 * n + 2
4.第n個元素的父節點爲  (n-1) / 2

5.n : 表示二叉樹中的第幾個元素(0開始編號,如圖所示)

需求: 給你一個數組 *{*1,2,3,4,5,6,7},要求以二叉樹前序遍歷的方式進行遍歷。 前序遍歷的結果應當爲 1,2,4,5,3,6,7

  • 代碼實現如下:
public class BTreeTest {

	public static void main(String[] args) {
		int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
		//創建一個 BTree
		BTree bTree = new BTree(arr);
		bTree.preOrder();
	}

}

//編寫一個BTree,實現順序存儲二叉樹遍歷
class BTree{
	private int[] arr;	//存儲數據結點的數組

	public BTree(int[] arr) {
		super();
		this.arr = arr;
	}
	
	//重載方法
	public void preOrder(){
		this.preOrder(0);
	}
	
	//編寫方法,實現順序存儲二叉樹的前序遍歷
	//index:數組的下標
	public void preOrder(int index){
		//如果數組爲空,或arr.length = 0
		if(arr == null || arr.length == 0){
			System.out.println("數組爲空,無法遍歷查找。");
		}
		//輸出當前數組的元素
		System.out.println(arr[index]);
		//向左遞歸遍歷
		if((index * 2 + 1) < arr.length){			
			preOrder(2 * index + 1);
		}
		//向右遞歸遍歷
		if((index * 2 + 2) < arr.length) {
			preOrder(2 * index + 2);
		}
	}
}

課後練習:請完成對數組以二叉樹中序,後序遍歷方式的代碼.

public class BTreeTest2 {

	public static void main(String[] args) {
		int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
		//創建一個 BTree
		BTree2 bTree = new BTree2(arr);
		bTree.infixOrder(0);	//中序
		System.out.println();
		bTree.postOrder(0);	//後序
	}

}

//編寫一個BTree,實現順序存儲二叉樹遍歷
class BTree2{
	private int[] arr;	//存儲數據結點的數組

	public BTree2(int[] arr) {
		super();
		this.arr = arr;
	}
	
	//編寫方法,實現順序存儲二叉樹的中序遍歷
	public void infixOrder(int index){
		//向左遞歸遍歷
		if((index * 2 + 1) < arr.length){			
			infixOrder(2 * index + 1);
		}
		//如果數組爲空,或arr.length = 0
		if(arr == null || arr.length == 0){
			System.out.println("數組爲空,無法遍歷查找。");
		}
		//輸出當前數組的元素
		System.out.print("中序遍歷:" + arr[index] + " ");
		//向右遞歸遍歷
		if((index * 2 + 2) < arr.length) {
			infixOrder(2 * index + 2);
		}
	}
	
	//編寫方法,實現順序存儲二叉樹的後序遍歷
	public void postOrder(int index){
		//向左遞歸遍歷
		if((index * 2 + 1) < arr.length){			
			postOrder(2 * index + 1);
		}
		//向右遞歸遍歷
		if((index * 2 + 2) < arr.length) {
			postOrder(2 * index + 2);
		}
		//如果數組爲空,或arr.length = 0
		if(arr == null || arr.length == 0){
			System.out.println("數組爲空,無法遍歷查找。");
		}
		//輸出當前數組的元素
		System.out.print("後序遍歷:" + arr[index] + " ");
	}
}

順序存儲二叉樹應用實例

  • 八大排序算法中的堆排序,就會使用到順序存儲二叉樹, 關於堆排序,請參考<< 第11章 樹結構實際應用 >> 。

線索化二叉樹

線索化二叉樹基本介紹

先看一個問題

將數列 {1, 3, 6, 8, 10, 14 } 構建成一顆二叉樹. n+1=7

在這裏插入圖片描述

問題分析: 
1.當我們對上面的二叉樹進行中序遍歷時,數列爲 {8, 3, 10, 1, 6, 14 }
2.但是 6, 8, 10, 14 這幾個節點的 左右指針,並沒有完全的利用上.
3.如果我們希望充分的利用 各個節點的左右指針, 讓各個節點可以指向自己的前後節點,怎麼辦?
4.解決方案 ---> 線索二叉樹

線索二叉樹基本介紹

  • n個結點的二叉鏈表中含有n+1 【公式 2n-(n-1)=n+1】 個空指針域。利用二叉鏈表中的空指針域,存放指向結點某種遍歷次序下的前驅和後繼結點的指針(這種附加的指針稱爲"線索"
  • 這種加上了線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹(Threaded BinaryTree)。根據線索性質的不同,線索二叉樹可分爲前序線索二叉樹、中序線索二叉樹後序線索二叉樹三種
  • 一個結點的前一個結點,稱爲前驅結點
  • 一個結點的後一個結點,稱爲後繼結點

線索化二叉樹思路圖解及實現

應用案例說明:將下面的二叉樹,進行中序線索二叉樹。中序遍歷的數列爲 {8, 3, 10, 1, 14, 6}

在這裏插入圖片描述

  • 思路分析: 中序遍歷的結果:{8, 3, 10, 1, 14, 6}

在這裏插入圖片描述

說明: 當線索化二叉樹後,Node節點的 屬性 left 和 right ,有如下情況:
1.left 指向的是左子樹,也可能是指向的前驅節點. 比如 ① 節點 left 指向的左子樹, 而 ⑩ 節點的 left 指向的就是前驅節點.
2.right指向的是右子樹,也可能是指向後繼節點,比如 ① 節點right 指向的是右子樹,而⑩ 節點的right 指向的是後繼節點.
public class ThreadedBinaryTreeTest {

	public static void main(String[] args) {
		//測試中序線索二叉樹的功能
		HeroNode root = new HeroNode(1, "Z1");
		HeroNode node2 = new HeroNode(3, "S5");
		HeroNode node3 = new HeroNode(6, "R6");
		HeroNode node4 = new HeroNode(8, "G4");
		HeroNode node5 = new HeroNode(10, "P8");
		HeroNode node6 = new HeroNode(14, "Q1");
		
		//二叉樹,後面需要要遞歸創建,所以先簡單處理使用手動創建
		root.setLeft(node2);
		root.setRight(node3);
		node2.setLeft(node4);
		node2.setRight(node5);
		node3.setLeft(node6);
		
		//測試中序線索化
		ThreadTree threadTree = new ThreadTree();
		threadTree.setRoot(root);
		threadTree.treadNodes();
		
		//測試:以10號節點測試
		HeroNode left = node5.getLeft();
		HeroNode right = node5.getRight();
		System.out.println("10號節點的前驅是:" + left);
		System.out.println("10號節點的後繼是:" + right);
		
	}
}
//創建一個HeroNode結點
class HeroNode {
	private int id;
	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 HeroNode() {
		super();
	}

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

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	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;
	}

	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;
	}

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

//定義一個ThreadTree二叉樹,實現線索化功能
class ThreadTree{
	private HeroNode root;
	
	//爲了實現線索化,需要創建要給指向當前結點的前驅結點的指針
	//在遞歸進行線索化時,pre 總是保留前一個結點
	private HeroNode pre = null;

	public void setRoot(HeroNode root) {
		this.root = root;
	}
	
	//重載 線索化的方法treadNodes
	public void treadNodes(){
		this.treadNodes(root);
	}

	//編寫對二叉樹進行中序線索化的方法
	//node 即當前需要線索化的結點
	public void treadNodes(HeroNode node){
		//如果node==null,無法線索化
		if(node == null) {
			return;
		}
			
		//1.先線索化左子樹
		treadNodes(node.getLeft());
		//2.線索化當前結點
			
		//處理當前結點的前驅結點
		//例如:8結點的.left = null , 8結點的.leftType = 1
		if(node.getLeft() == null) {
			//讓當前結點的左指針指向前驅結點 
			node.setLeft(pre); 
			//修改當前結點的左指針的類型,指向前驅結點
			node.setLeftType(1);
		}
			
		//處理後繼結點
		if (pre != null && pre.getRight() == null) {
			pre.setRight(node);		//讓前驅結點的右指針指向當前結點
			pre.setRightType(1);	//修改前驅結點的右指針類型
		}
		//重要:每處理一個結點後,讓當前結點是下一個結點的前驅結點
		pre = node;
		
		//3.再線索化右子樹
		treadNodes(node.getRight());
	}	
}

遍歷線索化二叉樹實現

說明:對前面的中序線索化的二叉樹, 進行遍歷

分析:因爲線索化後,各個結點指向有變化,因此原來的遍歷方式不能使用,這時需要使用新的方式遍歷線索化二叉樹,各個節點可以通過線型方式遍歷,因此無需使用遞歸方式,這樣也提高了遍歷的效率。 遍歷的次序應當和中序遍歷保持一致

  • 代碼實現:
public class ThreadedBinaryTreeTest2 {

	public static void main(String[] args) {
		//測試中序線索二叉樹的功能
		HeroNode2 root = new HeroNode2(1, "Z1");
		HeroNode2 node2 = new HeroNode2(3, "S5");
		HeroNode2 node3 = new HeroNode2(6, "R6");
		HeroNode2 node4 = new HeroNode2(8, "G4");
		HeroNode2 node5 = new HeroNode2(10, "P8");
		HeroNode2 node6 = new HeroNode2(14, "Q1");
		
		//二叉樹,後面需要要遞歸創建,所以先簡單處理使用手動創建
		root.setLeft(node2);
		root.setRight(node3);
		node2.setLeft(node4);
		node2.setRight(node5);
		node3.setLeft(node6);
		
		//測試中序線索化
		ThreadTree2 threadTree = new ThreadTree2();
		threadTree.setRoot(root);
		threadTree.treadNodes();
		
		//測試:以10號節點測試
		HeroNode2 left = node5.getLeft();
		HeroNode2 right = node5.getRight();
		System.out.println("10號節點的前驅是:" + left);
		System.out.println("10號節點的後繼是:" + right);
		
		//當線索化二叉樹後,不能再使用初始遍歷方式
//		threadTree.infixOrder();	//會報錯,死循環換溢出
		
		System.out.println("使用線索化的方式遍歷線索化二叉樹:");
		threadTree.threadedList();
	}
}
//創建一個HeroNode2結點
class HeroNode2 {
	private int id;
	private String name;
	private HeroNode2 left; // 默認爲null,左子節點
	private HeroNode2 right; // 默認爲null,右子節點
	
	//說明:
	//1.如果leftType == 0 表示指向的是左子樹, 如果 1 則表示指向前驅結點
	//2.如果rightType == 0 表示指向是右子樹, 如果 1表示指向後繼結點
	private int leftType;
	private int rightType;

	public HeroNode2() {
		super();
	}

	public HeroNode2(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

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

	public HeroNode2 getLeft() {
		return left;
	}

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

	public HeroNode2 getRight() {
		return right;
	}

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

	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;
	}

	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + "]";
	}
	
	//中序遍歷
	public void infixOrder(){
		//遞歸向左子樹中序遍歷
		if(this.left != null){
			this.left.infixOrder();
		}
		//輸出父節點
		System.out.println(this);
		//遞歸向右子樹中序遍歷 
		if(this.right != null){
			this.right.infixOrder();
		}
	}
	
}

//定義一個ThreadTree2二叉樹,實現線索化功能
class ThreadTree2{
	private HeroNode2 root;
	
	//爲了實現線索化,需要創建要給指向當前結點的前驅結點的指針
	//在遞歸進行線索化時,pre 總是保留前一個結點
	private HeroNode2 pre = null;

	public void setRoot(HeroNode2 root) {
		this.root = root;
	}
	
	//遍歷線索化二叉樹的方法
	public void threadedList() {
		//定義一個變量,用於|存儲當前遍歷的結點,從root開始
		HeroNode2 node = root;
		while(node != null){
			//循環的找到leftType == 1的結點,第一個找到就是8結點
			//後面隨着遍歷而變化,因爲當leftType==1時,說明該結點是按照線索化處理後的有效結點
			while(node.getLeftType() == 0){
				node = node.getLeft();
			}
			//打印當前節點
			System.out.println(node);
			//如果當前結點的右指針指向的是後繼結點,則會一直輸出
			while(node.getLeftType() == 1){
				//獲得當前節點的後繼節點
				node = node.getRight();
				System.out.println(node);
			}
			//替換這個遍歷節點
			node = node.getRight();
		}
	}

	//中序遍歷
	public void infixOrder(){
		if(this.root != null){
			this.root.infixOrder();
		}else{
			System.out.println("二叉樹爲空,無法遍歷");
		}
	}

	//重載 線索化的方法treadNodes
	public void treadNodes(){
		this.treadNodes(root);
	}

	//編寫對二叉樹進行中序線索化的方法
	//node 即當前需要線索化的結點
	public void treadNodes(HeroNode2 node){
		//如果node==null,無法線索化
		if(node == null) {
			return;
		}
			
		//1.先線索化左子樹
		treadNodes(node.getLeft());
		//2.線索化當前結點
			
		//處理當前結點的前驅結點
		//例如:8結點的.left = null , 8結點的.leftType = 1
		if(node.getLeft() == null) {
			//讓當前結點的左指針指向前驅結點 
			node.setLeft(pre); 
			//修改當前結點的左指針的類型,指向前驅結點
			node.setLeftType(1);
		}
			
		//處理後繼結點
		if (pre != null && pre.getRight() == null) {
			pre.setRight(node);		//讓前驅結點的右指針指向當前結點
			pre.setRightType(1);	//修改前驅結點的右指針類型
		}
		//重要:每處理一個結點後,讓當前結點是下一個結點的前驅結點
		pre = node;
		
		//3.再線索化右子樹
		treadNodes(node.getRight());
	}	
}

課後作業:

上面敘述了中序線索化二叉樹,請寫出前序線索化二叉樹和後序線索化二叉樹的實現.

  • 前序化遍歷
public class ThreadedBinaryTreeTest {

	public static void main(String[] args) {
		// 測試前、後序線索二叉樹的功能
		HeroNode root = new HeroNode(1, "Z1");
		HeroNode node2 = new HeroNode(3, "S5");
		HeroNode node3 = new HeroNode(6, "R6");
		HeroNode node4 = new HeroNode(8, "G4");
		HeroNode node5 = new HeroNode(10, "P8");
		HeroNode node6 = new HeroNode(14, "Q1");

		// 二叉樹,後面需要要遞歸創建,所以先簡單處理使用手動創建
		root.setLeft(node2);
		root.setRight(node3);
		node2.setLeft(node4);
		node2.setRight(node5);
		node3.setLeft(node6);

		// 測試前序線索化
		ThreadTree threadTree = new ThreadTree();
		threadTree.setRoot(root);
		threadTree.endTreadNodes(root);

		// 測試:以10號節點測試
		HeroNode left = node5.getLeft();
		HeroNode right = node5.getRight();
		System.out.println("10號節點的前驅是:" + left);
		System.out.println("10號節點的後繼是:" + right);

		System.out.println("使用線索化的方式遍歷線索化二叉樹:");
		threadTree.threadedList(); // 1,3,8,10,6,14
	}
}

// 定義一個ThreadTree二叉樹,實現線索化功能
class ThreadTree {
	private HeroNode root;

	// 爲了實現線索化,需要創建要給指向當前結點的前驅結點的指針
	// 在遞歸進行線索化時,pre 總是保留前一個結點
	private HeroNode pre = null;

	public void setRoot(HeroNode root) {
		this.root = root;
	}

	// 前序遍歷線索化二叉樹的方法
	public void threadedList() {
		// 定義一個變量,用於|存儲當前遍歷的結點,從root開始
		HeroNode node = root;
		while (node != null) {
			// 循環的找到leftType == 1的結點,第一個找到就是8結點
			// 後面隨着遍歷而變化,因爲當leftType==1時,說明該結點是按照線索化處理後的有效結點
			while (node.getLeftType() != 1) {
				System.out.println(node);
				node = node.getLeft();
			}
			System.out.println(node);
			// 替換這個遍歷節點
			node = node.getRight();
		}
	}

	// 編寫對二叉樹進行前序線索化的方法
	// node 即當前需要線索化的結點
	public void endTreadNodes(HeroNode node) {

		// 2.再線索化左子樹
		if (node == null) {
			return;
		}
		
		// 1.先線索化當前結點
		// 處理當前結點的前驅結點
		// 例如:8結點的.left = null , 8結點的.leftType = 1
		if (node.getLeft() == null) {
			// 讓當前結點的左指針指向前驅結點
			node.setLeft(pre);
			// 修改當前結點的左指針的類型,指向前驅結點
			node.setLeftType(1);
		}

		// 處理後繼結點
		if (pre != null && pre.getRight() == null) {
			pre.setRight(node); // 讓前驅結點的右指針指向當前結點
			pre.setRightType(1); // 修改前驅結點的右指針類型
		}
		// 重要:每處理一個結點後,讓當前結點是下一個結點的前驅結點
		pre = node;
		
		// 2.再線索化左子樹
		if(node.getLeftType() != 1) {
			endTreadNodes(node.getLeft());
		}
		
		// 3.再線索化右子樹
		if(node.getRightType() != 1) {
			endTreadNodes(node.getRight());
		}	
	}
}

// 創建一個HeroNode結點
class HeroNode {
	private int id;
	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 HeroNode() {
		super();
	}

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

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	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;
	}

	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;
	}

	@Override
	public String toString() {
		return "HeroNode [id=" + id + ", name=" + name + "]";
	}
}
  • 後序化遍歷——(有錯誤!!!待修改)
public class ThreadedBinaryTreeTest2 {

	public static void main(String[] args) {
		// 測試前、後序線索二叉樹的功能
		HeroNode2 root = new HeroNode2(1, "Z1");
		HeroNode2 node2 = new HeroNode2(3, "S5");
		HeroNode2 node3 = new HeroNode2(6, "R6");
		HeroNode2 node4 = new HeroNode2(8, "G4");
		HeroNode2 node5 = new HeroNode2(10, "P8");
		HeroNode2 node6 = new HeroNode2(14, "Q1");

		// 二叉樹,後面需要要遞歸創建,所以先簡單處理使用手動創建
		root.setLeft(node2);
		root.setRight(node3);
		node2.setLeft(node4);
		node2.setRight(node5);
		node3.setLeft(node6);
		
		//添加父節點
		node2.setParent(root);
		node3.setParent(root);
		node4.setParent(node2);
		node5.setParent(node2);
		node6.setParent(node3);

		// 測試後序線索化
		ThreadTree2 threadTree = new ThreadTree2();
		threadTree.setRoot(root);
		threadTree.endTreadNodes(root);

		// 測試:以10號節點測試
		HeroNode2 left = node5.getLeft();
		HeroNode2 right = node5.getRight();
		System.out.println("10號節點的前驅是:" + left);
		System.out.println("10號節點的後繼是:" + right);

		System.out.println("使用線索化的方式遍歷線索化二叉樹:");
		threadTree.threadedList(); //
	}
}

// 定義一個ThreadTree2二叉樹,實現線索化功能
class ThreadTree2 {
	private HeroNode2 root;

	// 爲了實現線索化,需要創建要給指向當前結點的前驅結點的指針
	// 在遞歸進行線索化時,pre 總是保留前一個結點
	private HeroNode2 pre = null;

	public void setRoot(HeroNode2 root) {
		this.root = root;
	}

	// 後序遍歷線索化二叉樹的方法
	public void threadedList() {
		//定義一個變量,用於|存儲當前遍歷的結點,從root開始
		HeroNode2 node = root;
		while(node != null && node.getLeftType() != 1){
				node = node.getLeft();
		}
		HeroNode2 pre = null;
		while(node != null){
			if(node.getRightType()!= 0){
				System.out.println(node);
				pre = node;
				node = node.getRight();
			}else{
				if(node.getRight() == pre){
					System.out.println(node);
					if(node == root){
						return;
					}
					pre = node;
					node = node.getParent();
				}else{
					node = node.getRight();
					while(node.getLeftType() != 1){
						node = node.getLeft();
					}
				}
			}
		}
	}

	// 編寫對二叉樹進行後序線索化的方法
	// node 即當前需要線索化的結點
	public void endTreadNodes(HeroNode2 node) {
		// 如果node==null,無法線索化
		if (node == null) {
			return;
		}
		
		// 1.線索化左子樹
		endTreadNodes(node.getLeft());
		
		// 2.再線索化右子樹
		endTreadNodes(node.getRight());
		
		// 3.線索化當前結點
		if (node.getLeft() == null) {
			// 讓當前結點的左指針指向前驅結點
			node.setLeft(pre);
			// 修改當前結點的左指針的類型,指向前驅結點
			node.setLeftType(1);
		}
		
		// 處理後繼結點
		if (pre != null && pre.getRight() == null) {
			pre.setRight(node); // 讓前驅結點的右指針指向當前結點
			pre.setRightType(1); // 修改前驅結點的右指針類型
		}
		// 重要:每處理一個結點後,讓當前結點是下一個結點的前驅結點
		pre = node;
	}
}

//創建一個HeroNode2結點
class HeroNode2 {
	private int id;
	private String name;
	private HeroNode2 left; // 默認爲null,左子節點
	private HeroNode2 right; // 默認爲null,右子節點
	private HeroNode2 parent;	//父結點指針

	// 說明:
	// 1.如果leftType == 0 表示指向的是左子樹, 如果 1 則表示指向前驅結點
	// 2.如果rightType == 0 表示指向是右子樹, 如果 1表示指向後繼結點
	private int leftType = 0;
	private int rightType = 0;

	public HeroNode2() {
		super();
	}

	public HeroNode2(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

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

	public HeroNode2 getLeft() {
		return left;
	}

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

	public HeroNode2 getRight() {
		return right;
	}

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

	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 HeroNode2 getParent() {
		return parent;
	}

	public void setParent(HeroNode2 parent) {
		this.parent = parent;
	}

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

參考鏈接:二叉樹的線索化與線索二叉樹的遍歷(Java實現)
這節對於初學的我來說,比較不易理解,雖然寫完了,但仍有諸多不解,做此紀錄,便於查閱!!!歡迎指正!!!

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