多叉树的设计、建立、层次优先遍历和深度优先遍历

参考:
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掉,一般空间稍微申请大一点点

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值