花了两天时间学习了思成的“树”,来跟大家做一个分享吧。
先上代码哈
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咯。。
好,希望大家喜欢我这样细致的解释。。^-^..