數據結構之樹

樹是n個結點的有限集,當n=0時,為空樹。在任意一個非空樹中有且僅有一個特定的稱為根的結點;當n>1時,其余結點可以分為m個互不相交的有限集,每一個集合本身又是一棵樹,稱為根的子樹

數的結點包含一個數據元素&若干個指向其子樹的分支。結點擁有的子樹的數目稱為 結點的度。度為0的結點稱為葉節點或終端結點,度不為0的結點,稱為分支結點,分支結點中除了根節點,其他結點也稱為內部節點。數的度為樹內所有結點的最大值。

結點的層次從根結點開始定義,根為第一層,根的孩子為第二層。樹中結點的最大層次稱為樹的深度或高度。

線性表與樹結構的差異

樹的抽象數據類型ADT


樹的存儲結構
對于樹這種存在一對多的情況,單純使用順序存儲無法滿足其邏輯關系。結合順序存儲和鏈式存儲可以實現對樹的存儲結構的要求。
常見的三種不同的表示法:雙親表示法、孩子表示法(雙親-孩子結合的表示法)、孩子兄弟表示法。


雙親表示法: 通過一定長度的結點數組存儲結點(結點存儲結點數據和雙親下標)
image

雙親表示法中根據結點的parent可以找到其雙親,但是無法找到結點的子結點,除非是遍歷整個樹??梢詫Y點的數據域進行擴展,增加長子索引??梢愿鶕枨罄^續擴展結點數據域。
image

孩子表示法:
孩子表示法為在結點數組中,每個結點形成一個單鏈表的結構,鏈表中的下一個元素為該結點的兄弟。

兄弟表示法
兄弟表示法中,每個結點如果存在長子結點,有且只有一個,而結點緊鄰右側的兄弟若存在,有且只有一個。這樣在結點數組中,每個結點的數據域中有兩個指針,分別指向第一個長子和它右側的兄弟。


這種表示法如果要找到雙親還需要再添加指針域指向雙親,不過這種表示法將復雜樹轉為了二叉樹,這樣可以利用二叉樹的特性和算法來處理相應的操作。

二叉樹
由n個結點構成的有限集,當n=0時,為空樹,當n>0時,由一個根結點和兩顆互不相交的,分別稱為根結點的左子樹和右子樹的二叉樹構成。

二叉樹特點:

  • 每個結點最多只有兩顆子樹,二叉樹中不存在度大于2的結點,可以是兩顆子樹,也可以是一顆或者沒有子樹。
  • 左子樹和右子樹有順序,次序不能任意顛倒。
  • 即使樹中某結點只有一棵子樹,也要區分是左子樹還是右子樹。

二叉樹的五種基本形態:

  • 空二叉樹
  • 只有一個根結點
  • 根結點只有左子樹
  • 根結點只有右子樹
  • 根結點既有左子樹又有右子樹

特殊二叉樹:

  • 斜樹
    所有結點都只有左子樹的二叉樹稱為左斜樹,所有結點都有右子樹的二叉樹稱為右斜樹。斜樹的結點數即為該樹的深度。

  • 滿二叉樹
    一顆二叉樹中,所有分支的結點都存在左子樹和右子樹,并且所有的葉子都在同一層上,這樣的二叉樹稱為滿二叉樹。
    滿二叉樹特點
    葉子只能出現在最下一層
    非葉子的結點的度一定是2 同樣深度的樹中,滿二叉樹的結點個數最多,葉子數最多。

  • 完全二叉樹
    對一顆n各結點的二叉樹按層序編號,若編號為i的結點與對應深度的滿二叉樹中的編號一致,則這樣的二叉樹稱為完全二叉樹。即滿二叉樹一定是完全二叉樹,而完全二叉樹不一定是滿二叉樹。完全二叉樹是滿二叉樹的子集。
    完全二叉樹特點
    葉子結點只能是最下兩層
    最下層的葉子一定集中在左部連續位置
    倒數第二層,若有葉子結點,一定都在右部連續位置
    如果結點的度為1,該結點只有左孩子,不存在只有右子樹的情況
    同樣結點數的二叉樹,完全二叉樹的深度最小

二叉樹的性質

  • 在二叉樹的第i層上至多有2^(i-1)個結點
  • 在深度為k的二叉樹中,結點數最多為2^k - 1
  • 對于任何一個二叉樹,終端結點數N0=N2+1(N2:度為2的結點)
    結點總數:N=N0+N1+N2
    連線總數=N-1(入線角度來算根結點沒有入線)=2N2+N1(出線角度來算)
    對于完全二叉樹,N0=[(N+1)/2]
  • 對于一顆完全二叉樹,樹的深度值為[log2N]+1 (N為結點數,[]為不大于該值的最大整數)
  • 對于一顆有n個結點的完全二叉樹(深度為[log2N]+1)的結點按層序編號,對于任一結點i:
    1.若i=1,則結點i為根結點,無雙親;若i>1,雙親結點的編號為[i/2]
    2.若2i>n,則結點無左孩子,否則左孩子結點為2i
    3.若2i+1>n,則結點無右孩子,否則右孩子結點為2i+1

二叉樹的存儲結構
二叉樹的特殊性可以使用順序結構存儲按層序編號的結點,不存在的結點需要在數組對應編號處空缺。極端情況下,一顆深度為k的右斜樹結點數為k,但需要 2^k - 1個存儲單元。一般順序存儲結構適用于完全二叉樹。
使用鏈式存儲結構,設計一個數據域和兩個指針域的結點鏈表來存儲二叉樹,這樣的鏈表叫做二叉鏈表,如有必要可以再添加指向雙親的指針域,為三叉鏈表。

二叉樹的遍歷
二叉樹的遍歷是指從根結點出發,按照某種次序依次訪問二叉樹中的所有結點,使得每個結點被訪問一次且僅被訪問一次。
如果限制遍歷方向從左向右,主要分為四種遍歷方法:


1.前根序遍歷
規則:二叉樹為空,則空操作返回,否則先遍歷根結點,然后遍歷左子樹,最后遍歷右子樹。
ABDHECFG
2.中根序遍歷
規則:二叉樹為空,則空操作返回,否則先遍歷左子樹,然后遍歷根結點,最后遍歷右子樹。
HDBEAFCG
3.后根序遍歷
規則:二叉樹為空,則空操作返回,否則先遍歷左子樹,然后遍歷右子樹,最后遍歷根結點。
HDEBFCGA
4.層序遍歷
規則:二叉樹為空,則空操作返回,否則按照層序從左至右依次訪問結點。

二叉樹的定義和遍歷采用遞歸的方式 二叉樹鏈表結構及遍歷實現:

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
 
#define MAXSIZE 100
 
typedef int Status;
 
//用于構造二叉樹的全局變量
int index = 1;
typedef char String[24];
String str; //字符數組的別名    
 
Status StrAssign(String T, char *chars) {
int i;
if (strlen(chars) > MAXSIZE)
    return ERROR;
else {
    T[0] = strlen(chars);
    for (i = 1; i <= T[0]; i++)
        T[i] = *(chars + i - 1);
    return OK;
}
}
 
typedef char TElemType;
TElemType Nil = ' ';
 
Status visit(TElemType e) {
printf("%c", e);
return OK;
}
 
typedef struct BiTNode {
TElemType data;
struct BiTNode *lchild, *rchild;
}BiTNode, *BiTree;
 
Status InitBiTree(BiTree *T) {
*T = NULL;
return OK;
}
 
//銷毀二叉樹
void DestroyBiTree(BiTree *T) {
if (*T) {
    if ((*T)->lchild)
        DestroyBiTree(&(*T)->lchild);
    if ((*T)->rchild)
        DestroyBiTree(&(*T)->rchild);
    free(*T);//釋放結點空間
    *T = NULL;
}
}
 
void CreatBiTree(BiTree *T) {
TElemType ch;
ch = str[index++];
if (ch == '#')
    *T = NULL;
else {
    *T = (BiTree)malloc(sizeof(BiTNode));
    if (!T)
        exit(OVERFLOW);
    //使用前根序的次序創建二叉樹
    (*T)->data = ch;
    CreatBiTree(&(*T)->lchild);   //構造左子樹
    CreatBiTree(&(*T)->rchild);   //構造右子樹
}
}
 
Status BiTreeEmpty(BiTree T) {
if (T)
    return FALSE;
else
    return TRUE;
}
 
int BiTreeDepth(BiTree T) {
int i, j;
if (!T)
    return 0;
if (T->lchild)
    i = BiTreeDepth(T->lchild);
else
    i = 0;
if (T->rchild)
    j = BiTreeDepth(T->rchild);
else
    j = 0;
return i > j ? i + 1 : j + 1;
}
 
TElemType Root(BiTree T) {
if (BiTreeEmpty(T))
    return Nil;
else
    return T->data;
}
 
TElemType Value(BiTree p) {
return p->data;
}
 
void Assign(BiTree p, TElemType e) {
p->data = e;
}
 
 
//二叉樹前根序遍歷
void PreOrderTraverse(BiTree T) {
if (T == NULL)
    return;
printf("%c", T->data);                    //先顯示結點數據
PreOrderTraverse(T->lchild);       //再遍歷左子樹
PreOrderTraverse(T->rchild);      //再遍歷右子樹
}
 
//二叉樹中根序遍歷
void InOrderTraverse(BiTree T) {
if (T == NULL)
    return;
InOrderTraverse(T->lchild);       //先遍歷左子樹
printf("%c", T->data);                //再顯示結點數據
InOrderTraverse(T->rchild);      //再遍歷右子樹
}
 
//二叉樹后根序遍歷
void PostOrderTraverse(BiTree T) {
if (T == NULL)
    return;
PostOrderTraverse(T->lchild);  //先遍歷左子樹
PostOrderTraverse(T->rchild);  //再遍歷右子樹
printf("%c", T->data);                //顯示結點數據
}
 
 
int main()
{
int i;
BiTree T;
TElemType e1;
InitBiTree(&T);
 
StrAssign(str, "ABDH#K###E##CFI###G#J##");
 
CreatBiTree(&T);
 
printf("構造空二叉樹后,樹空否?%d(0:否,1:是) 樹的深度=%d\n", BiTreeEmpty(T), BiTreeDepth(T));
e1 = Root(T);
printf("二叉樹的根結點:%c\n",e1);
 
printf("二叉樹前根序排列:\n");
PreOrderTraverse(T);
printf("\n二叉樹中根序排列:\n");
InOrderTraverse(T);
printf("\n二叉樹前跟序排列:\n");
PostOrderTraverse(T);
 
getchar();
return 0;
}

二叉樹遍歷性質

已知前根序和中根序,可以唯一確定一顆二叉樹
已知后根序和中根序,可以唯一確定一顆二叉樹
線索二叉樹
利用二叉鏈表中的空指針域存放指向結點在某種次序下的前驅和后繼結點的地址,將指向前驅和后繼的指針稱為線索,加上線索的二叉鏈表稱為線索鏈表,相應的二叉樹稱為線索二叉樹。
線索二叉樹中需要解決的一個問題是,如何知道一個結點的左指針域是指向其左孩子還是指向該結點的前驅,或者右指針域指向其右孩子還是該結點的后繼,因此線索二叉樹需要對原結點添加兩個標志位,ltag和rtag,標志位只存儲0和1,0表示指向其對應的孩子,1表示為對應前驅或后繼。

線索二叉樹實現:

typedef int Status; /* Status是函數的類型,其值是函數結果狀態代碼,如OK等 */
typedef char TElemType;
typedef enum {Link,Thread} PointerTag;  /* Link==0表示指向左右孩子指針, */
                                    /* Thread==1表示指向前驅或后繼的線索 */
typedef  struct BiThrNode   /* 二叉線索存儲結點結構 */
{
TElemType data; /* 結點數據 */
struct BiThrNode *lchild, *rchild;  /* 左右孩子指針 */
PointerTag LTag;
PointerTag RTag;        /* 左右標志 */
} BiThrNode, *BiThrTree;
 
TElemType Nil='#'; /* 字符型以空格符為空 */
 
Status visit(TElemType e)
{
printf("%c ",e);
return OK;
}
 
/* 按前序輸入二叉線索樹中結點的值,構造二叉線索樹T */
/* 0(整型)/空格(字符型)表示空結點 */
Status CreateBiThrTree(BiThrTree *T)
{ 
TElemType h;
scanf("%c",&h);
 
if(h==Nil)
    *T=NULL;
else
{
    *T=(BiThrTree)malloc(sizeof(BiThrNode));
    if(!*T)
        exit(OVERFLOW);
    (*T)->data=h; /* 生成根結點(前序) */
    CreateBiThrTree(&(*T)->lchild); /* 遞歸構造左子樹 */
    if((*T)->lchild) /* 有左孩子 */
        (*T)->LTag=Link;
    CreateBiThrTree(&(*T)->rchild); /* 遞歸構造右子樹 */
    if((*T)->rchild) /* 有右孩子 */
        (*T)->RTag=Link;
}
return OK;
}
 
BiThrTree pre; /* 全局變量,始終指向剛剛訪問過的結點 */
/* 中序遍歷進行中序線索化 */
void InThreading(BiThrTree p)
{ 
if(p)
{
    InThreading(p->lchild); /* 遞歸左子樹線索化 */
    if(!p->lchild) /* 沒有左孩子 */
    {
        p->LTag=Thread; /* 前驅線索 */
        p->lchild=pre; /* 左孩子指針指向前驅 */
    }
    if(!pre->rchild) /* 前驅沒有右孩子 */
    {
        pre->RTag=Thread; /* 后繼線索 */
        pre->rchild=p; /* 前驅右孩子指針指向后繼(當前結點p) */
    }
    pre=p; /* 保持pre指向p的前驅 */
    InThreading(p->rchild); /* 遞歸右子樹線索化 */
}
}
 
/* 中序遍歷二叉樹T,并將其中序線索化,Thrt指向頭結點 */
Status InOrderThreading(BiThrTree *Thrt,BiThrTree T)
{ 
*Thrt=(BiThrTree)malloc(sizeof(BiThrNode));
if(!*Thrt)
    exit(OVERFLOW);
(*Thrt)->LTag=Link; /* 建頭結點 */
(*Thrt)->RTag=Thread;
(*Thrt)->rchild=(*Thrt); /* 右指針回指 */
if(!T) /* 若二叉樹空,則左指針回指 */
    (*Thrt)->lchild=*Thrt;
else
{
    (*Thrt)->lchild=T;
    pre=(*Thrt);
    InThreading(T); /* 中序遍歷進行中序線索化 */
    pre->rchild=*Thrt;
    pre->RTag=Thread; /* 最后一個結點線索化 */
    (*Thrt)->rchild=pre;
}
return OK;
}
 
/* 中序遍歷二叉線索樹T(頭結點)的非遞歸算法 */
Status InOrderTraverse_Thr(BiThrTree T)
{ 
BiThrTree p;
p=T->lchild; /* p指向根結點 */
while(p!=T)
{ /* 空樹或遍歷結束時,p==T */
    while(p->LTag==Link)
        p=p->lchild;
    if(!visit(p->data)) /* 訪問其左子樹為空的結點 */
        return ERROR;
    while(p->RTag==Thread&&p->rchild!=T)
    {
        p=p->rchild;
        visit(p->data); /* 訪問后繼結點 */
    }
    p=p->rchild;
}
return OK;
 }  
 
 int main()
{
BiThrTree H,T;
printf("請按前序輸入二叉樹(如:'ABDH##I##EJ###CF##G##')\n");
CreateBiThrTree(&T); /* 按前序產生二叉樹 */
InOrderThreading(&H,T); /* 中序遍歷,并中序線索化二叉樹 */
printf("中序遍歷(輸出)二叉線索樹:\n");
InOrderTraverse_Thr(H); /* 中序遍歷(輸出)二叉線索樹 */
printf("\n");
 
return 0;
}   

如果所用二叉樹需經常遍歷或查找結點時需要某種遍歷序列中的前驅和后繼,可以采用線索二叉鏈表的存儲結構

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容