如下圖所示,這是一棵普通的樹,該如何存儲呢?通常,存儲具有普通樹結構數據的方法有 3 種:
雙親表示法;
孩子表示法;
孩子兄弟表示法;
圖1
樹的雙親表示法
雙親表示法採用順序表(也就是數組)存儲普通樹,其實現的核心思想是:順序存儲各個節點的同時,給各節點附加一個記錄其父節點位置的變量。
注意,根節點沒有父節點(父節點又稱爲雙親節點),因此根節點記錄父節點位置的變量通常置爲 -1。
圖2
雙親表示法存儲普通樹代碼
/*
* @Description: 樹的雙親表示法
* @Version: V1.0
* @Autor: Carlos
* @Date: 2020-05-21 14:41:32
* @LastEditors: Carlos
* @LastEditTime: 2020-06-01 22:12:34
*/
#include<stdio.h>
#include<stdlib.h>
//宏定義樹中結點的最大數量
#define MAX_SIZE 20
//宏定義樹結構中數據類型
typedef char ElemType;
//結點結構
typedef struct Snode
{
//樹中結點的數據類型
ElemType data;
//結點的父結點在數組中的位置下標
int parent;
}PNode;
//樹結構
typedef struct
{
//存放樹中所有結點
PNode tnode[MAX_SIZE];
//結點個數
int n;
}PTree;
/**
* @Description: 節點初始化
* @Param: PTree tree 結構體變量
* @Return: PTree 結構體地址
* @Author: Carlos
*/
PTree InitPNode(PTree tree)
{
int i,j;
char ch;
printf("請輸入節點個數:\n");
scanf("%d",&(tree.n));
printf("請輸入結點的值其雙親位於數組中的位置下標:\n");
for(i=0; i<tree.n; i++)
{
fflush(stdin);
scanf("%c %d",&ch,&j);
tree.tnode[i].data = ch;
tree.tnode[i].parent = j;
}
return tree;
}
/**
* @Description: 查找樹中指定節點
* @Param: PTree tree 結構體變量
* @Return: 無
* @Author: Carlos
*/
void FindParent(PTree tree)
{
char a;
int isfind = 0;
printf("請輸入要查詢的結點值:\n");
fflush(stdin);
scanf("%c",&a);
for(int i =0;i<tree.n;i++){
if(tree.tnode[i].data == a){
isfind=1;
//找到父節點的下標數值
int ad=tree.tnode[i].parent;
printf("%c的父節點爲 %c,存儲位置下標爲 %d",a,tree.tnode[ad].data,ad);
break;
}
}
if(isfind == 0){
printf("樹中無此節點");
}
}
int main()
{
PTree tree;
tree = InitPNode(tree);
FindParent(tree);
return 0;
}
樹的孩子表示法
孩子表示法存儲普通樹採用的是 “順序表+鏈表” 的組合結構,其存儲過程是:從樹的根節點開始,使用順序表依次存儲樹中各個節點,需要注意的是,與雙親表示法不同,孩子表示法會給各個節點配備一個鏈表,用於存儲各節點的孩子節點位於順序表中的位置。
如果節點沒有孩子節點(葉子節點),則該節點的鏈表爲空鏈表。
例如,使用孩子表示法存儲左圖中的普通樹,則最終存儲狀態如右圖所示:
圖3
/*
* @Description: 樹的孩子表示法。三部分構成,鏈表,節點,樹
* @Version:
* @Autor: Carlos
* @Date: 2020-05-21 14:59:47
* @LastEditors: Carlos
* @LastEditTime: 2020-06-01 22:47:38
*/
#include<stdio.h>
#include<stdlib.h>
#define MAX_SIZE 20
#define TElemType char
typedef struct CTNode{
//鏈表中每個結點存儲的不是數據本身,而是數據在數組中存儲的位置下標!!
int child;
struct CTNode * next;
}ChildPtr;
typedef struct {
//結點的數據類型
TElemType data;
//孩子鏈表的頭指針
ChildPtr* firstchild;
}CTBox;
typedef struct{
//存儲結點的數組
CTBox nodes[MAX_SIZE];
//結點數量和樹根的位置
int n,r;
}CTree;
/**
* @Description: 孩子表示法存儲普通樹
* @Param: CTree tree 樹的結構體變量
* @Return: CTree tree 結構體變量
* @Author: Carlos
*/
CTree InitTree(CTree tree){
printf("輸入節點數量:\n");
scanf("%d",&(tree.n));
for(int i=0;i<tree.n;i++){
printf("輸入第 %d 個節點的值:\n",i+1);
fflush(stdin);
scanf("%c",&(tree.nodes[i].data));
tree.nodes[i].firstchild=(ChildPtr*)malloc(sizeof(ChildPtr));
tree.nodes[i].firstchild->next=NULL;
printf("輸入節點 %c 的孩子節點數量:\n",tree.nodes[i].data);
int Num;
scanf("%d",&Num);
if(Num!=0){
//帶頭結點的鏈表
ChildPtr * p = tree.nodes[i].firstchild;
for(int j = 0 ;j<Num;j++){
ChildPtr * newEle=(ChildPtr*)malloc(sizeof(ChildPtr));
newEle->next=NULL;
printf("輸入第 %d 個孩子節點在順序表中的位置",j+1);
scanf("%d",&(newEle->child));
p->next= newEle;
p=p->next;
}
}
}
return tree;
}
/**
* @Description:查找節點
* @Param: CTree tree 樹的結構體,char a 要查找的節點
* @Return: 無
* @Author: Carlos
*/
void FindKids(CTree tree,char a){
int hasKids=0;
for(int i=0;i<tree.n;i++){
if(tree.nodes[i].data==a){
ChildPtr * p=tree.nodes[i].firstchild->next;
while(p){
hasKids = 1;
//打印所有孩子節點 p->child 孩子節點在數組中的位置
printf("%c ",tree.nodes[p->child].data);
p=p->next;
}
break;
}
}
if(hasKids==0){
printf("此節點爲葉子節點");
}
}
int main()
{
CTree tree;
tree = InitTree(tree);
//默認數根節點位於數組notes[0]處
tree.r=0;
printf("找出節點 F 的所有孩子節點:");
FindKids(tree,'F');
return 0;
}
樹的孩子兄弟表示法
樹結構中,位於同一層的節點之間互爲兄弟節點。例如,圖1中的普通樹中,節點 A、B 和 C 互爲兄弟節點,而節點 D、E 和 F 也互爲兄弟節點。
孩子兄弟表示法,採用的是鏈式存儲結構,其存儲樹的實現思想是:從樹的根節點開始,依次用鏈表存儲各個節點的孩子節點和兄弟節點。
因此,該鏈表中的節點應包含以下 3 部分內容:
節點的值;
指向孩子節點的指針;
指向兄弟節點的指針;
圖4
用 C 語言代碼表示節點結構爲:
#define ElemType char
typedef struct CSNode{
ElemType data;
struct CSNode * firstchild,*nextsibling;
}CSNode,*CSTree;
以圖1爲例,使用孩子兄弟表示法進行存儲的結果如下圖所示:
圖5
由圖5可以看到,節點 R 無兄弟節點,其孩子節點是 A;節點 A 的兄弟節點分別是 B 和 C,其孩子節點爲 D,依次類推。
實現上圖中的 C 語言實現代碼也很簡單,根據圖中鏈表的結構即可輕鬆完成鏈表的創建和使用,因此不再給出具體代碼。
接下來觀察圖 1 和圖 5。圖 1 爲原普通樹,圖5 是由圖 1 經過孩子兄弟表示法轉化而來的一棵樹,確切地說,圖5是一棵二叉樹。因此可以得出這樣一個結論,即通過孩子兄弟表示法,任意一棵普通樹都可以相應轉化爲一棵二叉樹,換句話說,任意一棵普通樹都有唯一的一棵二叉樹於其對應。
因此,孩子兄弟表示法可以作爲將普通樹轉化爲二叉樹的最有效方法,通常又被稱爲"二叉樹表示法"或"二叉鏈表表示法"。