資料結構看書筆記(六)--樹的定義、抽象資料型別、儲存結構
樹(Tree)是n(n>=0)個結點的有限集。n=0時稱為空樹,在任意一顆非空樹中:(1)有且只有一個特定的稱之為根(Root)的結點;(2)當n>1時,其餘結點可分為m(m>0)個互不相交的有限集T1、T2、……、Tm,其中每一個集合本身又是一棵樹,並且稱為根的子樹(SubTree).
對於樹的定義還要強調兩點:
1.n>0時根結點是唯一的,不可能存在多個根結點,應與現實中的大樹區分開
2.m>0時,子樹的個數沒有限制,但它們一定是互不相交的。
結點分類:
結點擁有的子樹稱為結點的度(Degree).度為0的結點稱為葉結點(Leaf)或終端結點;度不為0的結點稱為非終端結點或分支結點。除根結點外,分支節點也稱為內部結點。樹的度是樹內各結點的度的最大值。
結點間關係:
結點的子樹的根稱為該節點的孩子(Child),相應地,該結點稱為孩子的雙親(Parent).
同一個雙親的孩子之間的互稱兄弟(Sibling).
結點的祖先是從根到該節點所經分支上的所有節點。
以某結點為根的子樹中的任一結點都稱為該結點的子孫。
樹的其他相關概念:
結點的層次(Level)從根開始定義起,根為第一層,根的孩子為第二層。
雙親在同一層的結點互為堂兄弟。
樹中結點的最大層次稱為樹的深度(Depth)或高度。
如果將樹中結點的各子樹看成從左至右是有次序的,不能互換的,則稱該數為有序樹,否則稱為無序樹。
森林(Forest)是m(m>=0)棵互不相交的樹的集合。
對比線性表與樹的結構:
線性結構:
第一個資料元素:無前驅
最後一個數據元素:無後繼
中間元素:一個前驅一個後繼
樹結構:
根結點:無雙親,唯一
葉結點:無孩子,可以多個
中間結點:一個雙親多個孩子(或一個孩子)
樹的抽象資料型別:
ADT 樹(Tree)
Data
樹是由一個根結點和若干棵子樹構成。樹中結點具有相同資料型別及層次關係。
Operation
InitTree(*T):構造空樹T.
DestroyTree(*T):銷燬樹T.
CreateTree(*T,definition):按difinition中給出樹的定義來構造樹。
ClearTree(*T):若樹T存在,則將樹T清為空樹。
TreeEmpty(T):若T為空樹,返回true,否則返回false.
TreeDepth(T):返回T的深度。
Root(T):返回T的根結點。
Value(T,cur_e):cur_e是樹T中的一個結點,返回此結點的值。
Assign(T,cur_e,value):給樹T的結點cur_e賦值為value.
Parent(T,cur_e):若cur_e是樹的非根結點,則返回它的雙親,否則返回空。
LeftChild(T,cur_e):若cur_e是樹的非葉結點,則返回它的最左孩子,否則返回空。
RightSibling(T,cur_e):若cur_e有右兄弟,則返回它的右兄弟,否則返回空。
InsertChild(*T,*p,i,c):其中p指向樹的某個結點,i為所指結點p的度加上1,非空樹c與T不相交,操作結果為插入c為樹T中p指結點的第i棵子樹。
DeleteChild(*T,*p,i):其中p指向樹T的某個結點,i為所指結點的p的度,操作結果為刪除T中p所指結點的第i棵子樹。
endADT
樹的儲存結構:
雙親表示法:我們假設以一組連續空間儲存樹的結點,同時在每個結點中,附設一個指示器指示其雙親結點在陣列中的位置。---即每個結點除了知道自己是誰之外,還知道它的雙親在哪。
其結點結構如下所示:
|----------|-----------|
|data |parent |
|----------|-----------|
其中data為資料域,儲存結點的資料資訊,parent是指標域,儲存該結點的雙親在陣列中的下標。
以下為雙親表示法的結點結構定義程式碼:
#define MAX_TREE_SIZE 100
typedef int TElemType;
typedef struct PTNode
{
TElemType data;
int parent;
}PTNode;
typedef struct
{
PTNode nodes[MAX_TREE_SIZE];
int r,n;//根的位置和結點數
}PTree;
以下是一顆樹的表示:
|-----------|-----------|-----------|
| 下標 | data | parent |
|-----------|-----------|-----------|
| 0 | A | -1 |
|-----------|-----------|-----------|
| 1 | B | 0 |
|-----------|-----------|-----------|
| 2 | C | 0 |
|-----------|-----------|-----------|
| 3 | D | 1 |
|-----------|-----------|-----------|
| 4 | E | 2 |
|-----------|-----------|-----------|
| 5 | F | 2 |
|-----------|-----------|-----------|
| 6 | G | 3 |
|-----------|-----------|-----------|
| 7 | H | 3 |
|-----------|-----------|-----------|
| 8 | I | 3 |
|-----------|-----------|-----------|
| 9 | J | 4 |
|-----------|-----------|-----------|
此外還可以有以下型別的表示法(只是一部分):
|-----------|-----------|-----------|-----------|
| 下標 | data | parent | firstChild|
|-----------|-----------|-----------|-----------|
|-----------|-----------|-----------|-----------|
| 下標 | data | parent | rightsib |
|-----------|-----------|-----------|-----------|
儲存結構的設計師一個非常靈活的過程,一個儲存結構設計得是否合理,取決於基於該儲存結構的運算是否合適、是否方便,時間複雜度好不好等。
孩子表示法:
多重連結串列表示法:每個結點由多個指標域,其中每個指標指向一棵子樹的根結點,我們把這種方法叫做多重連結串列表示法。
以下有兩種方案解決孩子個數不同的問題。
方案一:
|----------|-----------|-----------|-----------|-----------|-----------|
|data | child1 | child2 | child3 | ………… | childn |
|----------|-----------|-----------|-----------|-----------|-----------|
其中data為資料域,child1到childn為指標域,用來指向該結點的孩子結點。
優缺點分析:這種方法對於樹中各結點的度相差很大時,顯然是浪費空間的,因為有很多的結點,其指標域都是空的,但是如果各個結點的度相差不大時,就意味著空間被充分的利用了,這時儲存結構的缺點反而變成了優點。
鑑於上述第一種方案可能帶來空間上的浪費的問題,我們提出了方案二:
方案二:
第二種方案每個結點指標域的個數等於該結點的度,我們專門取一個位置來儲存結點指標域的個數,其結構如下
|----------|-----------|-----------|-----------|-----------|-----------|-----------|
|data | degree | child1 | child2 | child3 | ………… | childn |
|----------|-----------|-----------|-----------|-----------|-----------|-----------|
其中data為資料域,degree為度域,也就是儲存該結點的孩子結點的個數,child1到childn為指標域,指向該結點的各個孩子的結點。
優缺點分析:這種方案客服了浪費空間的缺點,對空間的利用率是很高了,但是由於各個結點的連結串列是不相同的結構,加上要維護結點的度的數值,在運算上就會帶來時間上的損耗。
鑑於上述的多重連結串列表示法的兩種方案都不是很適合,所以我們引入孩子表示法這個概念:
具體的辦法就是,把每個結點的孩子結點排列起來,以單鏈表作儲存結構,則n個結點有n個孩子連結串列,如果是葉子結點則此單鏈表為空。然後n個頭指標又組成一個線性表,採用順序儲存結構,存放進一個一維陣列中,具體的表示如下所示
|------|------|-----------| |-------|------|
| 下標 | data | firstChild| |child |next |
|--------|--------|-------------| |-------|------| |----|----|
| 0 | A | | ==> |1 | |==> |2 | ^ |
|--------|--------|-------------| |-------|------| |----|----|
| 1 | B | | ==> |3 |^ |
|--------|--------|-------------| |-------|------| |----|----|
| 2 | C | | ==> |4 | |==> |5 | ^ |
|------|------|-----------| |-------|------| |----|----| |----|----|
| 3 | D | | ==> |6 | |==> |7 | |==> |8 |^ |
|------|------|-----------| |-------|------| |----|----| |----|----|
| 4 | E | | ==> |9 |^ |
|------|------|-----------| |-------|------|
| 5 | F | ^ |
|------|------|-----------|
| 6 | G | ^ |
|------|------|-----------|
| 7 | H | ^ |
|------|------|-----------|
| 8 | I | ^ |
|------|------|-----------|
| 9 | J | ^ |
|------|------|-----------|
為此需要設計兩種結點結構,一個是孩子連結串列的孩子結點
|-------|------|
|child |next |
|-------|------|
其中child是資料域,用來儲存某個結點在表頭陣列中的下標,next是指標域,用來儲存指向某結點的下一個孩子結點的指標。
另一個是表頭陣列的表頭結點
|------|-----------|
| data | firstChild|
|------|-----------|
其中data為資料域,儲存某結點的資料資訊,firstchild是頭指標域,儲存該結點的孩子連結串列的頭指標。
以下為孩子表示法的結構定義程式碼:
#define MAX_TREE_SIZE 100
typedef struct CTNode
{
int child;
struct CTNode *next;
}*ChildPtr;
typedef struct
{
TElemType data;
ChildPtr firstchild;
}CTBox;
typedef struct
{
CTBox nodes[MAX_TREE_SIZE];
int r,n;//根的位置和結點數
}CTree;
以上的結構對於我們要查詢某個結點的某個孩子,或者找某個結點的兄弟,只需要查詢這個結點的孩子單鏈表即可,對於遍歷整棵樹也是很方便的,對頭結點的陣列迴圈即可。
如果需要查詢某個結點的雙親結點是誰的話,則可以考慮一下這種結構
|------|------|------|-----------| |-------|------|
| 下標 | data |parent| firstChild| |child |next |
|------|------|------|-----------| |-------|------| |----|----|
| 0 | A |-1 | | ==> |1 | |==> |2 | ^ |
|------|------|------|-----------| |-------|------| |----|----|
| 1 | B |0 | | ==> |3 |^ |
|------|------|------|-----------| |-------|------| |----|----|
| 2 | C |0 | | ==> |4 | |==> |5 | ^ |
|------|------|------|-----------| |-------|------| |----|----| |----|----|
| 3 | D |1 | | ==> |6 | |==> |7 | |==> |8 |^ |
|------|------|------|-----------| |-------|------| |----|----| |----|----|
| 4 | E |2 | | ==> |9 |^ |
|------|------|------|-----------| |-------|------|
| 5 | F |2 | ^ |
|------|------|------|-----------|
| 6 | G |3 | ^ |
|------|------|------|-----------|
| 7 | H |3 | ^ |
|------|------|------|-----------|
| 8 | I |3 | ^ |
|------|------|------|-----------|
| 9 | J |4 | ^ |
|------|------|------|-----------|
以上的表示法稱為雙親孩子表示法
孩子兄弟表示法:
任意一棵樹,它的結點的第一個孩子如果存在就是唯一的,它的右兄弟如果存在也是唯一的。因此,我們設定兩個指標,分別指向該結點的第一個孩子和此結點的右兄弟。
|-----------|-----------|-----------|
| data | firstchild| rightsib |
|-----------|-----------|-----------|
其中data為資料域,firstchild為指標域,儲存該結點的第一個孩子結點的儲存地址,rightsib是指標域,儲存該結點的右兄弟結點的儲存地址。
結構定義程式碼如下。
typedef struct CSNode
{
TElemType data;
struct CSNode *firstchild,*rightsib;
}CSNode,*CSTree;
分析:這種表示法,給查詢某個結點的某個孩子帶來了方便,只需要通過firstchild找到此結點的長子,然後再通過長子結點的rightsib找到它的二弟,接著一直下去,知道找到具體的孩子。當然,如果想找某個結點的雙親,這個表示法也是有缺陷的。當然也可以增加一個雙親指標域來解決快速查詢雙親的問題,這樣其實表示出來的圖示就是一個二叉樹。二叉樹的內容,放到下一複習小節。
樹(Tree)是n(n>=0)個結點的有限集。n=0時稱為空樹,在任意一顆非空樹中:(1)有且只有一個特定的稱之為根(Root)的結點;(2)當n>1時,其餘結點可分為m(m)