1. 程式人生 > >淺析資料結構之樹與森林

淺析資料結構之樹與森林

樹是一種重要的非線性資料結構(二叉樹是每個結點最多有兩個子樹的有序樹)

  1. 二叉樹的定義:有且僅有一個根結點,除根結點外,每個結點只有一個父結點,最多含有兩個子結點,子點有左右之分。
  2. 儲存結構
    • 二叉樹的儲存結構可以採用順序儲存,也可以採用鏈式儲存,其中鏈式儲存更加靈活。
    •  在鏈式儲存結構中,與線性連結串列類似,二叉樹的每個結點採用結構體表示,結構體包含三個域:資料域、左指標、右指標。

二叉樹的建立和遍歷

#include<stdio.h>
//typedef int Elemment;
typedef char Elemment;
typedef struct node
{
    Elemment data;
    struct node *lchild,*rchild;        //左右子結點指標
} BinTree;

/*測試資料*/
//HDB#A##C##G#FE###
//1 2 3 -1 4 -1 -1 5 -1 -1 6 -1 7 8 -1 -1 -1

//先序建立二叉樹
BinTree *CreateBinTree(Elemment *e,int *i)
{
    BinTree *bt = NULL;
    //if(e[(*i)++]!=-1)
    if(e[(*i)++]!='#')
    {
        bt = (BinTree*)malloc(sizeof(BinTree));
        bt->data = e[(*i)-1];
        bt->lchild=CreateBinTree(e,i);
        bt->rchild=CreateBinTree(e,i);
    }
    return bt;
}
//先序遍歷二叉樹
void firstorder(BinTree *bt)
{
    if(bt!=NULL)
    {
        printf("%c",bt->data);
        //printf("%d",bt->data);
        Inorder(bt->lchild);
        Inorder(bt->rchild);
    }
}
//中序遍歷二叉樹
void Inorder(BinTree *bt)
{
    if(bt!=NULL)
    {
        Inorder(bt->lchild);
           printf("%c",bt->data);
        //printf("%d",bt->data);
        Inorder(bt->rchild);
    }
}
//後序遍歷二叉樹
void lastorder(BinTree *bt)
{
    if(bt!=NULL)
    {
        Inorder(bt->lchild);
        Inorder(bt->rchild);
        printf("%c",bt->data);
        //printf("%d ",bt->data);
    }
}

int main()
{
    int i=0,j=0;
    //int c[100];
    //while(~scanf("%d",&c[j++]));
    char c[100];
    gets(c);
    BinTree *bt = CreateBinTree(c,&i);
    Inorder(bt);
}
-------------------------------------------------------------------------------------------分割線-------------------------------------------------------------------------------------
 二叉樹線索化是一種可以簡化二叉樹遍歷的演算法(當以二叉連結串列作為二叉樹的儲存結構時,只能找到結點的左右子節點,而不能直接找到前趨和後繼,只有在遍歷的動態       過程中才能找到這些資訊,如果在第一次遍歷時就將這些資訊儲存起來,那麼第二次遍歷時就可將二叉樹作為線性結構進行訪問,從而簡化遍歷操作)
       n個結點的二叉連結串列中含有n+1(2n-(n-1)=n+1)個空指標域。利用二叉連結串列中的空指標域,存放指向結點在某種遍歷次序下的前趨和後繼結點的指標 (這種附加的指標稱為"線索")。
      加上線索的二叉樹稱為線索二叉樹,對二叉樹以某種遍歷方式使其加上線索的過程稱為線索化。

--------------------------------------------------------------------------------------------分割線------------------------------------------------------------------------------------

二叉樹的線索化規定如下:

若結點右左子樹,則其lchild域指向其左孩子,否則令lchild域指向其前驅;若結點有右子樹,其rchild域指向其右孩子,否則指向其後繼。
         線索二叉樹的描述如下

typedef struct node
{
    int ltag,rtag;                /*左右標誌域,ltag=0,lchild為左指標,ltag=1,lchild為左線索;
                                                rtag=0,rchild為右指標,rtag=1,rchild為右線索;*/
    Elemment data;
    struct node *lchild,*rchild;    //左右子節點指標
} BinTree;

為了操作方便,給線索連結串列新增一個”頭結點“,該結點的指標lchild指向二叉樹的根結點,ltag=0;rchild指向二叉樹遍歷序列的最後一個結點,rtag=1;同時讓遍歷序列中第一個結點的左線索和最後一個結點的右線索指向頭結點,這樣線索連結串列就構成了一個雙向迴圈連結串列(可逆序可正序遍歷)

下面以中序線索化為例,建立一箇中序線索連結串列

BinTree *pre;   //定義一個全域性變數。始終指向當前結點的前趨
//中序線索化
BinTree *InorderThreading(BinTree *bt)
{
    BinTree *head;        //新增”頭結點“
    head = (BinTree*)malloc(sizeof(BinTree));
    if(!head)
    {
        printf("分配失敗");
        return NULL;
    }
    head->ltag = 0;
    head->rtag = 1;
    head->rchild = head;
    if(bt==NULL)
    {
        head->lchild = head;   //若二叉樹為空,head左線索指向自身
    }
    else
    {
        head->lchild = bt;     //若二叉樹非空,head左線索指向根結點
        pre = head;
        InThreading(bt);        //中序遍歷進行線索化
        pre->rchild = head;     //最後一個結點右線索化
        pre->rtag = 1;
        head -> rchild = pre;    //頭結點的右指標指向最後一個結點
    }
    return head;
}
void InThreading(BinTree *bt)
{
    if(bt!=NULL)
    {
        InThreading(bt->lchild);
        if(bt->lchild==NULL)
        {
            bt->ltag=1;
            bt->lchild = pre;
        }
        if(pre->rchild==NULL)
        {
            pre->rtag=1;
            pre->rchild = bt;
        }
        pre = bt;
        InThreading(bt->rchild);
    }
}

中序線索二叉樹的遍歷

  • 若該結點的右標誌rtag==1,表明該結點沒有右孩子,則rchild為線索,直接指示後繼。
  • (逆序遍歷時,若該結點的左標誌ltag==1,表明該結點沒有左孩子,則lchild為線索,直接指示前趨。)
  • 若該結點的右標誌rtag==0,表明該結點有右孩子,結點的後繼應是遍歷其右子樹訪問的第一個結點,即最左下結點。
  • (逆序遍歷時,若該結點的左標誌ltag==0,表明該結點有左孩子,結點的前趨應是其左孩子)
//中序線索二叉樹的遍歷
void Inorder_T(BinTree *head)
{
    BinTree *bt;
    bt = head->lchild;
    while(bt!=head)
    {
        while(!bt->ltag)
        bt=bt->lchild;
        //printf("%d",bt->data);
        printf("%c",bt->data);
        while(bt->rtag==1&&bt->rchild!=head)
        {
            bt=bt->rchild;
            //printf("%d",bt->data);
            printf("%c",bt->data);
        }
        bt=bt->rchild;
    }
}

樹的儲存結構有幾種

  • 雙親表示法(習慣用順序儲存)
  • 孩子連結串列表示法(其指標域指向由其所有孩子結點組成的單鏈表首結點)
  • 孩子兄弟表示法,一個指標域指向其第一個孩子,另一個指標域指向其所有兄弟組成的單鏈表首結點

樹與二叉樹的轉換

從樹的二叉連結串列定義(如上紅色兩種表示,相當於二叉樹,解釋不同而已)可知,任何一棵樹所對應的二叉連結串列其右子樹必為空 (樹與二叉樹是一一對應的且唯一)

森林與二叉樹的轉換

左子樹為第一棵樹轉換,右子樹由剩下的森林轉換(反之亦然)