1.順序存儲二叉樹的概念
- 基本說明
從數據存儲來看,數組存儲方式和樹
的存儲方式可以相互轉換,即數組可
以轉換成樹,樹也可以轉換成數組,
看右面的示意圖。
要求:
右圖的二叉樹的結點,要求以數組�的方式來存放 arr : [1, 2, 3, 4, 5, 6, 6]
要求在遍歷數組 arr時,仍然可以以�前序遍歷,中序遍歷和後序遍歷的�方式完成結點的遍歷-
順序存儲二叉樹的特點:
順序二叉樹通常只考慮完全二叉樹
第n個元素的左子節點爲 2 * n + 1
第n個元素的右子節點爲 2 * n + 2
第n個元素的父節點爲 (n-1) / 2 n : 表示二叉樹中的第幾個元素(按0開始編號�如圖所示)
-
代碼實現
package cn.smallmartial.tree; /** * @Author smallmartial * @Date 2019/6/16 * @Email [email protected] */ public class ArrBinaryTreeDemo { public static void main(String[] args) { int[] arr = {1,2,3,4,5,6,7}; //創建 ArrBinaryTree arrBinaryTree = new ArrBinaryTree(arr); System.out.println("前序遍歷:"); arrBinaryTree.preOrder();//1,2,,4,5,3,6,7 } } class ArrBinaryTree{ private int[] arr; public ArrBinaryTree(int[] arr) { this.arr = arr; } public void preOrder(){ this.preOrder(0); } /** * 前序遍歷 * * @param index */ public void preOrder(int index){ if (arr == null || arr.length ==0){ System.out.println("數組爲空,不能按照二叉樹的前序遍歷"); } //輸出改元素 System.out.print(arr[index]+" "); //向左遞歸 if (index * 2 + 1 < arr.length ){ preOrder(2*index +1); } //向右遞歸 if (index * 2 + 2 < arr.length ){ preOrder(2*index +2); } } }
2.線索化二叉樹
2.1線索二叉樹基本介紹
n個結點的二叉鏈表中含有n+1 【公式 2n-(n-1)=n+1】 個空指針域。利用二叉鏈表中的空指針域,存放指向該結點在某種遍歷次序下的前驅和後繼結點的指針(這種附加的指針稱爲"線索")
這種加上了線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹(Threaded BinaryTree)。根據線索性質的不同,線索二叉樹可分爲前序線索二叉樹、中序線索二叉樹和後序線索二叉樹三種
一個結點的前一個結點,稱爲前驅結點
一個結點的後一個結點,稱爲後繼結點
2.2線索二叉樹應用案例
應用案例說明:將下面的二叉樹,進行中序線索二叉樹。中序遍歷的數列爲 {8, 3, 10, 1, 14, 6}
2.3代碼實現
package cn.smallmartial.tree.threadedbinarytree;
/**
* @Author smallmartial
* @Date 2019/6/16
* @Email [email protected]
*/
public class ThreadedBinaryTreeDemo {
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);
//測試中序線索化
ThreadedBinaryTree threadedBinaryTree = new ThreadedBinaryTree();
threadedBinaryTree.setRoot(root);
threadedBinaryTree.threadedNodes();
//測試: 以10號節點測試
HeroNode leftNode = node5.getLeft();
HeroNode rightNode = node5.getRight();
System.out.println("10號結點的前驅結點是 =" + leftNode); //3
System.out.println("10號結點的後繼結點是=" + rightNode); //1
//當線索化二叉樹後,能在使用原來的遍歷方法
//threadedBinaryTree.infixOrder();
System.out.println("使用線索化的方式遍歷 線索化二叉樹");
threadedBinaryTree.threadedList(); // 8, 3, 10, 1, 14, 6
}
}
//定義ThreadedBinaryTree 實現了線索化功能的二叉樹
class ThreadedBinaryTree {
private HeroNode root;
//爲了實現線索化,需要創建要給指向當前結點的前驅結點的指針
//在遞歸進行線索化時,pre 總是保留前一個結點
private HeroNode pre = null;
public void setRoot(HeroNode root) {
this.root = root;
}
//重載一把threadedNodes方法
public void threadedNodes() {
this.threadedNodes(root);
}
//遍歷線索化二叉樹的方法
public void threadedList() {
//定義一個變量,存儲當前遍歷的結點,從root開始
HeroNode node = root;
while(node != null) {
//循環的找到leftType == 1的結點,第一個找到就是8結點
//後面隨着遍歷而變化,因爲當leftType==1時,說明該結點是按照線索化
//處理後的有效結點
while(node.getLeftType() == 0) {
node = node.getLeft();
}
//打印當前這個結點
System.out.println(node);
//如果當前結點的右指針指向的是後繼結點,就一直輸出
while(node.getRightType() == 1) {
//獲取到當前結點的後繼結點
node = node.getRight();
System.out.println(node);
}
//替換這個遍歷的結點
node = node.getRight();
}
}
//編寫對二叉樹進行中序線索化的方法
/**
*
* @param node 就是當前需要線索化的結點
*/
public void threadedNodes(HeroNode node) {
//如果node==null, 不能線索化
if(node == null) {
return;
}
//(一)先線索化左子樹
threadedNodes(node.getLeft());
//(二)線索化當前結點[有難度]
//處理當前結點的前驅結點
//以8結點來理解
//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;
//(三)在線索化右子樹
threadedNodes(node.getRight());
}
//刪除結點
public void delNode(int no) {
if(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 this.root.postOrderSearch(no);
}else {
return null;
}
}
}
//先創建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) {
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.我們就需要向左子樹進行遞歸刪除
if(this.left != null) {
this.left.delNode(no);
}
//5.則應當向右子樹進行遞歸刪除
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("進入前序遍歷");
//比較當前結點是不是
if(this.no == no) {
return this;
}
//1.則判斷當前結點的左子節點是否爲空,如果不爲空,則遞歸前序查找
//2.如果左遞歸前序查找,找到結點,則返回
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("進入中序查找");
//如果找到,則返回,如果沒有找到,就和當前結點比較,如果是則返回當前結點
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("進入後序查找");
//如果左右子樹都沒有找到,就比較當前結點是不是
if(this.no == no) {
return this;
}
return resNode;
}
}