1. 程式人生 > >樹:線索二叉樹詳解

樹:線索二叉樹詳解

線索二叉樹介紹

我們在有n個結點的二叉連結串列中,每個結點有指向左右2個孩子的指標域,所以有2n個指標域,而n個結點的二叉樹一共有n-1條分支線,也就是說,其實存在2n-(n-1) = n+1 個空指標域。空間十分浪費。在另一方面,我們對二叉樹做中序遍歷時,我只知道每個樹結點的左右孩子是誰,卻不知道該樹結點的前驅和後繼是誰要想知道必須重新遍歷一遍

為什麼不考慮在建立時就記住這些前驅和後繼呢,那將會省去很多時間仔細想想,如果我們的樹結點左右孩子都不為NULL ,如果採用中序遍歷,那麼該結點的前驅結點是不是左孩子,後繼結點是不是右孩子?這就是為什麼我們要採用中序遍歷的形式來對二叉樹進行線索化,那麼我需要將孩子結點為NULL的樹結點 空間利用起來!

因為位元組點雖然為NULL,但是都有前驅和後繼結點,那麼我們可以考慮將 lchild 放樹節點的前驅結點,rchild放樹結點的後繼結點那麼這裡我們是否要考慮區分lchild,rchild 是原有的子節點還是我們後續線索化新增的前驅後繼結點呢?所以要線上索二叉樹結點的資料結構都要體現唄。我們把這種指向前驅和後繼的指標稱為線索,加上線索的二叉連結串列稱為線索連結串列,相應的的二叉樹就稱為線索二叉樹(Threaded Binary Tree)。

線索二叉樹實現思路

建立線索二叉樹和建立普通的二叉樹(二叉連結串列)相似,我們同樣約定採用前序遍歷的方式進行建立樹結點如果普通二叉樹不是很清楚的,

如何線索化二叉樹呢?

我們採用中序遍歷二叉樹訪問每一個樹結點的時候,我們只知道當前結點,如何知道前驅結點呢?我們能否考慮將剛剛訪問的結點(pre)儲存下來,每次訪問樹結點的時候,發現左孩子結點為NULL,就將其線索化!它的前驅結點是不是pre結點呢?!tree->lchild = pre;那個剛剛訪問的結點 (pre)的右孩子結點 為NULL,那麼將其線索化 pre 它的後繼結點是不是當前結點呢?!pre->rchild = tree具體詳細程式碼請看程式碼實現。

下面是一張簡陋的二叉線索樹的圖箭頭方向 代表該結點前驅和後繼結點。紅色箭頭表示 原來的孩子結點都不為空,直接就是自己的前驅或者後繼結點。黑色箭頭表示的通過線索新增的前驅後驅結點,都是孩子結點為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__,下劃線是空格的意思