多叉樹的設計、建立、層次優先遍歷和深度優先遍歷
阿新 • • 發佈:2019-01-01
使用者的多叉樹資料儲存在一個檔案中,格式如下:
aA 4 g cC z bBbB
z 2 f i
g 1 d
d 3 x e j
每行的第一個元素指定一個節點,第二個元素表示該節點有幾個子節點,緊接著後面跟了幾個子節點;
/*
演算法1:層次優先遍歷多叉樹(佇列)
功能:將多叉樹中的節點按照樹的深度(深度從大到小)進行輸出<正常的層次輸出為深度從小到大>,故要用到棧
*/
/*
演算法2:深度優先遍歷多叉樹(遞迴)
功能:找到從跟節點到葉子節點路徑上節點名字字母個數最大的路徑
*/
實現: 棧的資料結構;佇列的資料結構;多叉樹的資料結構,多叉樹的建立,層次優先遍歷(BFS),深度優先遍歷(DFS< 遞迴>)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX 100+1 //名字的最大字母長度
//定義多叉樹的節點資料結構
typedef struct node_t{
char *name; //節點名
int n_children; //位元組點的個數
int level; //記錄子節點在多叉樹中的層數
struct node_t **children;//指向其自身的子節點,看以將children看成一個數組,數組裡的每個元素為一個node_t指標,指標指向一個node_t結構體物件
}NODE;//NODE為結構體node_t的別名
/*
注意:我們一般自己定義資料結構時,都應該使用typedef struct 結構體名(如果結構體中沒有再次用到此結構體
的型別的話<比如上面的結構體中就再次用到了node_t型別,struct node_t **children;>,其結構體名可以省略,)
{
}類型別名;// 上面的類型別名為NODE,這樣避免每次定義結構體物件時,要寫如stuct node_t a,而是直接下NODE a;
*/
//實現一個棧資料結構
typedef struct stact_t{
NODE **array; //array為一個數組,其元素型別為NODE*指標(用於棧空間,便於動態申請空間)
int index;//指示棧頂元素
int size; //棧大小
}STACK; //定義類型別名
//實現一個佇列資料結構
typedef struct queue_t{
NODE **array; //佇列空間
int head;// 佇列的頭
int tail; //佇列的尾
int num;//佇列中的元素
int size; //佇列的大小
}QUEUE;
//注意上述定義的節點的子節點空間,棧的空間,佇列的空間,都是通過動態陣列實現的,也可以通過連結串列實現
//記憶體分配函式(將malloc封裝一下)
void *util_malloc(int size)
{
void *ptr = malloc(size);
if (ptr == NULL)//如果分配失敗,終止程式
{
printf("Memory allocation failed!\n");
exit(EXIT_FAILURE);
}
return ptr;
}
//對fopen()函式的封裝
FILE *util_fopen(const char *name, const char *access)
{
FILE *fp = fopen(name, access);
if (fp == NULL)
{
printf("Open file %s failed \n", name);
exit(EXIT_FAILURE);
}
return fp;
}
//實現棧的操作
//棧的初始化
STACK *StackInit(int size)
{
STACK *tmp;
tmp = (STACK*)util_malloc(sizeof(STACK));//初始化指向棧結構的指標(任何指標都要初始化,因為指標編譯後只佔4位元組(32位機),用於存放地址,故申請一個記憶體空間,存放結構體的物件,然後這個記憶體空間的地址給到指標)
tmp->size = size;
tmp->index = 0;
tmp->array = (NODE**)util_malloc(size*sizeof(NODE*)); //將void * 型別的指標強制轉化為指向NODE*的指標的指標型別NODE**(即開始的指標是指向void的指標,強制轉化為指向NODE*的指標)
return tmp; //將指向初始化的棧的地址返回
}
//檢查棧是否為空:空返回1,非空返回0
int StackEmpty(STACK *sp)
{
if (sp->index <= 0 || sp == NULL)
return 1;
else return 0;
}
//壓棧
int Push(STACK *sp, NODE *data)
{
if (sp->index >= sp->size || sp == NULL)
{
return 1; //壓棧失敗
}
else
{
sp->array[sp->index++] = data;
return 0;
}
}
//彈棧
int Pop(STACK *sp, NODE **data)
{
if (sp->index <= 0 || sp == NULL)
{
return 1;
}
else
{
*data = sp->array[--sp->index];
return 0;
}
}
//將棧銷燬
void StackDestroy(STACK *sp)
{
free(sp->array);
free(sp);
}
//佇列操作
//佇列初始化
QUEUE *QueueInit(int size)
{
QUEUE *tmp;
tmp = (QUEUE*)util_malloc(sizeof(QUEUE));
tmp->array = (NODE**)util_malloc(size*sizeof(NODE*));
tmp->size = size;
tmp->head = tmp->tail = tmp->num = 0;
return tmp;
}
//檢測佇列為空:1為空,0非空
int QueueEmpty(QUEUE *qp)
{
if (qp->num <= 0 || qp == NULL)
return 1;
else return 0;
}
//入隊
int Enqueue(QUEUE *qp, NODE *data)
{
if (qp->num >= qp->size || qp == NULL)
return 1;//入隊失敗
else
{
qp->array[qp->tail] = data;
qp->tail = (qp->tail + 1) % (qp->size); //迴圈佇列
++qp->num;
return 0;
}
}
//出隊
int Dequeue(QUEUE *qp, NODE **data)
{
if (qp->num <= 0 || qp == NULL)
return 1;
else
{
*data = qp->array[qp->head];
qp->head = (qp->head + 1) % (qp->size); //迴圈佇列
--qp->num;
return 0;
}
}
//銷燬佇列
void QueueDestory(QUEUE *qp)
{
free(qp->array);
free(qp);
}
//生成多叉樹的節點
NODE *CreatNode()
{
NODE *q;
q = (NODE *)util_malloc(sizeof(NODE));
q->name = NULL;
q->level = -1;
q->n_children = 0;
q->children = NULL;
return q;
}
//按節點名字查詢
NODE *SearchNode(const char *name, NODE *head)
{
NODE *tmp = NULL;
int i;
if (head != NULL)
{
if (strcmp(name, head->name) == 0)
tmp = head;
else
{
for (i = 0; i < head->n_children&&tmp==NULL; i++)
{
tmp = SearchNode(name, head->children[i]);//遞迴搜尋,當tmp不為空時,遞迴一層一層向上返回
}
}
}
return tmp;
}
//從檔案中讀取多叉樹資料,並建立多叉樹
void ReadFile(NODE **head, const char *filename)
{
NODE *tmp = NULL;
int i = 0, n = 0;
char name[MAX], child[MAX];
FILE *fp;
fp = util_fopen(filename, "r");
while (fscanf(fp, "%s %d", name, &n) != EOF)
{
if (*head == NULL)
{
tmp = *head = CreatNode();//若為空,生成一個新節點
tmp->name = _strdup(name);//字串賦值函式,strdup函式直接進行字串賦值,不用對被賦值指標分配空間比strcpy用起來方便,但其不是標準庫裡面的函式, 用strdup函式賦值的指標,在最後也是需要free掉的;
}
else
{
tmp = SearchNode(name, *head);//根據name找到節點,這裡預設資料檔案是正確的,一定可以找到與name匹配的節點
}
tmp->n_children = n;
tmp->children = (NODE**)util_malloc(n*sizeof(NODE*));
if (tmp->children == NULL)
{
fprintf(stderr, "Dynamic allocation error !\n");
exit(EXIT_FAILURE);
}
//如果分配成功,則讀取後面的子節點,並存儲
for (i = 0; i < n; i++)
{
fscanf(fp, "%s", child);
tmp->children[i] = CreatNode();//生成子節點
tmp->children[i]->name = _strdup(child);
}
}
fclose(fp);
}
/*
演算法1:層次優先遍歷多叉樹(佇列)
功能:將多叉樹中的節點按照樹的深度(深度從大到小)進行輸出<正常的層次輸出為深度從小到大>,故要用到棧
*/
void Bfs_Tree(NODE *head)
{
NODE *p = NULL;
QUEUE *q = NULL;//定義一個佇列
STACK *s = NULL;//定義一個棧
int i = 0;
q = QueueInit(100);//初始化佇列為100
s = StackInit(100);//初始化棧為100
head->level = 0;// 根節點的深度為0
Enqueue(q, head);//將跟節點入隊
// 對多叉樹中的節點的深度值level進行賦值
// 採用層次優先遍歷方法,藉助於佇列
while (QueueEmpty(q) == 0)//佇列不為空
{
Dequeue(q, &p);//出佇列
for (i = 0; i < p->n_children; i++)
{
p->children[i]->level = p->level + 1; //對子節點深度進行賦值:父節點深度加1
Enqueue(q, p->children[i]);// 將子節點入佇列
}
Push(s, p);//將p入棧,因為輸出的順序為深度從大到小
}
while (StackEmpty(s) == 0)
{
Pop(s, &p);
fprintf(stdout, "%d %s\n", p->level, p->name);
}
//棧和佇列進行銷燬
QueueDestory(q);
StackDestroy(s);
}
/*
演算法2:深度優先遍歷多叉樹(遞迴)
功能:找到從跟節點到葉子節點路徑上節點名字字母個數最大的路徑
*/
void DFS_Tree(NODE *head, char *str,char **iBest)
{
int i = 0;
char *tmp = NULL;
if (head == NULL)
return;
tmp = (char*)util_malloc((strlen(str) + strlen(head->name)+1)*sizeof(char)); //申請空間,注意:此處空間需要+1,存放tmp字串末尾的'\0',如果不這樣的話,則會導致後面free(tmp)出錯
sprintf(tmp, "%s%s", str, head->name); //複習5個printf函式
if (head->n_children == 0)
{
if (*iBest == NULL || strlen(*iBest) < strlen(tmp))
{
free(*iBest); //先銷燬,因為這個空間是strdup分配的,需要釋放
*iBest = _strdup(tmp);
}
}
for (i = 0; i < head->n_children; i++)
{
DFS_Tree(head->children[i], tmp,iBest);
}
free(tmp); //釋放空間
}
//銷燬樹(遞迴銷燬)
void Destoy_Tree(NODE *head)
{
int i;
if (head == NULL)
return;
else
{
for (i = 0; i < head->n_children; i++)
Destoy_Tree(head->children[i]);
free(head->name);//因為name是strdup獲得
free(head->children);//釋放子節點空間
free(head);
}
}
int main(int argc, char **argv)
{
NODE *head = NULL;
char *iBest = NULL;
if (argc != 2)
{
fprintf(stderr, "Lack of parameters!\n");
exit(EXIT_FAILURE);
}
ReadFile(&head, argv[1]);
Bfs_Tree(head);
DFS_Tree(head, "", &iBest);
fprintf(stdout, "%s\n", iBest);
free(iBest);
Destoy_Tree(head);
system("pause");
return 0;
}
總結:
1.程式中(tmp = (char*)util_malloc((strlen(str) + strlen(head->name)+1)*sizeof(char)); //申請空間,注意:此處空間需要+1,存放tmp字串末尾的’\0’,如果不這樣的話,則會導致後面free(tmp)出錯)此處注意
2.free可以釋放空指標(如: p = NULL,free(p);)相當於什麼也沒做,這個也是為什麼一些寫法將p的記憶體釋放後,接著後面跟p = NULL(如 p = malloc(sizeof(…)), free(p),p=NULL),這個主要是避免再次釋放p
3.malloc後一定要手動free掉,一般空間稍微申請大一點點