對於一個有n個結點的二叉鏈表,每個結點有指向左右孩子的兩個指針域,所以一共是2n個指針域,而n個結點的二叉樹一共有n-1條分支線路,也就是說,存在2n-(n-1)= n+1個空指針域。
考慮利用那些空地址,存放指向結點在某種遍歷次序下的前驅和后繼結點的地址。 我們把這種指向前驅和后繼的指針稱為線索,加上線索的二叉鏈表稱為線索鏈表,相應的二叉樹就稱為線索二叉樹(Threaded Binary Tree)
對二叉樹進行中序遍歷后,將所有空指針域中的rchild改為指向它們的后繼結點。
① H的后繼是D
...
⑥ G的后繼是NULL,中序遍歷最后一個節點
此時共有6個空指針域被利用
|
將所有空指針域中的lchild改為指向當前節點的前驅。
① H的前驅是NULL,中序遍歷的第一個結點
...
⑥ G的前驅是C
此時共有5個空指針被利用
|
最終,空心箭頭實線為前驅,虛線黑箭頭為后繼。線索二叉樹,等於是把一棵二叉樹變成了一個雙向鏈表,這樣插入一個結點、查找一個結點都很方便。對二叉樹以某種次序遍歷使其變為線索二叉樹的過程稱為是線索化。
為了區分lchild是指向左孩子還是前驅,rchild是指向右孩子還是后繼,需要用一個標志位來區分
■ ltag為0時指向該結點的左孩子,為1時指向該結點的前驅
■ rtag為0時指向該結點的右孩子,為1時指向該結點的后繼
二叉樹輸入過程:ABDH##I##EJ###CF##G## | 中序遍歷:HDIBJEAFCG |
中序遍歷: bac |
中序遍歷:CBAEDF |
#include<iostream>
using namespace std;
enum TBT{child=0,thread}; //線索二叉樹結點的指針是指向孩子還是前驅后繼
typedef struct tbt
{
struct tbt* lchild;
enum TBT ltag;
char data;
enum TBT rtag;
struct tbt* rchild;
}TBTreeNode,*pTBTree;
int createThreadedBinaryTree(pTBTree& root);
void inorderThreadingBinaryTree(const pTBTree& root); //中序線索化二叉樹
//在中序遍歷的同時就線索化二叉樹
void inorderThreadedBinaryTreeTraversal(pTBTree root); //中序線索化二叉樹遍歷
int main()
{
TBTreeNode* root = nullptr;
int ret = createThreadedBinaryTree(root);
{
if(0==ret)
{
inorderThreadingBinaryTree(root);
cout<<endl;
inorderThreadingBinaryTreeTraversal(root);
cout<<endl;
}
}
system("pause");
}
![]() ![]() ![]() |
int createThreadedBinaryTree(pTBTree& root)
{
char data;
if(cin>>data)
{
if('#'==data)
{
root = nullptr;
return -1;
}
else
{ //用data數據來初始化root結點,然后遞歸建立左子樹和右子樹
root = new TBTreeNode(); //創建結點的時候就把結點全部賦值為空
root->data = data;
createThreadedBinaryTree(root->lchild);
createThreadedBinaryTree(root->rchild);
}
}
return 0;
}
static TBTreeNode* pre = nullptr; //定義一個指針指向中序遍歷當前訪問結點的前一個訪問結點
//線索化結點的后繼要用到,因為中序遍歷順序:左子樹,根結點,右子樹
//前驅可以用剛剛訪問過的結點直接賦值,后繼還沒有訪問,這時候當前結點就是上一個訪問結點pre的后繼
//當然,前提條件是pre的右子樹為空
//pre初始值為nullptr,因為從根結點開始訪問,前一個訪問結點就只能是空了
void inorderThreadingBinaryTree(const pTBTree& root)
{
if(nullptr==root)
return ;
/* 參考中序遍歷
inorderTraversal(root->lchild);
cout<<root->data<<" ";
inorderTraversal(root->rchild);
*/
inorderThreadingBinaryTree(root->lchild); //中序遍歷左子樹
//判斷結點指針域可不可以線索化
if(nullptr==root->lchild) //如果左子樹為空,就可以把指針域拿來線索化,指向前驅
{
root->lchild = pre;
root->ltag = thread;
}
if(nullptr!=pre&&nullptr==pre->rchild) //如果當前訪問的根結點不為空,並且前面訪問的結點pre右子樹為空,線索化前一個結點的后繼
{
pre->rchild = root;
pre->rtag = thread;
}
//訪問根結點就變成修改前一個訪問結點指針pre
pre = root; //之后要訪問右子樹,當前結點自然就是pre
inorderThreadingBinaryTree(root->rchild); //中序遍歷右子樹
}
void inorderThreadedBinaryTreeTraversal(pTBTree root)
{
if(nullptr==root)
return;
while(nullptr!=root)
{
while(nullptr!=root->lchild&&child==root->ltag) //兩個條件,區別中序遍歷第一個結點的前驅是nullptr
{//搜尋從根結點開始的左子樹的最后一個節點
root = root->lchild;
}
cout<<root->data<<" "; //輸出根結點
while(thread==root->rtag) //該結點有后繼,意味着沒有右子樹
{
cout<<root->rchild->data<<" "; //直接輸出后繼,也就是中序遍歷當前結點下一個要訪問的結點的值
root = root->rchild; //根結點回溯到后繼
}
//該結點有右子樹,root->rtag==child,左子樹已經遍歷完了,這里進入右子樹
root = root->rchild; //重復上面的操作
}
}
|