首先,什麼是二叉樹的線索化,爲什麼要對二叉樹線索化?
二叉樹是一種非線性結構,遍歷二叉樹幾乎都是通過遞歸或者用棧輔助實現非遞歸的遍歷。用二叉樹作爲存儲結構時,取到一個節點,只能獲取節點的左孩子和右孩子,不能直接得到節點的任一遍歷序列的前驅或者後繼。
爲了保存這種在遍歷中需要的信息,我們利用二叉樹中指向左右子樹的空指針來存放節點的前驅和後繼信息
n個節點的二叉樹中含有n+1個空指針域。利用二叉樹中的空指針域 來存放在某種遍歷次序下的前驅和後繼 ,這種指針叫“線索”。這種加上了線索的二叉樹稱爲線索二叉樹。
根據線索的性質的不同,線索二叉樹分爲:前序線索二叉樹 , 中序線索二叉樹 , 後序線索二叉樹(本篇博客主要講述前面兩種 , 後面會專門對後序線索二叉樹分析 )
typedef enum
2.{
3. Link,
4. Thread
5.} PointTag;
6.
7.typedef struct BinaryTreeNode
8.{
9. BinaryTreeNode(const char data)
10. :_data(data)
11. , pLeft(NULL)
12. , pRight(NULL)
13. , Ltag(Link)
14. , Rtag(Link)
15. {}
16. char _data;
17. struct BinaryTreeNode *pLeft , * pRight;
18. PointTag Ltag, Rtag;
19.}BiTreeNode;
Ltag 標記是否有左子樹 , Ltag 是Link 則表示有左子樹 ; Ltag是Thread表示沒有左子樹(有前驅) Rtag 標記是否有右子樹 , Rtag是Link 則表示有右子樹 ; Rtag是Thread 表示沒有右子樹(有後繼)
首先,在線索化二叉樹之前,你得有二叉樹吧 還是 按之前的方式:先序創建二叉樹
void _CreatTree(BiTreeNode*& Root, const char* arr, size_t size, size_t& index)
2. {
3. if (arr == NULL || size == 0)
4. {
5. cout << "輸入有誤 " << endl;
6. return;
7. }
8. if (index < size && arr[index] != '#')
9. {
10. Root = new BiTreeNode(arr[index]);
11. _CreatTree(Root->pLeft, arr, size, ++index);
12. _CreatTree(Root->pRight, arr, size, ++index);
13. }
14. }
以下面的二叉樹爲例:
1、先序線索化二叉樹和遍歷
先序遍歷的順序:0 1 2 3 4
先序線索化二叉樹
分析:首先對於左子樹存在的節點,就不會有前驅;同樣,節點的右子樹存在,那麼不存在後繼。那麼我就先一直找尋節的左子樹,判斷左右子樹是否爲空 , 爲空了纔會考慮前驅和後繼的問題。後繼倒是很好找(反正要遍歷二叉樹),那麼前驅的位置呢?如果遇到左子樹不存在的節點,怎麼才能找到已經已經掃面過得節點呢?所以我們需要創建一個節點指針,每次記住上一次掃描的節點 。 對於右子樹就很好辦了 , 如果當前節點的前一個節點不爲空且右子樹不存在,那麼我們就放心大膽的鏈上吧 。然後循環得了。
假設 記錄前一個節點的指針爲:
- BiTreeNode* Prev =NULL;
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);
- }
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;
- }
- }
- }
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 _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;
- }
- }
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;
- }