前面介紹了前序線索化二叉樹、中序線索化二叉樹,本文將介紹后序線索化二叉樹。之所以用單獨的一篇文章來分析后序線索化二叉樹,是因為后序線索化二叉樹比前序、中序要復雜一些;另外在復習線索化二叉樹的過程中,大部分講解數據結構的書籍中都是以中序線索化為例,在網上搜索也很少有詳細講解前序、后序線索化的文章,對於使用Java語言編寫的代碼更是鳳毛麟角,因此決定把個人的理解過程記錄下,並分享給有需要的同學參考。
一、圖解后序線索化
如果你很清楚的理解了前序、中序線索化二叉樹,那么下面的圖解不難理解;如果你還未掌握前序、中序線索化二叉樹,請先詳細了解線索二叉樹之前序、中序線索化,然后再回來閱讀本文更便於理解。
下圖是一棵后序線索化的二叉樹,如下圖:
為了更清晰、直觀的表示出后繼線索,在上圖中忽略了前驅線索,請自行腦補。通過觀察上圖,節點H的后繼節點是I,因此節點H的right指針指向I;節點I的后繼節點是D,因此節點D的right指針指向D;節點D的后繼節點是E,但是節點D的right指針指向了子節點B,因此D的right指針也就不能指向后繼節點;同理節點B也沒辦法指向后繼節點F。
對這棵二叉樹完成后序線索化之后,我們在對其進行遍歷時,我們知道后序遍歷的順序是:左右根,那對於上圖的后序遍歷結果是:HIDEBFGCA。
遍歷后序線索化二叉樹的思路:由於是后序線索化,那么后序遍歷的開始節點一定是最左子節點,從根節點出發找到最左子節點,如何判斷是否是最左子節點呢?如果是最左子節點,則其left指針一定的線索,如上圖我們找到最左子節點H,H的right指針是后繼線索,找到節點I,節點I的right指針是后繼線索,找到節點D,節點D的right指針是子節點I,並不是后繼線索指針,那么問題來了?此時我們該如何處理呢?
通過觀察D的后繼節點E,但是D與E沒有直接線索,不過D的父節點是B,B的右字節是E,存在這樣一個間接的關系,我們是否可以利用這個間接的關系呢?答案是肯定的,但是按照我們上文介紹的節點數據結構,並不存在指向父節點的指針,因此我們要對節點數據結構進行修改,修改如下:
//節點存儲結構
static class Node {
String data; //數據域
Node left; //左指針域
Node right; //右指針域
Node parent; //父節點的指針(為了后序線索化使用)
boolean isLeftThread = false; //左指針域類型 false:指向子節點、true:前驅或后繼線索
boolean isRightThread = false; //右指針域類型 false:指向子節點、true:前驅或后繼線索
Node(String data) {
this.data = data;
}
}
按照如上的存儲結構增加了parent指針之后,D節點存在了指向父節點B的指針。當遍歷到D節點時找到D節點的父節點B,B的right指針指向了子節點E,E的right指針又指向了B,這里又出現了另一個問題,就是進入了兩次B,如果按照前面的方式則進入了一個死循環。以節點B為例,我們什么時候去找B的父節點,什么時候去處理他的右節點呢。我們分析下兩次進入節點B,第一次是通過B的左節點進入,第二次是通過右子節點進入,我們可以記錄上一個處理的節點,如果上一個處理的節點是B的左節點,則接下進入B的右節點,如果上一個處理的節點是B的右節點,則說明B的左右子樹都處理完成,繼續處理B的父節點。
二、Java代碼實現后序線索化
package com.bj58.demo.struct;
/**
* @Title: 后序線索化二叉樹相關操作
* @Description:
* @Author: Uncle Ming
* @Date:2017年1月8日 下午3:42:14
* @Version V1.0
*/
public class PostThreadBinaryTree {
private Node preNode; //線索化時記錄前一個節點
//節點存儲結構
static class Node {
String data; //數據域
Node left; //左指針域
Node right; //右指針域
Node parent; //父節點的指針(為了后序線索化使用)
boolean isLeftThread = false; //左指針域類型 false:指向子節點、true:前驅或后繼線索
boolean isRightThread = false; //右指針域類型 false:指向子節點、true:前驅或后繼線索
Node(String data) {
this.data = data;
}
}
/**
* 通過數組構造一個二叉樹(完全二叉樹)
* @param array
* @param index
* @return
*/
static Node createBinaryTree(String[] array, int index) {
Node node = null;
if(index < array.length) {
node = new Node(array[index]);
node.left = createBinaryTree(array, index * 2 + 1);
node.right = createBinaryTree(array, index * 2 + 2);
//記錄節點的父節點(后序線索化遍歷時使用)
if(node.left != null) {
node.left.parent = node;
}
if(node.right != null) {
node.right.parent = node;
}
}
return node;
}
/**
* 后序線索化二叉樹
* @param node 節點
*/
void postThreadOrder(Node node) {
if(node == null) {
return;
}
//處理左子樹
postThreadOrder(node.left);
//處理右子樹
postThreadOrder(node.right);
//左指針為空,將左指針指向前驅節點
if(node.left == null) {
node.left = preNode;
node.isLeftThread = true;
}
//前一個節點的后繼節點指向當前節點
if(preNode != null && preNode.right == null) {
preNode.right = node;
preNode.isRightThread = true;
}
preNode = node;
}
/**
* 后續遍歷線索二叉樹,按照后繼方式遍歷(思路:后序遍歷開始節點是最左節點)
* @param node
*/
void postThreadList(Node root) {
//1、找后序遍歷方式開始的節點
Node node = root;
while(node != null && !node.isLeftThread) {
node = node.left;
}
Node preNode = null;
while(node != null) {
//右節點是線索
if(node.isRightThread) {
System.out.print(node.data + ", ");
preNode = node;
node = node.right;
} else {
//如果上個處理的節點是當前節點的右節點
if(node.right == preNode) {
System.out.print(node.data + ", ");
if(node == root) {
return;
}
preNode = node;
node = node.parent;
} else { //如果從左節點的進入則找到有子樹的最左節點
node = node.right;
while(node != null && !node.isLeftThread) {
node = node.left;
}
}
}
}
}
public static void main(String[] args) {
String[] array = {"A", "B", "C", "D", "E", "F", "G", "H", "I"};
Node root = createBinaryTree(array, 0);
PostThreadBinaryTree tree = new PostThreadBinaryTree();
tree.postThreadOrder(root);
System.out.println("后序按后繼節點遍歷線索二叉樹結果:");
tree.postThreadList(root);
}
}
運行結果如下:
后序按后繼節點遍歷線索二叉樹結果:
H, I, D, E, B, F, G, C, A,
三、前序、中序、后序線索化比較
1. 前序線索化二叉樹遍歷相對最容易理解,實現起來也比較簡單。由於前序遍歷的順序是:根左右,所以從根節點開始,沿着左子樹進行處理,當子節點的left指針類型是線索時,說明到了最左子節點,然后處理子節點的right指針指向的節點,可能是右子樹,也可能是后繼節點,無論是哪種類型繼續按照上面的方式(先沿着左子樹處理,找到子樹的最左子節點,然后處理right指針指向),以此類推,直到節點的right指針為空,說明是最后一個,遍歷完成。
2. 中序線索化二叉樹的網上相關介紹最多。中序遍歷的順序是:左根右,因此第一個節點一定是最左子節點,先找到最左子節點,依次沿着right指針指向進行處理(無論是指向子節點還是指向后繼節點),直到節點的right指針為空,說明是最后一個,遍歷完成。
3. 后序遍歷線索化二叉樹最為復雜,通用的二叉樹數節點存儲結構不能夠滿足后序線索化,因此我們擴展了節點的數據結構,增加了父節點的指針。后序的遍歷順序是:左右根,先找到最左子節點,沿着right后繼指針處理,當right不是后繼指針時,並且上一個處理節點是當前節點的右節點,則處理當前節點的右子樹,遍歷終止條件是:當前節點是root節點,並且上一個處理的節點是root的right節點。
轉載自:https://blog.csdn.net/UncleMing5371/article/details/54291221