参考:
http://www.cnblogs.com/unixfy/p/3486179.html
用户的多叉树数据存储在一个文件中,格式如下:
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掉,一般空间稍微申请大一点点