花了两天时间学习了思成的“树”,来跟大家做一个分享吧。
先上代码哈
tree.h
#ifndef _TREE_H
#define _TREE_H
typedef struct _node
{
void *data;
struct _node * parent;
struct _node * child;
struct _node * next;
}TREE;
int InsertTree(TREE **t,void *data,int size);
int DeleteAllTree(TREE *t);
int DeleteTree(TREE *t);
TREE * GetTreeByKey(TREE *r,void *key,int(*compare)(void *,void *));
int PrintTree(TREE *t,void (*print)(void *));
#endif
testTree.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include "tree.h"
//定义根目录和当前目录
TREE * root = NULL,* curDir = NULL;
//处理生成目录的函数
void ParseMkdir(char * com)//mkdir diralkd
{
if(InsertTree(&curDir,&com[6],strlen(&com[6])+1) == 0)
printf("命令操作失败!]n");
}
int CompareDir(void *data,void *key)
{
return strcmp((char*)data,(char*)key)==0?1:0;
}
//处理删除目录的函数
void ParseRmdir(char * com)
{
TREE *p = GetTreeByKey(root,&com[6],CompareDir);
if(p == NULL)
printf("文件不存在!");
DeleteTree(p);
}
//处理切换目录的函数
void ParseCd(char * com)
{
TREE *p = GetTreeByKey(root,&com[3],CompareDir);
if(p == NULL)
printf("找不到目录!");
else
curDir = p;
}
void PrintDir(void *data){
printf("%20s",(char *)data);
}
//处理查看目录的函数
void ParseLs(char * com)
{
if(PrintTree(curDir,PrintDir) == 0)
printf("没有数据\n");
printf("\n");
}
void main(){
//实现windows、linux目录的一些操作
char str[1024];
InsertTree(&root,"/",2);
curDir = root;
while(1){
//提示可以通过命令做一些操作。
printf("mkdir创建目录;rmdir删除目录;cd切换目录;ls查看目录内容;exit退出\n");
printf(">>: ");
gets(str);
fflush(stdin);
/*函数原型:extern char *strstr(char *str1, char *str2);
功能:找出str2字符串在str1字符串中第一次出现的位置(不包括str2的串结束符)。
返回值:返回该位置的指针,如找不到,返回空指针。*/
//查看输入的内容,实现相应功能
//如果输入了且mkdir在行首,
if(strstr(str,"mkdir") == str)
ParseMkdir(str);//解析dir
else if(strstr(str,"rmdir") == str)
ParseRmdir(str);
else if(strstr(str,"cd") == str)
ParseCd(str);
else if(strstr(str,"ls") == str)
ParseLs(str);
else if(strstr(str,"exit") == str)
{
printf("bye!");
exit(0);
}else
printf("command not find!\n");
}
}
tree.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include"tree.h"
//创建节点,挂给根目录
//首先,要将内容放到根(TREE *)下,
//所以要改变指针变量要通过传如指针的指针实现
//还要传入放进根中的数据,可以传任何数据,
//所以定义为无类型的,但是这就必须输入一个
//这个数据的大小,否则就没法初始化。
int InsertTree(TREE **t,void *data,int size){
//定义新节点
TREE *p = (TREE *)malloc(sizeof(TREE));
TREE *tmp;
if(p == NULL) return 0;
memset(p,0,sizeof(TREE));
p->data = malloc(size);
if(p->data == NULL){
free(p);
return 0;
}
memcpy(p->data,data,size);
//root为空,把新节点插入其下
if(*t == NULL)
*t = p;
//如果它不空,但子节点为空,则插入它子节点下
else if((*t)->child == NULL){
//别忘了给新节点p的父节点赋值
p->parent = *t;
(*t)->child = p;
}else{
p->parent = *t;
//这一层有多个节点的,遍历到这层最后一个节点
tmp = (*t)->child;
while(tmp->next)
tmp = tmp->next;
//遍历到了这层最后节点了,把相应新节点赋值给它
tmp->next = p;
}
return 1;
}
int DeleteAllTree(TREE *t){
TREE *p = NULL;
while(t){
//树非空
if(t->child != NULL){
//把其它的左节点全部挂接到他最左边节点下,
//作为他最左边节点的子节点
p = t->child->child;
while(p->next)
p = p->next;
//p指向要删除节点的子节点的的子节点的最后一个。
p->next = t->child->next;
t->child->next = NULL;
}
p = t->child;
free(t->data);
free(t);
t = p;
}
return 1;
}
int DeleteTree(TREE *t){
if(t == NULL)return 0;
//1.要删除的是根节点,这时候删除整棵树
if(t->parent == NULL)
;
//2.要删除的是最左边的节点,把下一个兄弟节点挂到他的父集的子节点中
else if(t == t->parent->child)
t->parent->child = t->next;
//3.一般情况,找到这个节点前一个
else
{
//找到这个节点的前一个
TREE *p = t->parent->child;//这层最左边一个
while(p->next != t)
p = p->next;
//循环后p指向t的前一个。
p->next = t->next;
}
DeleteAllTree(t);
return 1;
}
//查找树中节点
TREE * GetTreeByKey(TREE *r,void *key,int(*compare)(void *,void *)){
if(r == NULL)
return NULL;
if(compare(r->data,key))
return r;
if(GetTreeByKey(r->child,key,compare))
return GetTreeByKey(r->child,key,compare);
if(GetTreeByKey(r->next,key,compare))
return GetTreeByKey(r->next,key,compare);
return NULL;
}
int PrintTree(TREE *t,void (*print)(void *)){
int i=0;
TREE *p = t->child;
while(p)
{
print(p->data);
i++;
p = p->next;
}
return i;
}
首先介绍下tree.h文件,老规矩,用#ifndef和#endif块包含需要“条件编译”的内容,可以选择是否编译此段代码。在调试中很有用。其次就是数据结构的声明。
struct _node
{
void *data;
struct _node * parent;
struct _node * child;
struct _node * next;
}
树节点是由一个数据域data,一个指向父节点指针,一个指向孩子节点的指针,和一个指向下一个兄弟节点的指针组成。
让我们来看下程序执行的效果:上图!
首先我查看了数据为空,然后我先后插入了m,n,o三个节点(注:他们应该分别是树根的子节点,他们互为兄弟:见下图:
由上面的程序展示可见如下的存储结构
让我们来首先分析一下testTree.c的代码。
在main函数当中,首先我们的目标就是利用“树”这种结构,实现上面演示的那样的类windows和Linux的目录操作。首先插入一个根。字符串“/”占用两个字节分别为/和/0。把它作为根节点,让root指向它。
接着给用户输出一些提示,为了每次输入gets函数获得我们需要的输入,先执行fflush(stdin)清空输入缓冲区,通常是为了确保不影响后面的数据读取(例如在读完一个字符串后紧接着又要读取一个字符,此时应该先执行fflush(stdin);)
然后利用strstr函数extern char *strstr(char *str1, char *str2);返回该位置的指针
找出str2字符串在str1字符串中第一次出现的位置
这里if(strstr(str,"mkdir") == str)的意思是mkdir所在的位置与str所指位置相同(mkdir字符串在行首),说明输入正确,此时调用ParseMkdir(str);函数。
如果用户输入mkdir m,那么ParseMkdir的参数com就="mkdir m"那么它的第六个位置就是指向m的,即com[6]=”m”执行InsertTree(&curDir,&com[6],strlen(&com[6])+1),传入的参数有当前目录,要插入内容"m"和申请TREE节点data域的空间大小strlen(&com[6])+1。应为strlen求的的只会是1,然后给个位置放\0所以就加1咯。。。
下面的应该不难理解,就不再赘述。。。
最后来看核心代码tree.c上图!!
1.理解插入:
2 理解删除:
3.最后再解释下查找树内节点(思成的视频里的代码运不起来,改了下思路)
//查找树中节点
TREE * GetTreeByKey(TREE *r,void *key,int(*compare)(void *,void *)){
if(r == NULL)
return NULL;
if(compare(r->data,key))
return r;
if(GetTreeByKey(r->child,key,compare))
return GetTreeByKey(r->child,key,compare);
if(GetTreeByKey(r->next,key,compare))
return GetTreeByKey(r->next,key,compare);
return NULL;
}
我们先传入树的根root,key是要找的内容,compare是比较TREE的data域是不是和key一样的函数,返回0或1.
首先我们看r的是不是就是要找的节点,是的话直接返回r
然后再找它的子节点(可以看到这里有点深度优先的味道),思成视频里返回的是r->child,试了试不对,改成了return GetTreeByKey(r->child,key,compare); ,那是显然的,递归有几层我们怎么可能知道呢?但我们可以相信,递归返回的一定会是要找的节点,对不对。
最后如果孩子和兄弟,兄弟的孩子孩子的兄弟。。什么都找过了还没找到,那只能返回NULL咯。。
好,希望大家喜欢我这样细致的解释。。^-^..