二叉樹線索化以及線索化的先序、中序、後序遍歷
本篇部落格主要講述:二叉樹的線索化以及對線索二叉樹遍歷實現。
首先,什麼是二叉樹的線索化,為什麼要對二叉樹線索化?
二叉樹是一種非線性結構,遍歷二叉樹幾乎都是通過遞迴或者用棧輔助實現非遞迴的遍歷。用二叉樹作為儲存結構時,取到一個節點,只能獲取節點的左孩子和右孩子,不能直接得到節點的任一遍歷序列的前驅或者後繼。
為了儲存這種在遍歷中需要的資訊,我們利用二叉樹中指向左右子樹的空指標來存放節點的前驅和後繼資訊
n個節點的二叉樹中含有n+1個空指標域。利用二叉樹中的空指標域 來存放在某種遍歷次序下的前驅和後繼 ,這種指標叫“線索”。這種加上了線索的二叉樹稱為線索二叉樹。
根據線索的性質的不同,線索二叉樹分為:前序線索二叉樹 , 中序線索二叉樹 , 後序線索二叉樹(本篇部落格主要講述前面兩種 , 後面會專門對後序線索二叉樹分析 )
線索二叉樹節點:
typedef enum
{
Link,
Thread
} PointTag;
typedef struct BinaryTreeNode
{
BinaryTreeNode(const char data)
:_data(data)
, pLeft(NULL)
, pRight(NULL)
, Ltag(Link)
, Rtag(Link)
{}
char _data;
struct BinaryTreeNode *pLeft , * pRight;
PointTag Ltag, Rtag;
}BiTreeNode;
Ltag 標記是否有左子樹 , Ltag 是Link 則表示有左子樹 ; Ltag是Thread表示沒有左子樹(有前驅)
Rtag 標記是否有右子樹 , Rtag是Link 則表示有右子樹 ; Rtag是Thread 表示沒有右子樹(有後繼)
首先,線上索化二叉樹之前,你得有二叉樹吧
還是 按之前的方式:先序建立二叉樹
void _CreatTree(BiTreeNode*& Root, const char* arr, size_t size, size_t& index) { if (arr == NULL || size == 0) { cout << "輸入有誤 " << endl; return; } if (index < size && arr[index] != '#') { Root = new BiTreeNode(arr[index]); _CreatTree(Root->pLeft, arr, size, ++index); _CreatTree(Root->pRight, arr, size, ++index); } }
以下面的二叉樹為例:
先序遍歷的順序:0 1 2 3 4
先序線索化二叉樹
分析:首先對於左子樹存在的節點,就不會有前驅;同樣,節點的右子樹存在,那麼不存在後繼。那麼我就先一直找尋節的左子樹,判斷左右子樹是否為空 , 為空了才會考慮前驅和後繼的問題。後繼倒是很好找(反正要遍歷二叉樹),那麼前驅的位置呢?如果遇到左子樹不存在的節點,怎麼才能找到已經已經掃面過得節點呢?所以我們需要建立一個節點指標,每次記住上一次掃描的節點 。 對於右子樹就很好辦了 , 如果當前節點的前一個節點不為空且右子樹不存在,那麼我們就放心大膽的鏈上吧 。然後迴圈得了。
假設 記錄前一個節點的指標為:
BiTreeNode* Prev =NULL;
沒聽懂描述麼? 沒關係,如果所示:
上程式碼:
void _PreOrderThreading(BiTreeNode*& Root)
{
if (Root == NULL)
{
return;
}
if (Root->pLeft == NULL) //沒有左子樹
{
Root->pLeft = Prev; //前驅
Root->Ltag = Thread;
}
if (Prev != NULL && Prev->pRight == NULL) // 上一個節點有沒有 右子樹
{ //Prev第一次進來 為空
Prev->pRight = Root; //後繼
Prev->Rtag = Thread;
}
Prev = Root;//前驅 , 每次記住上次的節點
//判斷Root是否有左右孩子
if (Root->Ltag == Link)
_PreOrderThreading(Root->pLeft);
if (Root->Rtag == Link)
_PreOrderThreading(Root->pRight);
}
對於先序線索二叉樹,我想提醒的是:每次只會把當前節點的左子樹前驅鏈上,這一次的 後繼 不會在本次鏈上,當pCur指向下一個節點的時候,才會把上一次的後繼鏈上
結果:
那麼 怎麼 寫先序遍歷線索二叉樹呢
void _PreOrder(BiTreeNode* Root)
{
if (Root == NULL)
{
return;
}
BiTreeNode* pCur = Root;
while (pCur != NULL)
{
while (pCur->pLeft != NULL && pCur->Ltag == Link)//找到最左邊的節點,左標記一直為Link
{
cout << pCur->_data << ' ';
pCur = pCur->pLeft;
}
//到這來,左邊的的節點還沒有訪問
cout << pCur->_data << ' ';
if (pCur->Ltag == Thread)//遇到線索 就看右節點
{
pCur = pCur->pRight;
}
while (pCur != NULL)//迴圈右節點
{
if (pCur->pLeft != NULL && pCur->Ltag == Link)//遇到左節點存在 , 則訪問
{
break;
}
cout << pCur->_data << ' ';
pCur = pCur->pRight;
}
}
}
程式碼解釋:
2、中序線索化二叉樹和遍歷
中序遍歷的順序:213 0 4
中序遍歷線索化二叉樹
分析:還是和先序很像的 ,中序的順序是左-根-右,我們同樣可以使用上面的遞迴方式;
話不多說,上程式碼:
void _InOrderThreading(BiTreeNode*& Root)
{
if (Root == NULL)
{
return;
}
_InOrderThreading(Root->pLeft); // 左
if (Root->pLeft == NULL) //根
{
Root->Ltag = Thread;
Root->pLeft = Prev;
}
if (Prev != NULL && Prev->pRight == NULL)
{
Prev->pRight = Root;
Prev->Rtag = Thread;
}
Prev = Root;
_InOrderThreading(Root->pRight); //右
}
中序遍歷線索二叉樹void _InOrder(BiTreeNode* Root)
{
if (Root == NULL)
{
return;
}
BiTreeNode* pCur = Root;
while (pCur )
{
while (pCur->Ltag == Link) //找最左邊的節點
{
pCur = pCur->pLeft;
}
cout << pCur->_data << ' ';
while ( pCur && pCur->Rtag == Thread )//找中序後繼節點
{
pCur = pCur->pRight;
cout << pCur->_data << ' ';
}
//沒有後繼,有右子樹
pCur = pCur->pRight;
}
}
其實,中序遍歷和先序遍歷二叉樹還是很像的,首先按照中序遍歷的順序,找到二叉樹的最左邊的節點,判斷是否有前驅,有則遍歷訪問,沒有則看右子樹和後繼的情況。
此處,可以按照之前的畫圖過程繼續一步步來。
全部程式碼:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
#include<assert.h>
//線索二叉樹
typedef enum
{
Link,
Thread
} PointTag;
typedef struct BinaryTreeNode
{
BinaryTreeNode(const char data)
:_data(data)
, pLeft(NULL)
, pRight(NULL)
, Ltag(Link)
, Rtag(Link)
{}
char _data;
struct BinaryTreeNode *pLeft , * pRight;
PointTag Ltag, Rtag;
}BiTreeNode;
class Thread_BiTree
{
public:
//先序 --建立樹
Thread_BiTree(const char* arr, size_t Size)
:_pRoot(NULL)
, Prev(NULL)
{
size_t index = 0;
_CreatTree(_pRoot, arr, Size , index);//建立二叉樹
}
protected:
void _CreatTree(BiTreeNode*& Root, const char* arr, size_t size, size_t& index)
{
if (arr == NULL || size == 0)
{
cout << "輸入有誤 " << endl;
return;
}
if (index < size && arr[index] != '#')
{
Root = new BiTreeNode(arr[index]);
_CreatTree(Root->pLeft, arr, size, ++index);
_CreatTree(Root->pRight, arr, size, ++index);
}
}
public:
//先序--線索化二叉樹
void PreOrderThreading()
{
_PreOrderThreading(this->_pRoot);
}
//先序--遍歷 線索二叉樹
void PreOrder()
{
_PreOrder(this->_pRoot);
}
protected:
//先序--線索化二叉樹--C
//思路:先看左子樹, 找下一個節點的時候,在檢測上一個節點的右節點
void _PreOrderThreading(BiTreeNode*& Root)
{
if (Root == NULL)
{
return;
}
if (Root->pLeft == NULL) //沒有左子樹
{
Root->pLeft = Prev; //前驅
Root->Ltag = Thread;
}
if (Prev != NULL && Prev->pRight == NULL) // 上一個節點有沒有 右子樹
{ //Prev第一次進來 為空
Prev->pRight = Root; //後繼
Prev->Rtag = Thread;
}
Prev = Root;//前驅 , 每次記住上次的節點
//判斷Root是否有左右孩子
if (Root->Ltag == Link)
_PreOrderThreading(Root->pLeft);
if (Root->Rtag == Link)
_PreOrderThreading(Root->pRight);
}
//先序--遍歷 線索二叉樹--C
void _PreOrder(BiTreeNode* Root)
{
if (Root == NULL)
{
return;
}
BiTreeNode* pCur = Root;
while (pCur != NULL)
{
while (pCur->pLeft != NULL && pCur->Ltag == Link)//找到最左邊的節點,左標記一直為Link
{
cout << pCur->_data << ' ';
pCur = pCur->pLeft;
}
//到這來,左邊的的節點還沒有訪問
cout << pCur->_data << ' ';
if (pCur->Ltag == Thread)//遇到線索 就看右節點
{
pCur = pCur->pRight;
}
while (pCur != NULL)//迴圈右節點
{
if (pCur->pLeft != NULL && pCur->Ltag == Link)//遇到左節點存在 , 則訪問
{
break;
}
cout << pCur->_data << ' ';
pCur = pCur->pRight;
}
}
}
public:
//中序--線索化二叉樹
void InOrderThreading()
{
_InOrderThreading(_pRoot);
}
//中序--遍歷線索二叉樹
void InOrder()
{
_InOrder(this->_pRoot);
}
protected:
//中序--線索化二叉樹--C
//思路:按 左-根-右的順序 先找到最左邊的節點-> 和先序線索一樣 ,先連結左子樹,執行到下一個節點在看上次節點的右子樹 -> 右子樹
void _InOrderThreading(BiTreeNode*& Root)
{
if (Root == NULL)
{
return;
}
_InOrderThreading(Root->pLeft); // 左
if (Root->pLeft == NULL) //根
{
Root->Ltag = Thread;
Root->pLeft = Prev;
}
if (Prev != NULL && Prev->pRight == NULL)
{
Prev->pRight = Root;
Prev->Rtag = Thread;
}
Prev = Root;
_InOrderThreading(Root->pRight); //右
}
//中序--遍歷二叉樹--C
//思路:找到中序開始的節點(最左邊的節點)-> (後繼 )它的根節點,若沒有則找右節點
void _InOrder(BiTreeNode* Root)
{
if (Root == NULL)
{
return;
}
BiTreeNode* pCur = Root;
while (pCur )
{
while (pCur->Ltag == Link) //找最左邊的節點
{
pCur = pCur->pLeft;
}
cout << pCur->_data << ' ';
while ( pCur && pCur->Rtag == Thread )//找中序後繼節點
{
pCur = pCur->pRight;
cout << pCur->_data << ' ';
}
//沒有後繼,有右子樹
pCur = pCur->pRight;
}
}
public:
//後序--線索二叉樹
void PostOrderThreading()
{
_PostOrderThreading(_pRoot);
}
protected:
//後序--線索二叉數--C
//思路:左-右-根 和前面的一樣
void _PostOrderThreading(BiTreeNode*& Root)
{
if (Root == NULL)
{
return;
}
_PostOrderThreading(Root->pLeft);
_PostOrderThreading(Root->pRight);
if (Root->pLeft == NULL)
{
Root->pLeft = Prev;
Root->Ltag = Thread;
}
if (Prev != NULL && Prev->pRight == NULL)
{
Prev->pRight = Root;
Prev->Rtag = Thread;
}
Prev = Root;
}
private:
BiTreeNode* _pRoot;
BiTreeNode* Prev; //記錄
};
int main()
{
//char * arr = "013##4##25##6##";
char * arr = "012##3##4##";
Thread_BiTree tree(arr, strlen(arr));
tree.PreOrderThreading(); //先序線索化
tree.PreOrder(); //遍歷先序線索二叉樹
cout << endl << "------------------------" << endl;
char * arr1 = "013##4##25##6##";
Thread_BiTree tree1(arr1, strlen(arr1));
tree1.InOrderThreading(); //中序線索化
tree1.InOrder(); //遍歷中序線索二叉樹
cout << endl << "------------------------" << endl;
char * arr2 = "013##4##25##6##";
Thread_BiTree tree2(arr2, strlen(arr2));
tree2.PostOrderThreading();
tree2.PostOrder();
cout << endl << "------------------------" << endl;
return 0;
}
下一篇將詳細的講述,後序線索化二叉樹及遍歷(我也是花了很大的心思,看了好久才看明白)
PS:祝你們學習愉快,希望能學到知識的同時,分享給渴求知識的各位童鞋