樹:線索二叉樹詳解
線索二叉樹介紹
我們在有n個結點的二叉連結串列中,每個結點有指向左右2個孩子的指標域,所以有2n個指標域,而n個結點的二叉樹一共有n-1條分支線,也就是說,其實存在2n-(n-1) = n+1 個空指標域。空間十分浪費。在另一方面,我們對二叉樹做中序遍歷時,我只知道每個樹結點的左右孩子是誰,卻不知道該樹結點的前驅和後繼是誰。要想知道必須重新遍歷一遍。
為什麼不考慮在建立時就記住這些前驅和後繼呢,那將會省去很多時間。仔細想想,如果我們的樹結點左右孩子都不為NULL ,如果採用中序遍歷,那麼該結點的前驅結點是不是左孩子,後繼結點是不是右孩子?這就是為什麼我們要採用中序遍歷的形式來對二叉樹進行線索化,那麼我需要將孩子結點為NULL的樹結點 空間利用起來!
線索二叉樹實現思路
建立線索二叉樹和建立普通的二叉樹(二叉連結串列)相似,我們同樣約定採用前序遍歷的方式進行建立樹結點。如果普通二叉樹不是很清楚的,。
如何線索化二叉樹呢?
下面是一張簡陋的二叉線索樹的圖,箭頭方向 代表該結點前驅和後繼結點。紅色箭頭表示 原來的孩子結點都不為空,直接就是自己的前驅或者後繼結點。黑色箭頭表示的通過線索新增的前驅後驅結點,都是孩子結點為NULL 而新增的
線索二叉樹程式碼實現
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
typedef char TElemType;
//指標指向型別,Link(0) 表示為左右孩子結點,Thread(1) 表示前驅或者後驅結點
typedef enum{Link,Thread}PointerTag;
typedef struct BiThrNode
{
TElemType data;
struct BiThrNode *lchild, *rchild;
PointerTag ltag, rtag;
}BiThrNode,*BiThrTree;
//全域性變數,pre 指向剛剛訪問的樹結點
BiThrTree pre = NULL;
//約定前序遍歷的方式 建立線索二叉樹
void CreateBiThrTree(BiThrTree* tree)
{
TElemType data;
scanf("%c", &data);
if (' ' == data)
{
*tree = NULL;
}
else
{
*tree = (BiThrTree)malloc(sizeof(BiThrNode));
(*tree)->data = data;
(*tree)->ltag = (*tree)->rtag = Link;
CreateBiThrTree(&(*tree)->lchild);
CreateBiThrTree(&(*tree)->rchild);
}
return;
}
//中序遍歷的形式 將二叉樹結點 線索化
void MidOrderTraverse_Thr(BiThrTree tree)
{
if (NULL != tree)
{
//線索化左子樹
MidOrderTraverse_Thr(tree->lchild);
if (NULL == tree->lchild)
{
tree->ltag = Thread;
tree->lchild = pre;
}
if (NULL == pre->rchild)
{
pre->rtag = Thread;
pre->rchild = tree;
}
pre = tree;
//線索化右子樹
MidOrderTraverse_Thr(tree->rchild);
}
return;
}
//建立一個頭結點(lchild指向二叉樹根結點),配合二叉樹 對二叉樹進行線索化。
void BiThrTree_Thr(BiThrTree *head, BiThrTree tree)
{
//建立二叉樹的頭結點,頭結點預設ltag為Link 指向根結點,rtag 預設為線索,rchild指向中序遍歷最後一個結點(樹最右的結點)
BiThrTree headNode = (BiThrTree)malloc(sizeof(BiThrNode));
headNode->ltag = Link;
headNode->rtag = Thread;
//空樹 ,左右結點指向自己
if (NULL == tree)
{
headNode->lchild = headNode;
headNode->rchild = headNode;
}
else
{
pre = headNode;//pre 初始化為頭結點
headNode->lchild = tree;
MidOrderTraverse_Thr(tree);
//線索化完畢,pre指向樹中序遍歷的最後一個結點(樹最右邊的結點)
pre->rtag = Thread;
pre->rchild = headNode;
headNode->rchild = pre;
}
*head = headNode;
return;
}
void visit(TElemType data)
{
printf("%c ", data);
}
//中序遍歷 線索二叉樹,非遞迴形式
void MidOrderTraverse(BiThrTree head)
{
BiThrTree tree = head->lchild;
//迴圈結束條件:空樹 或者 中序遍歷完畢
while (tree != head)
{
//一直迴圈,直到找到樹最左邊的結點(樹的中序遍歷的起點)
while (tree->ltag == Link)
{
tree = tree->lchild;
}
visit(tree->data);
//迴圈完畢tree為中序遍歷中的 根結點
while (tree->rtag == Thread && tree->rchild != head)
{
tree = tree->rchild;
visit(tree->data);
}
//進行右子樹
tree = tree->rchild;
}
printf("\n");
return;
}
int main(int argc, char *argv[])
{
BiThrTree tree = NULL, head = NULL;
printf("請輸入前序遍歷結點:");
CreateBiThrTree(&tree);
BiThrTree_Thr(&head, tree);
printf("中序遍歷線索二叉樹:");
MidOrderTraverse(head);
return 0;
}
執行結果檢測
注意輸入是,ABC__D__E_G__,下劃線是空格的意思。