【筆記】樹的表示與實現

Time-space發表於2017-10-29

1.樹的抽象資料型

  與二叉樹一樣,在樹上規定如下基本操作,可以把樹定義成抽象資料型。

  • Parent(n,T):返回樹T結點n的父親。若n是根,則返回空。
  • LeftMostChild(n,T):返回樹T結點的最左兒子。
  • RightSibling(n,T):返回樹T結點n的右兄弟。
  • Data(n,T):返回樹T結點n的DATA域的值。
  • Create(v,T1,T2,,Tk
    v,T_1,T_2,…,T_k
    ):建立DATA域為v的根結點r,此結點有k顆子樹T1,T2,,Tk
    T_1,T_2,…,T_k
    ,且T1,T2,,Tk
    T_1,T_2,…,T_k
    是自左而右排列的。返回以r為根的樹;若k=0,則r既是根也是葉結點。
  • Root(T):返回樹T的根結點。

  通常可以對樹定義3種遍歷順序,假設各子樹記為T1,T2,,Tk

T_1,T_2,…,T_k
。則樹的遍歷定義如下:

  (1)先根順序

  • 訪問根結點。
  • 先根順序遍歷T1
    T_1
  • 先根順序遍歷T2
    T_2

  • 先根順序遍歷Tk
    T_k
  • 退出

  (2)中根順序

  • 中根順序遍歷T1
    T_1
  • 訪問根結點。
  • 中根順序遍歷T2
    T_2

  • 中根順序遍歷Tk
    T_k
  • 退出

  (3)後根順序

  • 後根順序遍歷T1
    T_1
  • 後根順序遍歷T2
    T_2

  • 後根順序遍歷Tk
    T_k
  • 訪問根結點。
  • 退出

  樹的先根遍歷演算法:

void PreOrder(node n,TREE T)
/*按先根順序列出樹T中結點n及其所有後代的資料域的值*/
{
    node c;
    visit(Data(n,T));
    c=LeftMostChild(n,T);
    while(c!=NULL)
    {
        PreOrder(c,T);
        c=RightSibling(c,T);
    }
}

  如果要按先跟順序遍歷整棵樹T,則只要呼叫PreOrder(Root(T),T)即可。

2.樹的表示

樹的陣列表示

  設T是一棵樹,其中的結點命名為1,2,3,…,n。根據樹中每個結點只有一個父親這個特性,可用陣列A來表示樹T。令陣列的下標對應於結點名,陣列元素A[i]定義如下:

A[i]={j,0,iji
A[i]= \left\{\begin{array}{cc} j, & 若結點i的父親是j\\ 0, & 若結點i是根 \end{array}\right.

  這種表示法除根結點外,每個結點只有一個指向其父親結點的鏈域。所以有時把這種表示法稱為樹的父連結串列示法或單連結串列示法。

這裡寫圖片描述

  一般來說,Parent(i)=A[i]。陣列元素由parent和data兩個域構成。可以把上圖表示的樹的結構描述為:

struct node
{
    int parent;
    char data;
};
typedef node TREE[11];
TREE T;


這裡寫圖片描述

  這種只有父鏈的表示方法,對於求父結點及祖先結點都很方便。但對於給定的結點n,欲求其子結點的資訊相當困難,也不能反映各兄弟結點的順序。但是如果在給結點命名時,可按照這樣一種順序:對每個結點n,在對n進行編號之後,按自左而右遞增的順序依次對n的兒子進行編號。在此假設之下,將很容易實現RightSibling和LeftMostChild。

typedef int node;
typedef node TREE[maxnodes];
node LeftMostChild(node n,TREE T)
{
    node i;
    for(i=n+1;i<=maxnodes-1;i++)
        if(T[i]==n)
            return i;
    return 0;
}

樹的鄰接表表示

  樹的一種重要表示法是每個結點的所有子結點形成一個線性表,但因每個結點所具有的子結點個數不同,所以通常都採用連結式表示。
  上圖中的樹為例可以表示為下圖所示。其中有一個陣列header,每一個陣列元素header[i]是一個表頭結點,指向結點i的兒子結點連結串列的首結點。對於每個結點i,由header[i]出發,可以很容易地將其全部兒子結點找到。


這裡寫圖片描述

  鄰接表的型別說明:

typedef int node;
struct celltype
{
    node element;
    celltype *next;
};
typedef celltype *LIST;
typedef celltype *position;
struct TREE
{
    LIST header[maxnodes];
    datatype data[maxnodes];
    node root;
};

  用樹的這種表示法實現LeftMostChild操作:

node LeftMostChild(node n ,TREE T)
{
    LIST L;
    L=T.header[n];
    if(Empty(L))
        return 0;
    else
        return(Retrieve(First(L),L));
}

樹的左右連結串列示法

  通常,可以按照其自然形式,令每個結點除其資訊域外,設定多個鏈域,使它們指向相應的兒子結點,但這樣會使每個結點佔用的空間較多。為了節省每個結點所佔用的空間,在每個結點中只設定兩個鏈域,令左鏈指向最左兒子,右鏈指向緊挨它的右兄弟,樹的這種儲存表示方法稱為樹的左右連結串列示法,也稱為樹的左兄弟右兒子表示法或者樹的二叉連結串列表示法。這種表示法很容易實現由較小的樹來構造較大的樹。


這裡寫圖片描述

  其儲存結構為:

struct 
{
    datatype data;
    int leftchild;
    int rightsibling;
}cellspace[maxnodes];


這裡寫圖片描述

  用樹的左右連結串列示法,很容易實現除Parent操作以外的各種操作。如果希望有效地實現Parent操作,即快速地找到一個結點的父結點,則在每個結點中增加指向父結點的鏈域。

struct
{
    datatype data;
    int leftchild;
    int rightsibling;
    int parent;
}cellspace[maxnodes];

  此外,還可以選擇這樣一種結構:令最右兒子的rightsibling域指向右父親,於是必須對每個結點增加一個標誌位,用於區別其rightsibling域是指向兄弟還是指向父親。在求一個結點的父親時,只需沿著rightsibling域查到虛線的指向即可。這比在每一個結點中增加parent域可節省控制元件,但花費的時間稍多一些。


  • 樹不能為空的理由

  如果在設計的體系中定義了森林,同夥私又允許樹為空, 那麼由於森林是由若干顆樹構成的,很可能出現一個集合(森林)包含許多空集(樹),或者一個空集(森林)包含許多個空集(樹)的情況。如果允許樹為空,則很難確定森林中的一顆空樹對應於什麼樣的二叉樹,兩顆空樹對應於什麼樣的二叉樹。

3.樹的實現

原始碼參見樹的實現

相關文章