樹概念+ADT+二叉樹實現

樹(Tree)是n(n>=0)個節點的有限集

在任意一顆非空樹中:

(1)有且僅有一個特定的稱爲根(Root)的結點;

(2)當n>1時,其餘節點可分爲m(m>0)個互不相交的有限集,其中每一個集合本身又是一棵樹,並稱爲根節點的子樹(SubTree)

 如圖:

樹的概念

結點(Degree):包含一個元素及若干指向其子樹的結點。

結點的度:結點所擁有的子樹數。

葉子(Leaf)終端結點:度爲0的結點。

非終端結點分支結點:度數不爲0的結點。

樹的度:樹內各結點的度的最大值。

孩子(Child):結點的子樹的根稱爲該結點的孩子。

雙親(Parent):孩子的父結點。

兄弟(Sibling):同一雙親的孩子之間互稱爲兄弟。

堂兄弟:其雙親在同一層的結點互爲堂兄弟。

祖先:從根到該結點所經分支的所有結點。

子孫:以某結點爲根的子樹中的任一結點都稱爲該結點的子孫。

結點的層次(Level):從根開始定義起,根爲第一層。

樹的深度(Depth)高度:樹中結點的最大層次稱爲樹的深度。

森林(Forest):m(m>=0)棵互不相交的樹的集合。

樹的存儲結構

雙親表示法

在每個結點中,附設一個指示器指示其雙親結點到鏈表中的位置。

data parent

 data是數據域,parent是指針域,存儲該結點的雙親在數組中的下標。

/*樹的雙親表示法結點結構定義*/
# define MAX_TREE_SIZE 100
type int ElemType;
typedef struct PTNode
{
    TElemType data;/*結點數據*/
    int parent;/*雙親位置*/
}PTNode;

typedef struct 
{
    PTNode nodes[MAX_TREE_SIZE];/*結點數組*/
    int r,n;/*根的位置和結點數*/
}PTree;

指針域可以根據實際要求擴展結構,雙親域、長子域、左兄弟域。

孩子表示法

每個結點有多個指針域,其中每個指針指向子樹的根結點,這種表示方法叫做多重鏈表表示法。

 一、指針域的個數等於樹的度

這種方法適用於樹中個結點的度相差很小,每個結點的空間都別充分利用。

二、每個結點指針域的個數等於該結點的度,取一個位置存儲結點域的個數

適用於各結點的度相差比較大時

由於各結點的子樹有不同結構,在加上要維護結點的度的數值,造成時間上的損耗。 

 改進

把每個結點的孩子排列起來,以單鏈表作存儲結構,則n個結點有n個孩子鏈表,如果是葉子結點則此單鏈表爲空。n個頭指針組成一個線性表,採用順序存儲結構。

/*樹的孩子表示法結構定義*/
/*
需要設計兩種結點結構
一種是孩子鏈表的孩子結點 |child|next|【第一個域表示該結點所指向的結點,next表示該結點同一層級的相鄰結點】
另一種是表頭數組的表頭結點 |data|firstchild|【第一個域表示結點的數據,第二個結點表示與該結點子樹的 第一個孩子的下標】
*/
# define MAX_TREE_SIZE 100

/*孩子結點*/
typedef struct CTNode
{
    int child;
    struct CTNode *next;
}*ChildPtr;

/*表頭結構*/
typedef struct
{
    TElemType data;
    ChildPtr firstchild;
};

/*樹結構*/
{
    CTBox[MAX_TREE_SIZE];/*結點數組*/
    int r,n;/*根位置和結點樹*/
}

雙親孩子表示法

多用一個位置表示結點的雙親。

孩子兄弟表示法

任意一棵樹,如果他的第一個孩子存在,則唯一,它的右兄弟如果存在則唯一。

所以可設兩個指針指向該結點的第一個孩子,和該結點的右兄弟。

data firstchild rightsib

 

 

 

/*樹的孩子兄弟表示法結構*/
typedef struct CSNode
{
    TElemType data;
    struct CSNode *firstchild,*rightsib;
}CSNode,*CSTree;

需要時也可以增加一個雙親指針域來快速查找雙親。

二叉樹

二叉樹(Binary Tree)的特點是每個結點至多隻有兩棵子樹(即二叉樹中不存在度大於2的結點),並且,二叉樹有左右之分,其次序不能顛倒。

ADT

ADT BinaryTree{
    數據對象D:D是具有相同特性的數據元素的集合
    數據關係R:
      若D = Φ,則R = Φ,稱BinaryTree爲空二叉樹
      若D ≠ Φ,則R = {H},H有如下二元關係:
        (1)在D中存在唯一的稱爲根的數據元素root,它在關係H下無前驅;
        (2)若D-{root} ≠ Φ,則存在D - {root} = {Dl,Dr},且Dl ∩ Dr = Φ;
        (3)若Dl ≠ Φ,則Dl中存在唯一的元素xl,<root,xl>∈H,且存在Dl上的關係Hl⊂H;
            若Dr ≠ Φ,則Dr中存在唯一的元素xr,<root,xr>∈H,且存在Dr上的關係Hr⊂H;
            H = {<root,xl>,<root,xr>,Hl,Hr};
        (4)(Dl,{Hl})是一棵符合本定義的二叉樹,稱爲根的左子樹,
             (Dr,{Hr})是一棵符合本定義的二叉樹,稱爲根的右子樹;
    基本操作P:
     InitBiTree(&T);
        操作結果:構造空的二叉樹T。
     DestoryBiTree(&T);
        初始條件:二叉樹T存在。
        操作結構:銷燬二叉樹T。
     CreateBiTree(&T,definition);
        初始條件:definition給出二叉樹T的定義。
        操作結果:按definition構造二叉樹T。
     ClearBiTree(&T);
        初始條件:二叉樹T存在。
        操作結果:將二叉樹清爲空樹。
     BiTreeEmpty(T);
        初始條件:二叉樹T存在。
        操作結果:若T爲空二叉樹,返回TRUE,否則返回FALSE。
     BiTreeDepth(T);
        初始條件:二叉樹T存在。
        操作結果:返回T的深度。
     Root(T);
        初始條件:二叉樹T的根存在。
        操作結果:返回T的根。
     Value(T,e);
        初始條件:二叉樹T存在,e是T中某個結點。
        操作結果:返回e的值。
     Assign(T,&e,value);
        初始條件:二叉樹T存在,e是T中的某個結點。
        操作結果:結點e賦值爲value。
     Parent(T,e);
        初始條件:二叉樹T存在,e是T中的結點。
        操作結果:若e是T的非根結點,則返回它的雙親,否則返回"空"。
     LeftChild(T,e);
        初始條件:二叉樹T存在,e是T中的某個結點。
        操作結果:返回e的左孩子。若e無左孩子,則返回"空"。
     RightChild(T,e);
        初始條件:二叉樹T存在,e是T中的某個結點。
        操作結果:返回e的右孩子。若e無右孩子,則返回"空"。
     LeftSibling(T,e);
        初始條件:二叉樹T存在,e是T中的某個結點。
        操作結果:返回e的左兄弟。若e是T的左孩子或無左兄弟,則返回"空"。
     RightSibling(T,e);
        初始條件:二叉樹T存在,e是T中的某個結點。
        操作結果:返回e的右兄弟。若e是T的右孩子或無右兄弟,則返回"空"。
     InsertChild(T,p,LR,c);
        初始條件:二叉樹T存在,p指向T中的某個結點,LR爲0或1,非空二叉樹c與T不想交且右子樹爲空。
        操作結果:根據LR爲0或1,插入c爲T中p所指結點的左或右子樹。p所指結點的原有左或右子樹則成爲c的右子樹。
     DeleteChile(T,p,LR);
        初始條件:二叉樹T存在,p指向T中的某個結點,LR爲0或1.
        操作結果:根據LR爲0或1,刪除T中p所指結點的左或右子樹。
     PreOrderTraverse(T,visit());
        初始條件:二叉樹T存在,visit()是對結點操作的應用函數。
        操作結果:先序遍歷T,對每個結點調用函數visit一次且僅一次。一旦visit失敗,則操作失敗。
     InOrderTraverse(T,visit());
        初始條件:二叉樹T存在,visit()是對結點操作的應用函數。
        操作結果:中序遍歷T,對每個結點調用函數visit一次且僅一次。一旦visit失敗,則操作失敗。
     PostOrderTraverse(T,visit());
        初始條件:二叉樹T存在,visit()是對結點操作的應用函數。
        操作結果:後序遍歷T,對每個結點調用函數visit一次且僅一次。一旦visit失敗,則操作失敗。
     LevelOrderTraverse(T,visit());
        初始條件:二叉樹T存在,visit()是對結點操作的應用函數。
        操作結果:層序遍歷T,對每個結點調用函數visit一次僅且一次。一旦visit失敗,則操作失敗。
}ADT BinaryTree

二叉樹的五種基本形態

1、空樹

2、只有一個根結點

3、根結點只有左子樹

4、根結點只有右子樹

5、根結點既有左子樹又有右子樹  

特殊的二叉樹

1、斜樹

右斜樹:所有結點都只有左子樹的二叉樹

左斜樹:所有結點都只有右子樹的二叉樹

2、滿二叉樹

所有分支結點都存在左子樹和有子樹,並且葉子都在同一層上。

特點:

(1)葉子只能出現在最下一層,出現在其他層就不能達成平衡。

(2)非葉子結點的度一定是2。

(3)在同樣深度的二叉樹中,滿二叉樹的結點個數最多,葉子最多。

3、完全二叉樹

二叉樹具有n個結點的二叉樹按層序編號,如果編號爲i(1<=i<=n)的結點與同樣深度的滿二叉樹中編號爲i的結點在二叉樹中的位置完全相同,則稱該二叉樹爲完全二叉樹。

特點

(1)葉子結點只出現在最下兩層

(2)最下層葉子一定集中在左部連續的位置

(3)倒數第二層,若有葉子結點,一定都在右部連續位置

(4)如果結點的度爲1,則該結點只有左孩子,不存在右子樹情況

(5)同樣結點數的二叉樹,完全二叉樹深度最小【不存在空擋】

二叉樹的性質

性質一:第i層上最多有2^{i-1^{}}個結點(i>=1)

性質二:深度爲k的二叉樹至多有2^{k}-1個結點(i>=1)

性質三:任何一棵二叉樹T,其葉子結點爲n0,度爲2的結點數爲n2,則n0=n2+1

               任意一棵樹,其葉子結點的個數n0=(k-1)n_{}^{k}+(k-2)n_{}^{k-1}+(k-3)n_{}^{k-2}+...+1

性質四:具有n個結點的完全二叉樹深度爲 ,【向下取整】

性質五:如果有一棵有n個結點的完全二叉樹(深度爲)的結點按層序編號,對任一結點i有(1 <= n <=n)

             1.如果i=1,則結點i是二叉樹的跟,無雙親;如果i>1,則其雙親是結點⌊i/2⌋。

             2.如果2i > n,則結點i無左孩子(結點i爲葉子結點);否則其左孩子是結點2i。

             3.如果2i+1 > n,則結點i右孩子,否則其右孩子是結點 2i+1。

二叉樹的存儲結構

二叉樹的順序存儲結構

完全二叉樹適用於這種存儲結構,將結點的編號對應到數組的相應位置上。

二叉樹的鏈式存儲結構

二叉鏈表:一個數據域,兩個指針域

可根據具體需求增加雙親指針域,稱爲三叉鏈表

/*二叉樹的二叉鏈表結點結構*/
typedef struct BiNode
{
    TElemeType data;
    struct BiNode *lchild,*rchild;
}BiNode,*BTree;

遍歷二叉樹

Traverse Binary Tree:從根結點出發,按照某種次序依次訪問二叉樹中的所有結點,使得每個結點被訪問一次,且僅被訪問一次。【限定先左後右】

先序(根)遍歷

若二叉樹爲空,則空操作;否則:

(1)訪問根結點;

(2)先序遍歷左子樹;

(3)先序遍歷右子樹;

Status PreOrderTraverse(BiTree T,Status (*visit)(TElemType e))
{
    if(T)
    {
        if(visit(T->data))
            if(PreOrderTraverse(T->lchild,visit))
                 if(PreOrderTraverse(T->rchild,visit))  
                   return OK;
    
        return ERROR;
    }

    else
        return OK;
}

中序(根)遍歷

若二叉樹爲空,則空操作;否則:

(1)中序遍歷左子樹;

(2)訪問根結點;

(3)中序遍歷右子樹;  

/*遞歸遍歷二叉樹*/
Status InOrderTraverse(BiTree T,Status (*visit)(TElemType e))
{
    if(T)
    {
        if(InOrderTraverse(T->lchild,visit))
            if(visit(T->data))
                if(InOrderTraverse(T->rchild,visit))  
                    return OK;
    
        return ERROR;
    }

    else
        return OK;
}

當棧頂記錄中的指針非空時,應遍歷左子樹,即指向左子樹的指針進棧;

若棧頂記錄中的指針爲空,則應退至上一層,若是從左子樹返回,則應訪問當前層即棧頂記錄中指針所指的根結點。

若是從右子樹返回,則表明當前層的遍歷結束,應繼續退棧。【不必保存根結點,只需修改棧頂記錄即可】 

/*中序遍歷的非遞歸實現*/
Status InOrderTraverse(BiTree T,Status (*visit)(TElemType e))
{
	InitStack(S);
	Push(S,T);//根指針進棧
	while(!StackElempty(S))
	{
		while(GetTop(S,p))
			push(S,p->lchild);//向左走到頭
		Pop(S,p);//空指針退棧 
		if(!StackEmpty(S))//訪問結點,向右走一步 
		{
			Pop(S,p);
			if(!visit(p->data))
			{
				return ERROR;
			}
			Push(S,p->rchild);
		}
	 } 
}

Status InOrderTraverse(BiTree T,Status (*visit)(TElemType e))
{
    InitStack(S);
    p = T;
    while(p || !StackEmpty(S))
    {
        if(p)
        {//根指針進棧,遍歷左子樹
            Push(S,p);
            p = p->lchild;
        }
        else 
       {//根指針退棧,訪問根結點,遍歷右子樹
            Pop(S,p);   
            if(!visit(p->data))
                return ERROR;
            p = p->rchild;
        }
    }
}

後序(根)遍歷

若二叉樹爲空,則空操作;否則:

(1)後序遍歷左子樹;

(2)後序遍歷右子樹;

(3)遍歷根結點;

Status PostOrderTraverse(BiTree T,Status (*visit)(TElemType e))
{
    if(T)
    {
        if(PostOrderTraverse(T->lchild,visit))
            if(PostOrderTraverse(T->rchild,visit))
                if(visit(T->data))
                    return OK;
    
        return ERROR;
    }

    else
        return OK;
}

推導遍歷結果

前序和中序————後序

中序和後序————前序

前序和後序不能確定一棵二叉樹,所以不能確定中序序列

二叉樹的建立

擴展二叉樹:

爲了便於建立二叉樹,需要對二叉樹的每個結點的空指針引出一個虛結點,其值爲以特定值。

/*按前序輸入二叉樹的結點的值(字符)*/
/* .表示空樹,構建二叉鏈表表示二叉樹*/
Status CreateBiTree(BiTree &T)
{
	scanf(&ch);
	if(ch == ".")
		T = NULL;
	else
	{
		if(!(T = (BiTNode*)malloc(sizeof(BiTnode))))
			exit(OVERFLOW);
		T->data = ch;//生成根結點 
		CreateBiTree(T->lchild);//構建左子樹 
		createBiTree(T->rchild);//構建右子樹 
	}
	
	return OK;
 }

二叉樹常用操作代碼實現 

// 二叉樹操作.cpp : 定義控制檯應用程序的入口點。
//由於在vs中編寫的代碼,若scanf_s處出錯可以改爲scanf

#include "stdafx.h"
# include<stdio.h>
# include<algorithm>
# include<stdlib.h>
# include<stdexcept>
# include<queue>
# include<stack>
# include<iostream>

using namespace std;

typedef char TElemType;
typedef struct BiTNode {
	TElemType data;
	struct BiTNode *lChild;
	struct BiTNode *rChild;
}BiTNode;//二叉樹結點

typedef BiTNode *BiTree;//指向二叉樹結點的指針

void InitBiTree(BiTree &T) {
	T = NULL;
}//初始化二叉樹
void ClearBiTree(BiTree &T) {
	if (T) {
		if (T->lChild)
			ClearBiTree(T->lChild);
		if (T->rChild)
			ClearBiTree(T->rChild);
		free(T);

		T = NULL;
	}
}//清空二叉樹
bool BiTreeEmpty(BiTree T) {
	return T == NULL ? true : false;
}//判斷二叉樹是否爲空
bool CreateBiTree(BiTree &T) {
	char ch;
	
	scanf_s("%c",&ch);
	if (ch == '.')
		T = NULL;
	else {
		try {
			if (!(T = (BiTNode *)malloc(sizeof(BiTNode))))
				throw "內存分配不成功!";
		}
		catch(const char* msg){
			printf("%s",msg);
			return false;
		}
		T->data = ch;
		CreateBiTree(T->lChild);
		CreateBiTree(T->rChild);
	}

	return true;
}//先序創建二叉樹

/*非遞歸創建二叉樹*/
// 二叉樹的建立(非遞歸)(按照先序遍歷的順序來生成一顆二叉樹)
BiTree createBiTree()
{
	char ch[20];
	cin >> ch;
	int len = (int)strlen(ch);

	stack<BiTree> s;// 用來存儲節點地址的棧

	int flag = 1; // 標誌位
	int i = 0;
	if (ch[i] == '.')
		return NULL;

	BiTree temp;

	BiTree root = new BiTNode();
	root->data = ch[i++]; // 此處應該加一個判斷首字符是否爲‘#’的判斷,保證根節點不爲空。
	root->lChild = NULL;
	root->rChild = NULL;

	s.push(root);// 根節點入棧

	while (i < len)
	{

		BiTree pNew = NULL;

		if (flag == 1) // 創建左孩子
		{
			if (ch[i] == '.')
				flag = 2;
			else
			{
				pNew = new BiTNode();
				pNew->data = ch[i];
				pNew->lChild = NULL;
				pNew->rChild = NULL;

				temp = s.top(); // 棧頂元素(取出棧頂元素賦予temp,注意,這不是出棧操作,因爲棧頂指針top沒有變)
				temp->lChild = pNew;
				s.push(pNew); // 棧頂元素的左子樹入棧
				flag = 1;
			}
		}
		else if (flag == 2) // 創建右孩子
		{
			if (ch[i] == '.')
				flag = 3;
			else
			{
				pNew = new BiTNode();
				pNew->data = ch[i];
				pNew->lChild = NULL;
				pNew->rChild = NULL;

				temp = s.top(); // 棧頂元素
				temp->rChild = pNew;
				s.push(pNew); // 棧頂元素的右子樹入棧
				flag = 1;
			}
		}
		else // 左右孩子都已經創建完畢
		{
			temp = s.top();
			s.pop(); // 棧頂元素出棧,並修改棧頂指針
			while (!s.empty() && s.top()->rChild == temp) // 若此時棧中的元素個數仍大於1個,並且剛剛出棧的元素是當前棧頂元素的右子樹,則繼續讓棧頂元素出棧,並修改棧頂指針。
				s.pop();


			flag = 2; // 跳出此while循環時,創建當前棧頂節點的右孩子,此時的i在ch[]中對應的數據剛好就是下一個待使用的數據,所以執行 --i是爲了防止下面的 ++i跳過了該數據(即當前i對應的數據就是我們下一步創建右子樹所應該使用的數據)。
			--i;
		}

		++i;
	}

	return root;
}

int BiTreeDepth(BiTree T) {
	int LD, RD;

	if (T == NULL)
		return 0;
	else {
		LD = BiTreeDepth(T->lChild);
		RD = BiTreeDepth(T->rChild);

		return max(LD, RD) + 1;
	}
}//求樹深度
TElemType Value(BiTree p) {
	return p->data;
}//返回結點的值
void Assign(BiTree p, TElemType value) {
	p->data = value;
}//爲二叉樹某結點賦值
BiTree Location(BiTree T, TElemType e) {
	BiTree p = NULL;

	if (T) {
		if (T->data == e)
			return T;
		else
		{
			if (p = Location(T->lChild, e))
				return p;
			if (p = Location(T->rChild, e))
				return p;
		}
	}

	return p;
}
bool InsertBiTree(BiTree T, TElemType e, BiTree T0, int LR) {
	BiTree p = Location(T, e);

	if (p) {
		T0->rChild = LR ? p->lChild : p->rChild;
		LR ? p->lChild = T0 : p->rChild = T0;

		return true;
	}

	return false;
}//將T0插入樹T,成爲結點e的子樹,LR爲左右子樹標誌,T0只有左子樹
bool DeleteBiTree(BiTree T, TElemType e, int LR) {
	BiTree p = Location(T, e);

	if (p) {
		LR ? ClearBiTree(p->lChild) : ClearBiTree(p->rChild);

		return true;
	}

	return false;
}//刪除e結點的左子樹右子樹,LR爲刪除標誌
void Visit(TElemType e) {
	printf("%c",e);
}
void PreOrderTraverse(BiTree T, void(Visit)(TElemType)) {
	if (T) {
		Visit(T->data);
		PreOrderTraverse(T->lChild, Visit);
		PreOrderTraverse(T->rChild, Visit);
	}
}//先序遍歷
void InOrderTraverse(BiTree T, void(Visit)(TElemType)) {
	if (T) {
		PreOrderTraverse(T->lChild, Visit);
		Visit(T->data);
		PreOrderTraverse(T->rChild, Visit);
	}
}//中序遍歷
void PostOrderTraverse(BiTree T, void(Visit)(TElemType)) {
	if (T) {
		PreOrderTraverse(T->lChild, Visit);
		PreOrderTraverse(T->rChild, Visit);
		Visit(T->data);
	}
}//後序遍歷

/*非遞歸遍歷樹*/
void F_PostOrderTraverse(BiTree &T) {
	stack<BiTree> s;
	BiTree cur, pre = NULL;

	s.push(T);
	while (!s.empty()) {
		cur = s.top();
		if ((cur->lChild == NULL && cur->rChild == NULL) || (pre !=NULL && (pre == cur->lChild || pre == cur->rChild))) {//如果當前節點沒有孩子結點或左右結點均已訪問
			cout << cur->data;
			s.pop();
			pre = cur;
		}
		else
		{
			if (cur->rChild != NULL)
				s.push(cur->rChild);
			if (cur->lChild != NULL)
				s.push(cur->lChild);
		}
	}
}

void LevelOrderTraverse(BiTree T, void(Visit)(TElemType)) {
	BiTree p[100];//數指針數組
	int i, j;

	i = j = 0;
	if (T)
		p[j++] = T;
	while (i < j) {
		Visit(p[i]->data);
		if (p[i]->lChild)
			LevelOrderTraverse(p[i]->lChild, Visit);
		if (p[i]->rChild)
			LevelOrderTraverse(p[i]->rChild, Visit);
		i++;
	}

}//層序遍歷
void PrintBiTree(BiTree &T, void(Visit)(TElemType)) {
	int row, col;
	int i, j, m, l, r;
	BiTNode a[101][101] = {};

	if (T)
	{
		row = BiTreeDepth(T);
		col = pow(2, row) - 1;

		for (i = 1; i <= row - 1; ++i)
		{
			for (j = 1; j <= pow(2, i - 1); j++)
			{//只要二叉樹的深度確定,其結點在二叉數組中的橫縱座標都可以確定 
				m = (2 * j - 1)*pow(2, row - i);//根結點在二叉數組中的列位置 
				l = (4 * j - 3)*pow(2, row - i - 1);//左孩子在二叉數組中的列位置 
				r = (4 * j - 1)*pow(2, row - i - 1);//有孩子在二叉數組中的列位置 

				if (i == 1)
					a[i][m] = *T;
				if (a[i][m].lChild)
					a[i + 1][l] = *(a[i][m].lChild);
				if (a[i][m].rChild)
					a[i + 1][r] = *(a[i][m].rChild);
			}
		}

		for (i = 1; i <= row; ++i)
		{
			for (j = 1; j <= col; ++j)
			{
				if (a[i][j].data)
					printf("%c", a[i][j].data);
				else
					printf(" ");
			}
			printf("\n");
		}
	}
	else
	{
		printf("THIS  IS  A  EMPTY  BINARY  TREE\n");
	}
}

int main()
{
	BiTree T = createBiTree();
	/* ABC..DE.G..F... */
	//CreateBiTree(T);
	printf("%d", BiTreeDepth(T));
	PreOrderTraverse(T,Visit);
	printf("\n");
	PrintBiTree(T, Visit);
	cout << endl;
	F_PostOrderTraverse(T);

	/*
	BiTree p = NULL;
	p = (BiTree)malloc(sizeof(BiTNode));
	p->data = 'P';
	p->lChild = p->rChild = NULL;
	InsertBiTree(T, 'B', p, 1);
	DeleteBiTree(T, 'B', 1);
	printf("\n");
	PrintBiTree(T, Visit);
	*/

    return 0;
}

線索二叉樹

n個結點的二叉樹,有2n個指針域,使用的有n-1個,剩n+1個指針域浪費,可以用來存儲線索。

指向前驅和後繼的指針稱作線索,加上線索的二叉鏈表稱爲線索鏈表,相應的二叉樹稱爲線索二叉樹(Threaded Binary Tree)

將二叉樹以某種次序遍歷使其變爲線索二叉樹的過程稱作是線索化

  

指向前驅和後繼的結點加起來一共是11,在加上指向孩子的指針域,一共20,充分利用了空間。

但爲了清楚地確定lchild域指向的是前驅還是左孩子,需設兩個標誌域【boolean類型】

LTag爲0時指向該結點的左孩子,爲1時指向該結點的前驅

RTag爲0時指向該結點的右孩子,爲1時指向該結點的後繼。

 

/*二叉樹的二叉線索存儲結構的定義*/
typedef enum{Link,Thread} PointerTag;/*Link表示指向左右孩子的指針,
                                        Thread表示指向前驅或後繼的線索*/
typedef struct BiThrNode
{
    TElemType data;/*結點數據*/
    struct BiThrNode *lchild,*rchild;/*左右孩子指針*/
    PointerTag LTag;
    PointerTag RTag;/*左右標誌*/
}BiThrNode,*BiThrTree;

線索化的實質是將二叉鏈表中的空指針改爲指向前驅或後繼的線索的過程。

線索化的過程就是遍歷的過程中修改空指針的過程。

BiThrTree pre;/*全局變量,始終指向剛剛訪問過的結點*/

Status InThreading(BiThrTreee p)
{
    if(p)
    {
        InThreading(p->lchild);//遞歸左子樹線索化
        if(!p->lchild)
        {//沒有左孩子
            p->LTag = Thread;//前驅線索
            p->lchild = pre;
        }
        if(!pre->rchild)
        {//前驅沒有右孩子
            pre->RTag = Thread;//後繼線索
            pre->rchild = p;//前驅右孩子指針指向後繼
        }
        pre = p;//保持pre指向p(下一個)的前驅
        InThread(p->rchild);//遞歸右子樹線索化
    }
}

若二叉樹不爲空,在二叉鏈表上添加一個頭結點,並令其lchild域的指針指向二叉樹的根結點,其rchild域的指針指向中序遍歷的最後一個結點。

若二叉樹爲空,令其lchild域和rchild域的指針都指向頭結點。

這樣做,我們既可以從第一個結點順後繼進行遍歷,也可以從最後一個結點順前驅進行遍歷。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章