資料結構之樹的三種儲存結構
說到儲存結構,我們就會想到常用的兩種儲存方式:順序儲存和鏈式儲存兩種。
先來看看順序儲存,用一段地址連續的儲存單元依次儲存線性表中資料元素,這對於線性表來說是很自然的,但是對於樹這種一對多的結構而言是否適合呢?
樹中某個結點的孩子可以有多個,這就意味著,無論用哪種順序將樹中所有的結點儲存到陣列中,結點的儲存位置都無法直接反映邏輯關係,試想一下,資料元素挨個儲存,那麼誰是誰的雙親,誰是誰的孩子呢?所以簡單的順序儲存是不能滿足樹的實現要求的。
不過可以充分利用順序儲存和鏈式儲存結構的特點,完全可以實現對樹的儲存結構的表示。
下面介紹三種不同的樹的表示法:雙親表示法,、孩子表示法,、孩子兄弟表示法。
1、雙親表示法:
我們假設以一組連續空間儲存樹的結點,同時在每個結點中,附設一個指示器指向其雙親結點到連結串列中的位置。也就是說每個結點除了知道自己之外還需要知道它的雙親在哪裡。
它的結構特點是如圖所示:
以下是我們的雙親表示法的結構定義程式碼:
/*樹的雙親表示法結點結構定義 */ #define MAXSIZE 100 typedef int ElemType; //樹結點的資料型別,暫定為整形 typedef struct PTNode //結點結構 { ElemType data; //結點資料 int parent; //雙親位置 }PTNode; typedef struct { PTNode nodes[MAXSIZE]; //結點陣列 int r,n; //根的位置和結點數 }PTree;
2、孩子表示法
換一種不同的考慮方法。由於每個結點可能有多棵子樹,可以考慮使用多重連結串列,即每個結點有多個指標域,其中每個指標指向一棵子樹的根結點,我們把這種方法叫做多重連結串列表示法。不過樹的每個結點的度,也就是它的孩子個數是不同的。所以可以設計兩種方案來解決。
方案一:
一種是指標域的個數就等於樹的度(樹的度是樹的各個結點度的最大值)
其結構如圖所示:
不過這種結構由於每個結點的孩子數目不同,當差異較大時,很多結點的指標域就都為空,顯然是浪費空間的,不過若樹的各結點度相差很小時,那就意味著開闢的空間都被利用了,這時這種缺點反而變成了優點。
方案二:
第二種方案是每個結點指標域的個數等於該結點的度,我們專門取一個位置來儲存結點指標域的個數。
其結構如圖所示:
這種方法克服了浪費空間的缺點,對空間的利用率是很高了,但是由於各個結點的連結串列是不相同的結構,加上要維護結點的度的數值,在運算上就會帶來時間上的損耗。
能否有更好的方法呢,既可以減少空指標的浪費,又能是結點結構相同。
說到這大家肯定就知道是有的麥,那就是孩子表示法。
具體辦法是,把每個結點的孩子排列起來,以單鏈表做儲存結構,則n個結點有n個孩子連結串列,如果是葉子結點則此單鏈表為空。然後n個頭指標有組成一個線性表,採用順序儲存結構,存放進入一個一維陣列中。
為此,設計兩種結點結構,
一個是孩子連結串列的孩子結點,如下所示:
其中child是資料域,用來儲存某個結點在表頭陣列中的下標。next是指標域,用來儲存指向某結點的下一個孩子結點的指標。
另一個是表頭結點,如下所示:
其中data是資料域,儲存某結點的資料資訊。firstchild是頭指標域,儲存該結點的孩子連結串列的頭指標。
以下是孩子表示法的結構定義程式碼:
/*樹的孩子表示法結點結構定義 */
#define MAXSIZE 100
typedef int ElemType; //樹結點的資料型別,暫定為整形
typedef struct CTNode //孩子結點
{
int child;
struct CTNode *next;
}*ChildPtr;
typedef struct //表頭結構
{
ElemType data;
ChildPtr firstchild;
}CTBox;
typedef struct //樹結構
{
CTBox nodes[MAXSIZE]; //結點陣列
int r,n; //根結點的位置和結點數
}CTree;
3、孩子兄弟表示法
我們發現,任意一顆樹,它的結點的第一個孩子如果存在就是的,它的右兄弟如果存在也是唯一的。因此,我們設定兩個指標,分別指向該結點的第一個孩子和此結點的右兄弟。
其結點結構如圖所示:
以下是孩子兄弟表示法的結構定義程式碼:
/*樹的孩子兄弟表示法結構定義 */
typedef struct CSNode
{
ElemType data;
struct CSNode *firstchild, *rightsib;
}CSNode, *CSTree;