前提
樹中的某個結點的孩子可以有多個,所以僅僅使用簡單的順序結構或者鏈式結構是不能完全表示一整棵樹的。
充分利用順序存儲結構和鏈式存儲結構的特點,完全可以實現對樹的存儲結構的表示
我們表示一棵樹的方法有:雙親表示法,孩子表示法,孩子兄弟表示法
補充
對於雙親表示法:我們先將雙親結點存入,我們每插入一個結點都是知道雙親結點位置的,數據可以直接插入。使用順序存儲結構更加方便
而對於孩子表示法,我們每次插入一個結點,對其子樹的位置存放暫不確定,所有使用鏈式存儲結構占主要
(一)雙親表示法
以雙親作為索引的關鍵詞的一種存儲方式
每個結點只有一個雙親,所以選擇順序存儲占主要
以一組連續空間存儲樹的結點,同時在每個結點中,附設一個指示其雙親結點位置的指針域
1.結點結構
2.結點結構定義
/*樹的雙親表示法結點結構定義*/ #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; //r是根位置,n是結點數 }PTree;
3.優缺點分析
優點:parent指針域指向數組下標,所以找雙親結點的時間復雜度為O(1),向上一直找到根節點也快
缺點:由上向下找就十分慢,若要找結點的孩子或者兄弟,要遍歷整個樹
4.改進一:方便獲取孩子結點
在雙親結點基礎上加入孩子結點位置,由於可能一個結點有多個子樹,所以我們要根據數的度來設置添加幾個孩子結點的元素
樹的度為3,所以我們在結點結構設置上添加3個指針域,指向孩子結點,若是孩子為空則位置為-1
/*樹的雙親表示法結點結構定義*/ #define MAX_TREE_SIZE 100 typedef int TElemType; typedef struct PTNode //結點結構 { TElemType data; //結點數據 int parent; //雙親位置 int child1; //孩子結點1 int child2; //孩子結點2 int child3; //孩子結點3 }PTNode; typedef struct //樹結構 { PTNode nodes[MAX_TREE_SIZE]; //結點數組 int r, n; //r是根位置,n是結點數 }PTree;
缺點:這樣消耗了大量的空間,是不必要的,
我們盡可能使用較小的空間,所以我們一般只添加一個長子域,可以獲取到有0個或1個孩子結點,甚至兩個子樹都可以獲取,但是對於較多的孩子我們若是非得使用順序存儲,就得使用上面方法。
注意:長子域是最左邊孩子的域
/*樹的雙親表示法結點結構定義*/ #define MAX_TREE_SIZE 100 typedef int TElemType; typedef struct PTNode //結點結構 { TElemType data; //結點數據 int parent; //雙親位置 int firstchild; //長子域 }PTNode; typedef struct //樹結構 { PTNode nodes[MAX_TREE_SIZE]; //結點數組 int r, n; //r是根位置,n是結點數 }PTree;
5.改進二:方便獲取各兄弟之間的關系
我們只需要增加一個有兄弟域,即可依次獲取所有的兄弟結點
/*樹的雙親表示法結點結構定義*/ #define MAX_TREE_SIZE 100 typedef int TElemType; typedef struct PTNode //結點結構 { TElemType data; //結點數據 int parent; //雙親位置 int rightsib; //右兄弟結點 }PTNode; typedef struct //樹結構 { PTNode nodes[MAX_TREE_SIZE]; //結點數組 int r, n; //r是根位置,n是結點數 }PTree;
總結:
存儲結構的設計是一個十分靈活的過程。一個存儲結構設計是否合理,取決於基於該存儲結構的運算是否合適,方便,時間復雜度好不好等。
例如若是我們既關注孩子又關注兄弟,而且對時間遍歷要求高,那么我們可以擴展上面結構含有雙親域,長子域,右兄弟域
(二)孩子表示法(主要關注孩子結點)
由於每個結點可有多個子樹(無法確定子樹個數),可以考慮使用多重鏈表來實現。
根據樹的度來設置孩子域的個數,例如本例中度為3,設置3個孩子域
/*樹的孩子表示法結點結構定義*/ #define MAX_TREE_SIZE 100 typedef int TElemType; typedef struct PTNode //結點結構 { TElemType data; //結點數據 int child1; //孩子1結點 int child2; //孩子2結點 int child3; //孩子3結點 }PTNode; typedef struct //樹結構 { PTNode nodes[MAX_TREE_SIZE]; //結點數組 int r, n; //r是根位置,n是結點數 }PTree;
缺點:占用了大量不必要的孩子域空指針
以其為標准:需要3n個指針域,實際上有用n-1個(除了根節點,其他n-1個都向上需要一條邊),則有2n+1個無用,浪費
改進一:為每個結點添加一個結點度域,方便控制指針域的個數
缺點:維護困難,不易實現
改進三:結合順序結構和鏈式結構
/*樹的孩子表示法結點結構定義*/ #define MAX_TREE_SIZE 100 typedef int TElemType; 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; //r是根位置,n是結點數 }CTree;
改進四:添加雙親域,方便查找雙親結點(雙親孩子表示法)
/*樹的孩子表示法結點結構定義*/ #define MAX_TREE_SIZE 100 typedef int TElemType; typedef struct CTNode //孩子結點 { int child; struct CTNode* next; }*ChildPtr; typedef struct //表頭結構 { TElemType data; int parent; ChildPtr firstChild; //指向第一個孩子的指針 }CTBox; typedef struct //樹結構 { CTBox nodes[MAX_TREE_SIZE]; //結點數組 int r, n; //r是根位置,n是結點數 }CTree;
(三)孩子兄弟表示法
上面從雙親,孩子角度研究樹的結構,下面我們從樹的結點的兄弟角度來研究
任意一棵樹,他的結點的第一個孩子如果存在就是唯一結點,他的右兄弟如果存在,也是唯一的,因此,我們設置兩個指針,分別指向該結點的第一個孩子和該結點的右兄弟
n個結點,有2n個指針域,有n-1條邊,空n+1個指針域
typedef int TElemType; typedef struct CSNode { TElemType data; struct CSNode* firstchild, *rightsib; }CSNode,*CSTree;
若有需要,可以再加入一個雙親域,但是上面的結構以及轉換為二叉樹,我們可以使用二叉樹的一系列方法,來解決問題