1. 程式人生 > >多叉樹的設計、建立、層次優先遍歷和深度優先遍歷

多叉樹的設計、建立、層次優先遍歷和深度優先遍歷

使用者的多叉樹資料儲存在一個檔案中,格式如下:
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掉,一般空間稍微申請大一點點