數(shù)據(jù)結(jié)構(gòu)(樹(shù)和二叉樹(shù))

1.樹(shù)和二叉樹(shù)的定義

(1) 樹(shù)的定義

樹(shù)是n (n≥0) 個(gè)結(jié)點(diǎn)的有限集。 n=0 時(shí)稱(chēng)為空樹(shù)。在任意一棵非空樹(shù)中:

  • 有且僅有一個(gè)特定的稱(chēng)為根(Root) 的結(jié)點(diǎn);
  • 當(dāng)n>1 時(shí),除了根結(jié)點(diǎn)以外其余結(jié)點(diǎn)可分為m(m>0) 個(gè)互不相交的有限集T1、 T2、 ……、 Tm, 其中每一個(gè)集合本身又是一棵樹(shù),并且稱(chēng)為根的子樹(shù)(SubTree)。

(2) 樹(shù)的基本術(shù)語(yǔ)

結(jié)點(diǎn):樹(shù)中的一個(gè)獨(dú)立單元。
結(jié)點(diǎn)的度:結(jié)點(diǎn)擁有的子樹(shù)數(shù)稱(chēng)為結(jié)點(diǎn)的度。
樹(shù)的度:樹(shù)的度是樹(shù)內(nèi)各結(jié)點(diǎn)度的最大值。
葉子:度為0的結(jié)點(diǎn)稱(chēng)為葉子或者終端結(jié)點(diǎn)。
非終端結(jié)點(diǎn):度不為0的結(jié)點(diǎn)稱(chēng)為非終端結(jié)點(diǎn)或者分支結(jié)點(diǎn)。除根結(jié)點(diǎn)以外,非終端結(jié)點(diǎn)也稱(chēng)為內(nèi)部結(jié)點(diǎn)。
雙親和孩子:結(jié)點(diǎn)的子數(shù)的根稱(chēng)為該結(jié)點(diǎn)的孩子,相應(yīng)地,該結(jié)點(diǎn)稱(chēng)為孩子地雙親。
兄弟:同一個(gè)雙親地孩子之間互稱(chēng)為兄弟。
祖先:從根到該結(jié)點(diǎn)所經(jīng)分支上所有結(jié)點(diǎn)。
子孫:以某結(jié)點(diǎn)為根地子數(shù)中任一結(jié)點(diǎn)都稱(chēng)為該結(jié)點(diǎn)地子孫。
層次:結(jié)點(diǎn)地層次從根開(kāi)始定義起,根稱(chēng)為第一層,根地孩子為第二層。樹(shù)中任一結(jié)點(diǎn)層次等于其雙親結(jié)點(diǎn)地層次加1。
堂兄弟:雙親在同一層次地結(jié)點(diǎn)互為堂兄弟。
樹(shù)地深度:樹(shù)中結(jié)點(diǎn)地最大層次稱(chēng)為樹(shù)地深度或高度。
有序樹(shù)和無(wú)序樹(shù):如果將樹(shù)中結(jié)點(diǎn)地各個(gè)子樹(shù)看成從左至右是有次序地,不能互換,則稱(chēng)為有序樹(shù),否則稱(chēng)為無(wú)序樹(shù)。在有序樹(shù)中最左邊地子樹(shù)的根稱(chēng)為第一個(gè)孩子,最右邊的稱(chēng)為最后一個(gè)孩子。
森林:森林(Forest) 是m(m≥0) 棵互不相交的樹(shù)的集合。對(duì)樹(shù)中每個(gè)結(jié)點(diǎn)而言,其子樹(shù)的集合即為森林。
二叉樹(shù):是n(n≥0)個(gè)結(jié)點(diǎn)的有限集合,該集合或者為空集(稱(chēng)為空二叉樹(shù)),或者由一個(gè)根結(jié)點(diǎn)和兩棵互不相交的、分別稱(chēng)為根結(jié)點(diǎn)的左子樹(shù)和右子樹(shù)的二叉樹(shù)組成。


(3) 二叉樹(shù)的定義

二叉樹(shù)是n個(gè)結(jié)點(diǎn)所構(gòu)成的集合,它或?yàn)榭諛?shù)(n=0),或?yàn)榉强諛?shù),對(duì)于非空樹(shù)T:

  • 有且只有一個(gè)稱(chēng)為根的結(jié)點(diǎn)。
  • 除根結(jié)點(diǎn)以外的其余結(jié)點(diǎn)分為兩個(gè)互不相交的子集T1和T2,分別稱(chēng)為T(mén)的左子樹(shù)和右子樹(shù),且T1和T2本身又是二叉樹(shù)。

二叉樹(shù)和樹(shù)的區(qū)別:
* 二叉樹(shù)每個(gè)結(jié)點(diǎn)至多只有兩顆子樹(shù)。
* 二叉樹(shù)的子樹(shù)有左右之分,其次序不能任意顛倒。

2. 二叉樹(shù)的性質(zhì)和存儲(chǔ)結(jié)構(gòu)

(1) 二叉樹(shù)的性質(zhì)

性質(zhì)1: 在二叉樹(shù)上的第i層至多有2^(i-1)個(gè)結(jié)點(diǎn)。(i>=1)
性質(zhì)2: 深度為k 的二叉樹(shù)至多有2^k -1個(gè)結(jié)點(diǎn)(k>=1)。
性質(zhì)3:對(duì)于任何一棵二叉樹(shù)T,如果其終端結(jié)點(diǎn)數(shù)為n0,度為2的結(jié)點(diǎn)數(shù)為n2,則n0=n2+1。
證明:
二叉樹(shù)結(jié)點(diǎn)總數(shù):n = n0+n1+n2;
除了根結(jié)點(diǎn)外,其余結(jié)點(diǎn)都有一個(gè)分支進(jìn)入,設(shè)B為分支總數(shù),則n = B + 1;由于這些分支是由度為1或2的結(jié)點(diǎn)射出,所以B=n1+2*n2
于是:n = n1+2*n2+1 => n0 = n2+1

滿二叉樹(shù):深度為k且含有2^k - 1個(gè)結(jié)點(diǎn)的二叉樹(shù)。
滿二叉樹(shù)的特點(diǎn):每一層上的結(jié)點(diǎn)數(shù)都是最大結(jié)點(diǎn)數(shù),即每一層i的結(jié)點(diǎn)數(shù)都具有最大值2^(i-1)。
對(duì)滿二叉樹(shù)結(jié)點(diǎn)進(jìn)行編號(hào),約定編號(hào)從根結(jié)點(diǎn)起,自上而下,自左至右。

完全二叉樹(shù):深度為k對(duì),由n個(gè)結(jié)點(diǎn)的二叉樹(shù),當(dāng)且僅當(dāng)每一個(gè)結(jié)點(diǎn)都與深度為k的滿二叉樹(shù)中編號(hào)1至n的結(jié)點(diǎn)一一對(duì)應(yīng),稱(chēng)之為完全二叉樹(shù)。
完全二叉樹(shù)的特點(diǎn):
* 葉子結(jié)點(diǎn)只能在層次最大的兩層上出現(xiàn)。
* 對(duì)任一結(jié)點(diǎn),若其右分支下的子孫的最大層次為l,則其左分支下的子孫的最大層次必為l或l+1。


(2) 二叉樹(shù)的存儲(chǔ)結(jié)構(gòu)

1.順序存儲(chǔ)結(jié)構(gòu):使用一組地址連續(xù)的存儲(chǔ)單元來(lái)存儲(chǔ)數(shù)據(jù)元素,將二叉樹(shù)的結(jié)點(diǎn)依照自上而下,自左至右存儲(chǔ)結(jié)點(diǎn)元素。

#define MAXSIZE 100;
typedef TElemType SqBiTree[MAXSIZE];//0號(hào)單元存儲(chǔ)根結(jié)點(diǎn)
SqBiTree bt;

2.鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu):結(jié)點(diǎn)包含3個(gè)域:數(shù)據(jù)域,左右指針。

typedef struct BiTNode {
    TElemType data;
    Struct BiTree *lchild,*rchild;
}BiTNode,*BiTree;

3. 遍歷二叉樹(shù)和線索二叉樹(shù)

(1) 遍歷二叉樹(shù)

遍歷二叉樹(shù)是指按某條搜索路徑巡訪樹(shù)中每個(gè)結(jié)點(diǎn),使的每個(gè)結(jié)點(diǎn)均被訪問(wèn)一次,而且僅被訪問(wèn)一次。遍歷的實(shí)質(zhì)是對(duì)二叉樹(shù)進(jìn)行線性化的過(guò)程。

  1. 前序遍歷:規(guī)則是若二叉樹(shù)為空,則空操作返回,否則先訪問(wèn)根結(jié)點(diǎn),然后前序遍歷左子樹(shù), 再前序遍歷右子樹(shù)。如下最左圖,遍歷的順序?yàn)?ABDGHCEIF。
void PreOrderTraverse(BiTree T){    
    if(T)
    {   
        printf("%c",T->data);/* 顯示結(jié)點(diǎn)數(shù)據(jù),可以更改為其它對(duì)結(jié)點(diǎn)操作 */  
        PreOrderTraverse(T->lchild); /* 再先序遍歷左子樹(shù) */    
        PreOrderTraverse(T->rchild); /* 最后先序遍歷右子樹(shù) */
    }
}
  1. 中序遍歷:規(guī)則是若樹(shù)為空,則空操作返回,否則從根結(jié)點(diǎn)開(kāi)始(注意并不是先訪問(wèn)根結(jié)點(diǎn)),中序遍歷根結(jié)點(diǎn)的左子樹(shù),然后是訪問(wèn)根結(jié)點(diǎn),最后中序遍歷右子樹(shù)。 如下中左圖,遍歷的順序?yàn)? GDHBAEICF。
void InOrderTraverse(BiTree T){     
    if(T)
    {   
        InOrderTraverse(T->lchild); /* 中序遍歷左子樹(shù) */
        printf("%c",T->data);/* 訪問(wèn)根結(jié)點(diǎn) */    
               
        InOrderTraverse(T->rchild); /* 中序遍歷右子樹(shù) */
    }
}
  1. 后序遍歷:規(guī)則是若樹(shù)為空,則空操作返回,否則從左到右先葉子后結(jié)點(diǎn)的方式遍歷訪問(wèn)左右子樹(shù),最后是訪問(wèn)根結(jié)點(diǎn)。 如下中右圖,遍歷的順序?yàn)? GHDBIEFCA。
void PostOrderTraverse(BiTree T){   
    if(T)
    {   
        InOrderTraverse(T->lchild); /* 后序遍歷左子樹(shù) */          
        InOrderTraverse(T->rchild); /* 后序遍歷右子樹(shù) */
        printf("%c",T->data);/* 訪問(wèn)根結(jié)點(diǎn) */    
    }
}
  1. 層序遍歷:規(guī)則是若樹(shù)為空, 則空操作返回,否則從樹(shù)的第一層,也就是根結(jié)點(diǎn)開(kāi)始訪問(wèn),從上而下逐層遍歷,在同一層中, 按從左到右的頗用才結(jié)點(diǎn)逐個(gè)訪問(wèn)。如下最右圖所示,遍歷的順序?yàn)?ABCDEFGHI。

1. 根據(jù)遍歷順序確定二叉樹(shù)

己知前序遍歷序列和中序遍歷序列,可以唯一確定一棵二叉樹(shù);已知后序遍歷序列和中序遍歷序列,可以唯一確定一棵二叉樹(shù);但已知前序和后序遍歷,是不能確定一棵二叉樹(shù)的。



2. 先序遍歷的順序建立二叉樹(shù)

(1) 掃描字符序列,讀入字符ch。
(2) 如果ch是一個(gè)“#”字符,則表明該二叉樹(shù)為空樹(shù),即T為NULL,否則執(zhí)行以下操作:

  • 申請(qǐng)一個(gè)結(jié)點(diǎn)空間T。
  • 將ch賦給T->data。
  • 遞歸創(chuàng)建T的左子樹(shù)。
  • 遞歸創(chuàng)建T的右子樹(shù)。
void CreateBiTree(BiTree &T)
{//先按次序輸入二叉樹(shù)中結(jié)點(diǎn)的值(一個(gè)字符),創(chuàng)建二叉鏈表示的二叉樹(shù)T
    cin>>ch;
    if(ch=='#')  T=NULL;    //  遞歸結(jié)束,建空樹(shù)
    else    //遞歸創(chuàng)建二叉樹(shù)
    {       
        T = new BiTNode; /* 生成根結(jié)點(diǎn) */    
        T->data=ch; /* 根結(jié)點(diǎn)數(shù)據(jù)域?yàn)閏h */     
        CreateBiTree(T->lchild); /* 遞歸創(chuàng)建左子樹(shù) */
        CreateBiTree(T->rchild); /* 遞歸創(chuàng)建右子樹(shù) */  
      } 
}
 

3. 復(fù)制二叉樹(shù)

如果是空樹(shù),遞歸結(jié)束,否則執(zhí)行以下操作:

  • 申請(qǐng)一個(gè)新的結(jié)點(diǎn)空間,復(fù)制根結(jié)點(diǎn)。
  • 遞歸復(fù)制左子樹(shù)。
  • 遞歸復(fù)制右子樹(shù)。
 void Copy(BiTree T,BiTree &NewT)
{//復(fù)制一棵和T完全相同的二叉樹(shù)
    if(T == NULL)
    {
          NewT = NULL;
          return;
    }
    else
    {
          NewT = new BiTNode; 
          New->data = T->data; //復(fù)制根結(jié)點(diǎn)
          Copy(T->lchild,NewT->lchild);//遞歸復(fù)制左子樹(shù)
          copy(T->rchild,NewT->rchild);//遞歸復(fù)制右子樹(shù)
    }
}

4. 計(jì)算二叉樹(shù)的深度

如果是空樹(shù),遞歸結(jié)束,深度為0,否則執(zhí)行以下操作:

  • 遞歸計(jì)算左子樹(shù)的深度計(jì)為m。
  • 遞歸計(jì)算右子樹(shù)的深度為n。
  • 如果m大于n,二叉樹(shù)的深度為m+1,否則為n+1;

int Depth(BiTree T)
{
    if(T == NULL) return 0;
    else
    {
          m = Depth(T->lchild);
          n  = Depyh(T->rchild);
          if(m>n) return(m+1);
          else  return (n+1);
     }
}
5. 統(tǒng)計(jì)二叉樹(shù)中結(jié)點(diǎn)的個(gè)數(shù)
int NodeCount(BiTree T)
{
      if(T == NULL) return 0;
      else return NodeCount(T->lchild)+NodeCount(T->rchild)+1;
}

(2) 線索二叉樹(shù)

1. 線索二叉樹(shù)的基本概念

對(duì)于一個(gè)有n 個(gè)結(jié)點(diǎn)的二叉鏈表,每個(gè)結(jié)點(diǎn)有指向左右孩子的兩個(gè)指針域,所以一共是2n個(gè)指針域。而n個(gè)結(jié)點(diǎn)的二叉樹(shù)一共有n-1 條分支線數(shù),也就是說(shuō),其實(shí)是存在2n-(n-1)=n+1個(gè)空指針域。可以考慮利用那些空地址,存放指向結(jié)點(diǎn)在某種遍歷次序下的前驅(qū)和后繼結(jié)點(diǎn)的地址。這種指向前驅(qū)和后繼的指針?lè)Q為線索,加上線索的二叉鏈表稱(chēng)為線索鏈表,相應(yīng)的二叉樹(shù)就稱(chēng)為線索二叉樹(shù)。線索二叉樹(shù)等于是把一棵二叉樹(shù)轉(zhuǎn)變成一個(gè)雙向鏈表。這樣對(duì)我們插入刪除結(jié)點(diǎn)、查找某個(gè)結(jié)點(diǎn)帶來(lái)了方便。所以我們對(duì)二叉樹(shù)以某種次序遍歷使其變?yōu)榫€索二叉樹(shù)的過(guò)程稱(chēng)作是線索化。

若結(jié)點(diǎn)有左子樹(shù),則其lchild域指示其左孩子,否則令lchild指示其前驅(qū),若結(jié)點(diǎn)有右子樹(shù),則其rchild域指示其右孩子,否則令rchild域指示其后繼。為了避免混淆,需要改變結(jié)點(diǎn)結(jié)構(gòu),增加兩個(gè)標(biāo)志域,LTag、RTag。
LTag:
0->lchild域指示結(jié)點(diǎn)的左孩子
1-> lchild域指示結(jié)點(diǎn)的前驅(qū)

RTag:
0->Rchild域指示結(jié)點(diǎn)的右孩子
1-> Rchild域指示結(jié)點(diǎn)的后繼

//二叉樹(shù)的二叉線索存儲(chǔ)結(jié)構(gòu)
typedef struct BithrNode
{
    TElemType data;
    struct BiThrNode *lchid,*rchild;//左右孩子指針
    int LTag,RTag//左右標(biāo)志
}BiThrNode,*BiThrTree;

2. 構(gòu)造線索二叉樹(shù)

由于線索二叉樹(shù)構(gòu)造的實(shí)質(zhì)是將二叉鏈表中的空指針指向前驅(qū)或后繼的線索,而前驅(qū)或后繼的信息只有在遍歷時(shí)才能得到,因此線索化的過(guò)程即為在遍歷過(guò)程中修改空指針的過(guò)程。

為了紀(jì)下遍歷過(guò)程中訪問(wèn)結(jié)點(diǎn)的先后關(guān)系,附設(shè)一個(gè)指針pre始終指向剛剛訪問(wèn)過(guò)的結(jié)點(diǎn),而指針p指向當(dāng)前訪問(wèn)的結(jié)點(diǎn),由此紀(jì)錄遍歷過(guò)程中訪問(wèn)結(jié)點(diǎn)的先后關(guān)系。

以p結(jié)點(diǎn)為根的子樹(shù)中序線索化

  1. 如果p非空,左子樹(shù)遞歸線索化。
  2. 如果p的左孩子為空,則p加上左線索,將其LTag置為1,讓p的左孩子指針指向pre(前驅(qū)),否則將p的LTag置為0。
  3. 如果pre的右孩子為空,則pre加上右線索,將其RTag置為1,讓pre的右孩子指向p(后繼),否則將pre的RTag置為0。
  4. 將pre指向剛訪問(wèn)過(guò)的結(jié)點(diǎn)p,即pre = p 。
  5. 右子樹(shù)遞歸線索化。
void InThreading(BiThrTree p)
{//pre是全局變量,初始化時(shí)其右孩子指針為空,便于在樹(shù)的左點(diǎn)開(kāi)始建線索。
    InThreading(p->lchild); // 左子樹(shù)遞歸線索化
    if(!p->lchild)//p的左孩子為空
    {
        p->LTag = 1;//給p加上左線索
        p->lchild = pre;//p的左孩子指針指向pre(前驅(qū))
    }
    else p->LTag = 0;
    if(!pre->rchild) //pre的右孩子為空
    {
          pre->RTag =1;//給pre加上右線索
          pre->rchild = p;//pre的右孩子指針指向p(后繼)
    }
    else pre->RTag = 0;
    pre = p;//保持pre指向p的前驅(qū)
    InThreading(p->rchild);//右子樹(shù)遞歸線索化
}

帶頭結(jié)點(diǎn)的二叉樹(shù)中序線索化

void InOrderThreading(BiThrTree &Thrt, BiThrTree T)
{    //中序遍歷二叉樹(shù)T,并將其中序線索化,Thrt指向頭結(jié)點(diǎn)
    Thrt =  new BiThrNode; //建頭結(jié)點(diǎn)
    Thrt->LTag = 0; //頭結(jié)點(diǎn)有左孩子,若樹(shù)非空,則其左孩子為樹(shù)根
    Thrt->RTag = 1; //頭結(jié)點(diǎn)的右孩子指針為右線索
    Thrt->rchild = Thrt;    //初始化時(shí)右指針指向自己
    if (!T)     Thrt->lchild = Thrt;    //若樹(shù)非空,左指針也指向自己
    else    
    {       
          Thrt->lchild = T;     pre = Thrt;//頭結(jié)點(diǎn)的左孩子指向根,pre初始值指向頭結(jié)點(diǎn)
          InThreading(T);//以T為根的二叉樹(shù)進(jìn)行中序線索化      
          pre->rchild = Thrt;   //結(jié)束后,pre為最右結(jié)點(diǎn),pre的右線索指向頭結(jié)點(diǎn) 
          pre->RTag = 1;        
          Thrt->rchild = pre;   //頭結(jié)點(diǎn)的右線索指向pre
      } 
 }

 

3. 遍歷線索二叉樹(shù)




遍歷中序線索二叉樹(shù)

  1. 指針p指向根結(jié)點(diǎn)
  2. p為非空樹(shù)或遍歷未結(jié)束,循環(huán)執(zhí)行以下操作:
  • 沿左孩子向下,到達(dá)最左下結(jié)點(diǎn)*p,它是中序的第一個(gè)結(jié)點(diǎn)。
  • 訪問(wèn)*p。
  • 沿右線索反復(fù)查找當(dāng)前結(jié)點(diǎn)*p的后繼結(jié)點(diǎn)并訪問(wèn)后繼結(jié)點(diǎn),直至右線索為0或者遍歷結(jié)束。
  • 轉(zhuǎn)向p的右子樹(shù)。
void InOrderTraverse_Thr(BiThrTree T)
{//T指向頭結(jié)點(diǎn),頭結(jié)點(diǎn)的左鏈lchild指向根結(jié)點(diǎn)。
//中序遍歷二叉線索樹(shù)T的非遞歸算法,對(duì)每個(gè)數(shù)據(jù)元素直接輸出。
    BiThrTree p;    
    p = T->lchild;   //p指向根結(jié)點(diǎn)
    while (p != T)  //空樹(shù)或遍歷結(jié)束,p == T
    {       
         while (p->LTag == 0)  p = p->lchild;   //沿左孩子向下    
          cout<<p->data; //訪問(wèn)其左子樹(shù)為空的結(jié)點(diǎn)      
          while (p->RTag == 1 && p->rchild != T)        
          {         
              p = p->rchild;cout<<p->data;//沿右線索訪問(wèn)后繼結(jié)點(diǎn)  
           }        
            p = p->rchild;  //轉(zhuǎn)向右子樹(shù)
        }   
}

4. 樹(shù)和森林

(1) 樹(shù)的存儲(chǔ)結(jié)構(gòu)

1. 雙親表示法

假設(shè)以一組連續(xù)空間存儲(chǔ)樹(shù)的結(jié)點(diǎn),同時(shí)在每個(gè)結(jié)點(diǎn)中,附設(shè)一個(gè)指示器指示其雙親結(jié)點(diǎn)到鏈表中的位置。也就是說(shuō),每個(gè)結(jié)點(diǎn)除了知道自己是誰(shuí)以外,還知道它的雙親在哪里。結(jié)點(diǎn)結(jié)構(gòu)為表所示:



其中data是數(shù)據(jù)域,存儲(chǔ)結(jié)點(diǎn)的數(shù)據(jù)信息。而parent是指針域,存儲(chǔ)該結(jié)點(diǎn)的雙親在數(shù)組中的下標(biāo)。

#define MAX_TREE_SIZE 100 
typedef int TElemType;  /*樹(shù)結(jié)點(diǎn)的數(shù)據(jù)類(lèi)型,目前暫定為整型*/
typedef struct PTNode   /*結(jié)點(diǎn)結(jié)構(gòu)*/
{   
    TElemType data;     /*結(jié)點(diǎn)數(shù)據(jù)*/    
    int parent;         /*雙親位置*/
}PTNode;
typedef struct{ 
    PTNode nodes[MAX_TREE_SIZE];    /*結(jié)點(diǎn)數(shù)組*/    
    int r,n;                        /*根的位置和結(jié)點(diǎn)數(shù)*/
}PTree

根結(jié)點(diǎn)因?yàn)闆](méi)有雙親,我們約定根結(jié)點(diǎn)的位置域設(shè)置為-1,則每個(gè)結(jié)點(diǎn)都存有其雙親的位置。


這樣的結(jié)構(gòu)容易根據(jù)結(jié)點(diǎn)的parent指針找到它的雙親結(jié)點(diǎn),時(shí)間復(fù)雜度為O(1),直到parent為-1時(shí),表示找到了樹(shù)結(jié)點(diǎn)的根。但不易找到結(jié)點(diǎn)的孩子結(jié)點(diǎn),除非遍歷整個(gè)結(jié)構(gòu),如上圖,要找到E的孩子J,必須遍歷結(jié)點(diǎn),找到parent為E的下標(biāo)4,才能找到E的孩子J。

2. 孩子表示法

由于樹(shù)中每個(gè)結(jié)點(diǎn)可能有多棵子樹(shù),可以考慮用多重鏈表,即每個(gè)結(jié)點(diǎn)有多個(gè)指針域,其中每個(gè)指針指向一棵子樹(shù)的根結(jié)點(diǎn),這種方法叫多重鏈表表示法。不過(guò),樹(shù)的每個(gè)結(jié)點(diǎn)的度,也就是它的孩子個(gè)數(shù)是不同的。 所以可以設(shè)計(jì)兩種方案來(lái)解決。

方案一:
一種是指針域的個(gè)數(shù)就等于樹(shù)的度,復(fù)習(xí)一下,樹(shù)的度是樹(shù)各個(gè)結(jié)點(diǎn)度的最大值。其結(jié)構(gòu)如圖:



其中data是數(shù)據(jù)域。child1到childd是指針域,用來(lái)指向該結(jié)點(diǎn)的孩子結(jié)點(diǎn)。上述度為3的樹(shù)若用此法表示,指針域的個(gè)數(shù)為3,如圖。



這種方法對(duì)于樹(shù)中各結(jié)點(diǎn)的度相差很大時(shí),容易浪費(fèi)空間,因?yàn)橛泻芏嗟慕Y(jié)點(diǎn),它的指針域都是空的。不過(guò)如果樹(shù)的各結(jié)點(diǎn)度相差很小時(shí),意味著開(kāi)辟的空間被充分利用了,這時(shí)存儲(chǔ)結(jié)構(gòu)的缺點(diǎn)反而變成了優(yōu)點(diǎn)。

方案二:
第二種方案每個(gè)結(jié)點(diǎn)指針域的個(gè)數(shù)等于該結(jié)點(diǎn)的度,我們專(zhuān)門(mén)取一個(gè)位置來(lái)存儲(chǔ)結(jié)點(diǎn)指針域的個(gè)數(shù),其結(jié)構(gòu)如下圖:



其中data為數(shù)據(jù)域,degree 為度域,也就是存儲(chǔ)該結(jié)點(diǎn)的孩子結(jié)點(diǎn)的個(gè)數(shù),child1到childd為指針域,指向該結(jié)點(diǎn)的各個(gè)孩子的結(jié)點(diǎn)。方案一的表示可更改如下。



這種方法克服了浪費(fèi)空間的缺點(diǎn),對(duì)空間利用率是很高了,但是由于各個(gè)結(jié)點(diǎn)的鏈表是不相同的結(jié)構(gòu),加上要維護(hù)結(jié)點(diǎn)的度的數(shù)值,在運(yùn)算上就會(huì)帶來(lái)時(shí)間上的損耗。

能否有更好的方法,既可以減少空指針的浪費(fèi)又能使結(jié)點(diǎn)結(jié)構(gòu)相同。可以用孩子表示法。具體辦法是,把每個(gè)結(jié)點(diǎn)的孩子結(jié)點(diǎn)排列起來(lái),以單鏈表作存儲(chǔ)結(jié)構(gòu),則n個(gè)結(jié)點(diǎn)有n個(gè)孩子鏈表,如果是葉子結(jié)點(diǎn)則此單鏈表為空。然后n個(gè)頭指針又組成一個(gè)線性表,采用順序存儲(chǔ)結(jié)構(gòu),存放進(jìn)一個(gè)一維數(shù)組中,如圖:


孩子鏈表

為此,設(shè)計(jì)兩種結(jié)點(diǎn)結(jié)構(gòu),一個(gè)是孩子鏈表的孩子結(jié)點(diǎn),其中child是數(shù)據(jù)域,用來(lái)存儲(chǔ)某個(gè)結(jié)點(diǎn)在表頭數(shù)組中的下標(biāo)。next是指針域,用來(lái)存儲(chǔ)指向某結(jié)點(diǎn)的下一個(gè)孩子結(jié)點(diǎn)的指針。另一個(gè)是表頭數(shù)組的表頭結(jié)點(diǎn),其中也data是數(shù)據(jù)域,存儲(chǔ)某結(jié)點(diǎn)的數(shù)據(jù)信息。firstchild是頭指針域, 存儲(chǔ)該結(jié)點(diǎn)的孩子鏈表的頭指針。

/*樹(shù)的孩子表示法結(jié)構(gòu)定義*/
#define MAX_TREE_SIZE 100
typedef struct CTNode/*孩子結(jié)點(diǎn)*/
{   
    int child;  
    struct CTNode *next;
}*ChildPtr; 
typedef struct/*表頭結(jié)構(gòu)*/
{   
    TElemType data; 
    ChildPtr firstchild;
}CTBox;
 typedef struct/*樹(shù)結(jié)構(gòu)*/
{   
    CTBox nodes[MAX_TREE_SIZE];     /*結(jié)點(diǎn)數(shù)組*/    
    int r, n;           /*根的位置和結(jié)點(diǎn)數(shù)*/
}CTree;

此結(jié)構(gòu)便于查找某結(jié)點(diǎn)的孩子、兄弟,只需查找這個(gè)結(jié)點(diǎn)的孩子單鏈表即可。遍歷整棵樹(shù)也比較方便,對(duì)頭結(jié)點(diǎn)的數(shù)組循環(huán)即可。問(wèn)題是,不易知道某個(gè)結(jié)點(diǎn)的雙親,為此可以改進(jìn)成雙親孩子表示法。結(jié)構(gòu)如下圖:


帶雙親的孩子鏈表

3. 孩子兄弟法

對(duì)樹(shù)觀察發(fā)現(xiàn),任意一棵樹(shù), 它的結(jié)點(diǎn)的第一個(gè)孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。 因此,我們?cè)O(shè)置兩個(gè)指針, 分別指向該結(jié)點(diǎn)的第一個(gè)孩子和此結(jié)點(diǎn)的右兄弟。結(jié)構(gòu)如下圖



其中data是數(shù)據(jù)域,firstchild為指針域,存儲(chǔ)該結(jié)點(diǎn)的第一個(gè)孩子結(jié)點(diǎn)的存儲(chǔ)地址, rightsib是指針域,存儲(chǔ)該結(jié)點(diǎn)的右兄弟結(jié)點(diǎn)的存儲(chǔ)地址。結(jié)構(gòu)定義代碼如下。

/*樹(shù)的孩子兄弟表示法結(jié)構(gòu)定義*/
typedef struct CSNode
{
    TElemType data;
    struct CSNode *firstchild, *rightsib;
}CSNode,*CSTree;

此法方便找某個(gè)結(jié)點(diǎn)的孩子,只需要通過(guò)fistchild 找到此結(jié)點(diǎn)的長(zhǎng)子,然后再通過(guò)長(zhǎng)子結(jié)點(diǎn)的rightsib 找到它的二弟,接著一直下去,直到找到具體的孩子。如果想找到某個(gè)結(jié)點(diǎn)的雙親,這個(gè)表示法就有缺陷了,可以考慮加個(gè)parent指針域來(lái)解決。其實(shí)此法最大的好處就是,把一棵復(fù)雜的樹(shù)變成了二叉樹(shù)。上圖可以變形如下:


(2) 森林與二叉樹(shù)的轉(zhuǎn)換

1. 樹(shù)轉(zhuǎn)換成二叉樹(shù)

轉(zhuǎn)化方法:
1.把所有兄弟結(jié)點(diǎn)連接起來(lái)
2.刪掉除了結(jié)點(diǎn)第一個(gè)左孩子外的連線


2. 森林轉(zhuǎn)換成二叉樹(shù)

將森林轉(zhuǎn)換成二叉樹(shù)的規(guī)則與樹(shù)類(lèi)似。先將森林中的每一棵樹(shù)轉(zhuǎn)換成二叉樹(shù),再將第一棵樹(shù)的根作為轉(zhuǎn)換后的二叉樹(shù)的根,第一棵樹(shù)的左子樹(shù)作為轉(zhuǎn)換后二叉樹(shù)根的左子樹(shù),第二棵樹(shù)作為轉(zhuǎn)換后二叉樹(shù)根的右子樹(shù),第三棵樹(shù)作為轉(zhuǎn)換后二叉樹(shù)根的右子樹(shù)的右子樹(shù),以此類(lèi)推。


3. 二叉樹(shù)轉(zhuǎn)換成森林

若二叉樹(shù)非空,則二叉樹(shù)根及其左子樹(shù)為第一棵樹(shù)的二叉樹(shù)形式,二叉樹(shù)根的右子樹(shù)可以看作是一個(gè)由除第一棵樹(shù)外的森林轉(zhuǎn)換后的二叉樹(shù),應(yīng)用同樣的方法,直到最后產(chǎn)生一顆沒(méi)有右子樹(shù)的二叉樹(shù)為止,這樣就得到了原森林。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評(píng)論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,034評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,413評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,449評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,165評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,559評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,781評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,327評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,084評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,278評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,495評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,927評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,172評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,010評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,241評(píng)論 2 375