java實現線索化二叉樹的前序、中序、後續的遍歷
比如創建一個二叉樹
1
/ \
3 6
/ \ /
8 10 14
-
線索化二叉樹幾個概念:
- n個節點的二叉鏈表中含有n+1
【公式2n-(n-1)=n+1】個空指針域。利用二叉鏈表中的空指針域,存放指向該節點在某種遍歷次序下的前驅和後繼節點的指針(這種附加指針成爲線索)。
如下面的就是6+1=7個空指針域 (8,10,14各有連個指針沒有指向 6有一個) - 加上了線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹。分爲前序線索二叉樹、中序線索二叉樹、後序線索二叉樹
- 一個節點的前一個節點,稱爲前驅節點
- 一個節點的後一個節點,稱爲後繼節點
- 線索化後的二叉樹,節點可能指向的是前驅或者後繼節點,也有可能指向的是本身的二叉樹的節點
- n個節點的二叉鏈表中含有n+1
- 前序、中序、後序線索化和遍歷
//創建樹的節點
/**
* 〈節點定義〉
*
* @author nick
* @create 2019/9/17
* @since 1.0.0
*/
@Data
public class HeroNode {
private int no;
private String name;
/**
* //默認null
*/
private HeroNode left;
/**
* //默認null
*/
private HeroNode right;
/**
* //父節點的指針(爲了後序線索化使用)
*/
private HeroNode parent;
/**
* //說明
* //1. 如果leftType == 0 表示指向的是左子樹, 如果 1 則表示指向前驅結點
* //2. 如果rightType == 0 表示指向是右子樹, 如果 1表示指向後繼結點
*/
private int leftType;
private int rightType;
public HeroNode(int no, String name) {
this.no = no;
this.name = name;
}
@Override
public String toString() {
return "HeroNode [no=" + no + ", name=" + name + "]";
}
}
/**
* 〈線索化二叉樹〉
* 1
* / \
* 3 6
* / \ /
* 8 10 14
*
* @author nick
* @create 2019/9/17
* @since 1.0.0
*/
public 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);
}
/**
* 重載一把threadedNodesPre方法
*/
public void threadedNodesPre() {
this.threadedNodesPre(root);
}
/**
* 重載一把threadedNodesAfter方法
*/
public void threadedNodesAfter() {
this.threadedNodesAfter(root);
}
/***********************遍歷線索化二叉樹開始**********************/
/**
* 中序遍歷線索化二叉樹的方法
* <p>
*/
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();
}
}
/**
* 前序線索化二叉樹遍歷方法
* 1
* / \
* 3 6
* / \ /
* 8 10 14
* <p>
* {1,3,8,10,6,14}
*/
public void threadedListPre() {
//定義一個變量,存儲當前遍歷的結點,從root開始
HeroNode node = root;
while ( node != null ) {
while ( node.getLeftType() == 0 ) {
//如果是葉子節點,非前驅節點,打印當前這個結點
System.out.print(node + ",");
node = node.getLeft();
}
System.out.print(node + ",");
//替換這個遍歷的結點
node = node.getRight();
}
}
/**
* 後序線索化二叉樹遍歷方法
* <p>
* 注意後序有點複雜,需要建立二叉樹的時候,將節點的parent進行賦值,否則不能遍歷成功
* 1
* / \
* 3 6
* / \ /
* 8 10 14
* <p>
* {8,10,3,1,14,6}
* 1. 如果leftType == 0 表示指向的是左子樹, 如果 1 則表示指向前驅結點
* 2. 如果rightType == 0 表示指向是右子樹, 如果 1表示指向後繼結點
*/
public void threadedListAfter() {
//1、找後序遍歷方式開始的節點
HeroNode node = root;
while ( node != null && node.getLeftType() == 0 ) {
node = node.getLeft();
}
while ( node != null ) {
//右節點是線索
if (node.getRightType() == 1) {
System.out.print(node + ", ");
pre = node;
node = node.getRight();
} else {
//如果上個處理的節點是當前節點的右節點
if (node.getRight() == pre) {
System.out.print(node + ", ");
if (node == root) {
return;
}
pre = node;
node = node.getParent();
} else { //如果從左節點的進入則找到有子樹的最左節點
node = node.getRight();
while ( node != null && node.getLeftType() == 0 ) {
node = node.getLeft();
}
}
}
}
}
/***********************遍歷線索化二叉樹結束**********************/
/****************線索化二叉樹開始********************************/
/**
* 中序線索化
* 得到的數組{8, 3, 10, 1, 14, 6}
* 1
* / \
* 3 6
* / \ /
* 8 10 14
*
* @param node 就是當前需要線索化的結點
*/
public void threadedNodes(HeroNode node) {
//如果node==null, 不能線索化
if (node == null) {
return;
}
//(一)先線索化左子樹
threadedNodes(node.getLeft());
//(二)線索化當前結點[有難度]
//處理當前結點的前驅結點
//以8結點來理解
//8結點的.left = null , 8結點的.leftType = 1
if (null == node.getLeft()) {
//讓當前結點的左指針指向前驅結點
node.setLeft(pre);
//修改當前結點的左指針的類型,指向前驅結點
node.setLeftType(1);
}
//處理後繼結點,是下一次進行處理,有點不好理解
if (pre != null && pre.getRight() == null) {
//讓前驅結點的右指針指向當前結點
pre.setRight(node);
//修改前驅結點的右指針類型
pre.setRightType(1);
}
//!!! 每處理一個結點後,讓當前結點是下一個結點的前驅結點
pre = node;
//(三)在線索化右子樹
threadedNodes(node.getRight());
}
/**
* 前序線索化
* 變成數組後{1,3,8,10,6,14}
* 1
* / \
* 3 6
* / \ /
* 8 10 14
*
* @param node 就是當前需要線索化的結點
*/
public void threadedNodesPre(HeroNode node) {
//如果node==null, 不能線索化
if (node == null) {
return;
}
//左指針爲空,將左指針指向前驅節點
//8結點的.left = 上一個節點 , 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;
//(一)先線索化左子樹
if (node.getLeftType() != 1) {
threadedNodesPre(node.getLeft());
}
//(三)再線索化右子樹
if (node.getRightType() != 1) {
threadedNodesPre(node.getRight());
}
}
/**
* 後序線索化
* 變成數組後{8,10,3,1,14,6}
*
* @param node
*/
public void threadedNodesAfter(HeroNode node) {
//如果node==null, 不能線索化
if (node == null) {
return;
}
//(一)先線索化左子樹
threadedNodesAfter(node.getLeft());
//(三)再線索化右子樹
threadedNodesAfter(node.getRight());
//左指針爲空,將左指針指向前驅節點
//8結點的.left = 上一個節點 , 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;
}
/*********************線索化結束*********************************/
}
//測試二叉樹的遍歷
/**
* 〈線索化二叉樹測試〉
*
* @author nick
* @create 2019/9/17
* @since 1.0.0
*/
public class ThreadedBinaryTreeTest extends Tester {
@Test
public void testPolandNotation() throws Exception {
//測試一把中序線索二叉樹的功能 以數組{8, 3, 10, 1, 14, 6}爲例
/**
* 1
* / \
* 3 6
* / \ /
* 8 10 14
*/
HeroNode root = new HeroNode(1, "java");
HeroNode node2 = new HeroNode(3, "C#");
HeroNode node3 = new HeroNode(6, "Python");
HeroNode node4 = new HeroNode(8, "C++");
HeroNode node5 = new HeroNode(10, "GO");
HeroNode node6 = new HeroNode(14, "Dephi");
//二叉樹,後面我們要遞歸創建, 現在簡單處理使用手動創建
root.setLeft(node2);
root.setRight(node3);
node2.setLeft(node4);
node2.setRight(node5);
node3.setLeft(node6);
//*************測試中序線索化***************//
System.out.println("==========中序線索化開始=============");
System.out.println("{8, 3, 10, 1, 14, 6}");
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
//********************中序結束******************//
//******************前序*****************//
System.out.println("==========前序線索化開始=============");
System.out.println("{1,3,8,10,6,14}");
//前序:{1,3,8,10,6,14}
ThreadedBinaryTree threadedBinaryTreePre = new ThreadedBinaryTree();
threadedBinaryTreePre.setRoot(root);
threadedBinaryTreePre.threadedNodesPre();
//測試: 以10號節點測試
HeroNode leftNodePre = node4.getLeft();
HeroNode rightNodePre = node4.getRight();
System.out.println("8號結點的前驅結點是 =" + leftNodePre); //3
System.out.println("8號結點的後繼結點是=" + rightNodePre); //10
HeroNode leftNodetenPre = node5.getLeft();
HeroNode rightNodetenPre = node5.getRight();
System.out.println("10號結點的前驅結點是 =" + leftNodetenPre); //8
System.out.println("10號結點的後繼結點是=" + rightNodetenPre); //6
System.out.println("前序使用線索化的方式遍歷 線索化二叉樹");
threadedBinaryTreePre.threadedListPre();//{1,3,8,10,6,14}
//******************前序結束*****************//
//******************後序*****************//
//如果是後序,需要創建二叉樹的時候,將parent進行保存。這個是用於後續二叉樹的遍歷的
node2.setParent(root);
node3.setParent(root);
node4.setParent(node2);
node5.setParent(node2);
node6.setParent(node3);
System.out.println("==========後序線索化開始=============");
System.out.println("{8,10,3,14,6,1}");
//後序:{8,10,3,14,6,1}
ThreadedBinaryTree threadedBinaryTreeAfter = new ThreadedBinaryTree();
threadedBinaryTreeAfter.setRoot(root);
threadedBinaryTreeAfter.threadedNodesAfter();
HeroNode leftNodeAfter = node4.getLeft();
HeroNode rightNodeAfter = node4.getRight();
System.out.println("8號結點的前驅結點是 =" + leftNodeAfter); //null
System.out.println("8號結點的後繼結點是=" + rightNodeAfter); //10
HeroNode leftNodetenAfter = node5.getLeft();
HeroNode rightNodetenAfter = node5.getRight();
System.out.println("10號結點的前驅結點是 =" + leftNodetenAfter); //8
System.out.println("10號結點的後繼結點是=" + rightNodetenAfter); //3
System.out.println("後序使用線索化的方式遍歷 線索化二叉樹");
threadedBinaryTreeAfter.threadedListAfter();//{8,10,3,14,6,1}
}
}
- 前序和中序差不多,比較好理解。後序比較難以理解。不過結合代碼後,斷點執行,看一下過程,對理解有好處。特別注意是後序遍歷要在二叉樹創建的時候,將parent進行保存設置。