線索二叉樹 Threaded BinaryTree
一、原理
利用二叉樹中閒置的 n+1 個空間記錄每個節點的前驅和後繼。
線索:利用二叉樹空鏈域存放在某種遍歷次序下結點的前驅和後繼,這些指標稱為線索,加上線索的二叉樹稱為線索二叉樹。根據線索性質的不同,線索二叉樹可分為前序、中序、後序三種。
線索化的過程就是在遍歷的過程中修改空指標的過程。
記 ptr 指向二叉連結串列中的一個結點,以下是建立中序線索的規則:
- ptr->lchild 為空,則存放該結點的前驅結點,這個結點稱為 ptr 的中序前驅;
- ptr->rchild 為空,則存放該結點的後繼結點,這個結點稱為 ptr 的中序後繼;
顯然,在決定 lchild 是指向左孩子還是前驅,rchild 是指向右孩子還是後繼,需要一個區分標誌的。因此,我們在每個結點再增設兩個標誌域 ltag 和 rtag,注意 ltag 和 rtag 只是區分0或1數字的布林型變數,其佔用記憶體空間要小於像 lchild 和 rchild 的指標變數。
結點結構重新定義如下:
- ltag 為 0 時指向該結點的左孩子,為 1 時指向該結點的前驅;
- rtag 為 0 時指向該結點的右孩子,為 1 時指向該結點的後繼;
二、原始碼
/**
* 線索化即在遍歷過程中修改空指標
* n 個結點的二叉樹一共有 2n 個指標,n-1 已經用於 link,剩下 n+1 個用於 thread
* 剩下的:n-1 個用於按照遍歷的順序聯絡結點,多出的 2 個用於指向給出的頭結點
*/
#include <iostream>
using namespace std;
typedef enum {
link, thread // 線索儲存標誌位:link(0),表示指向左右指標,thread(1),表示指向前趨後繼的線索
} tag;
typedef struct Bnode {
char data;
struct Bnode *Lchild, *Rchild;
tag Ltag, Rtag; // 多了2個標誌位
} Bnode, *Bintree;
Bnode *pre; // 全域性變數,始終指向剛剛訪問過的結點
/**
* 先序建立二叉樹 TLR
* @param T 引用 main 函式中定義的樹根結點的地址
*/
void createBintree(Bintree &T) {
char ch;
cin >> ch;
getchar(); // 接收 space 或 enter
if (ch == '#') { // # 結束符
T = NULL;
} else {
T = (Bintree) malloc(sizeof(Bnode)); // 新建結點
T->data = ch;
cout << ch << " 左結點(#結束): ";
createBintree(T->Lchild);
cout << ch << " 右結點(#結束): ";
createBintree(T->Rchild);
}
}
/**
* 在中序遍歷基礎上,線索化聯絡結點,n-1
*/
void inThread(Bintree T) {
if (T) {
inThread(T->Lchild); // 左子樹線索化
// 原本中序遍歷輸出資料的地方 變為修改空指標
// 線索化結點的兩種方式:前趨,後繼
if (!T->Lchild) {
T->Ltag = thread; // 前趨線索
T->Lchild = pre; // 當前結點T 找前趨pre
}
if (!pre->Rchild) {
pre->Rtag = thread; // 後繼線索
pre->Rchild = T; // pre結點找後繼T (T在遞迴遍歷過程中可以是樹上任意一個結點)
}
pre = T; // 始終指向剛剛訪問過的結點 即下一輪當前結點的上一個結點
inThread(T->Rchild); //遞迴右孩子線索化
}
}
/**
* 完整化線索二叉樹 新增頭結點 設定結束結點指向
* @param head 線索二叉樹的頭結點
* @param T 原本的二叉樹
*/
void inThreadTree(Bnode *head, Bintree T) {
if (!T) {
// 若二叉樹為空,則將Lchild和Rchild指向自己(雙向連結串列head前趨和後繼指向自己,連結串列為空)
head->Lchild = head;
head->Ltag = link;
head->Rchild = head;
head->Rtag = link;
} else {
// 起始工作
head->Lchild = T; // head左指標記錄下樹根,這樣在inThreadTraverse方法中只需要傳入head就夠了
head->Ltag = link;
head->Rchild = head; // head右指標一定會線索化,先賦初值指向自己(head)
head->Rtag = thread;
pre = head; // pre 賦初值,指向head,然後中序線索化
// 中序線索化
inThread(T);
// 收尾工作
pre->Rtag = thread; // 線索化之後,pre是遍歷的最後一個結點
pre->Rchild = head;
head->Rchild = pre; // 在起始工作中,先給了head->Rchild一個初值,這裡賦給了真正的值
}
}
/**
* 中序遍歷線索二叉樹
* @param head 線索二叉樹的頭結點
*/
void inThreadTraverse(Bintree head) {
Bnode *p = head->Lchild; // 起始工作中設定了head->Lchild為樹根
// 1.滿足樹為空的情況,樹空時設定了head左右孩子都指向自己
// 2.滿足迴圈終結條件,收尾工作中設定了最後一個節點的Rchild為head
while (p != head) {
// 尋找線索開頭 從根尋找到最左結點
while (p->Ltag == link) {
p = p->Lchild;
}
cout << p->data << " ";
// 根據線索,用連結串列的方式遍歷
while (p->Rtag == thread && p->Rchild != head) {
p = p->Rchild;
cout << p->data << " ";
} // 該迴圈一般在 p->Rtag == link 時跳出,說明右邊是一棵樹,需要重新尋找右子樹的最左節點
p = p->Rchild;
}
}
/**
* 普通中序遍歷
*/
void inOrder(Bintree T) {
if (T) {
inOrder(T->Lchild);
cout << T->data << " ";
inOrder(T->Rchild);
}
}
int main() {
// 建立樹
Bintree bintree;
cout << "輸入樹的根結點: ";
createBintree(bintree);
cout << endl;
// 普通中序遍歷
cout << "普通中序遍歷" << endl;
inOrder(bintree);
cout << endl << endl;
// 線索二叉樹並遍歷
Bnode *head = (Bnode *) malloc(sizeof(Bnode)); // 頭結點
inThreadTree(head, bintree);
cout << "線索化後中序遍歷" << endl;
inThreadTraverse(head);
cout << endl << endl;
return 0;
}
輸入樹的根結點: 1
1 左結點(#結束): 2
2 左結點(#結束): #
2 右結點(#結束): #
1 右結點(#結束): 3
3 左結點(#結束): 4
4 左結點(#結束): #
4 右結點(#結束): #
3 右結點(#結束): 5
5 左結點(#結束): #
5 右結點(#結束): #
普通中序遍歷
2 1 4 3 5
線索化後中序遍歷
2 1 4 3 5
誤區
本來想用做個嘗試,線上索化二叉樹後,隨便取一個結點,輸出其前趨和後繼。
原本採用先序遍歷的方式尋找:
void preOrderSearch(Bintree T, char target) {
if (T) {
if (T->data == target) {
if (T->Ltag == thread) cout << "前趨結點:" << T->Lchild->data << endl;
if (T->Rtag == thread) cout << "後繼結點:" << T->Rchild->data << endl;
return;
}
preOrderSearch(T->Lchild, target);
preOrderSearch(T->Rchild, target);
}
}
錯誤點:if (T) 的條件控制已經不對了,線索化之後,不存在空結點了。
相關文章
- 13、線索二叉樹二叉樹
- 【筆記】線索二叉樹筆記二叉樹
- 二叉線索樹實現
- 線索二叉樹的線索化演算法二叉樹演算法
- 二叉樹(順序儲存二叉樹,線索化二叉樹)二叉樹
- 線索二叉樹【定義、應用、線索化、遍歷】二叉樹
- 樹(2)--二叉樹的遍歷(非遞迴)+線索二叉樹二叉樹遞迴
- 線索二叉樹的原理及建立二叉樹
- 線索二叉樹的構造和遍歷二叉樹
- 常用資料結構之線索二叉樹資料結構二叉樹
- 資料結構之線索化二叉樹資料結構二叉樹
- 【資料結構】二叉樹的線索化!!資料結構二叉樹
- java實現-資料結構之二叉樹(三):線索化二叉樹Java資料結構二叉樹
- 中序線索二叉樹的建立與遍歷二叉樹
- 論 如何畫線索二叉樹[資料結構]二叉樹資料結構
- 中序線索二叉樹的構造和遍歷二叉樹
- 演算法線索二叉樹的建立和遍歷演算法二叉樹
- 輕鬆讓你徹底搞定c++版 線索二叉樹的線索化演算法C++二叉樹演算法
- 【資料結構&演算法】12-線索二叉樹資料結構演算法二叉樹
- 實戰資料結構(12)_二叉樹的線索化資料結構二叉樹
- 基於二叉樹的高效IP檢索格式MMDB二叉樹
- 滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹(二叉查詢樹)和最優二叉樹二叉樹
- 二叉樹 & 二叉查詢樹二叉樹
- 排序二叉樹和平衡二叉樹排序二叉樹
- 二叉查詢樹(二叉排序樹)排序
- 手擼二叉樹——二叉查詢樹二叉樹
- 手擼二叉樹——AVL平衡二叉樹二叉樹
- 資料結構之樹結構概述(含滿二叉樹、完全二叉樹、平衡二叉樹、二叉搜尋樹、紅黑樹、B-樹、B+樹、B*樹)資料結構二叉樹
- 二叉樹二叉樹
- 二叉樹的應用(1)--二叉樹排序樹基本操作二叉樹排序
- 判斷二叉樹是否為滿二叉樹二叉樹
- 資料結構中的樹(二叉樹、二叉搜尋樹、AVL樹)資料結構二叉樹
- 自己動手作圖深入理解二叉樹、滿二叉樹及完全二叉樹二叉樹
- 二叉樹、B樹以及B+樹二叉樹
- 平衡二叉樹,B樹,B+樹二叉樹
- 深入學習二叉樹 (一) 二叉樹基礎二叉樹
- 相同二叉樹和鏡面二叉樹問題二叉樹
- 判斷某棵二叉樹是否二叉排序樹二叉樹排序