学习"树"(链式)数据结构

花了两天时间学习了思成的“树”,来跟大家做一个分享吧。

先上代码哈

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,一个指向父节点指针,一个指向孩子节点的指针,和一个指向下一个兄弟节点的指针组成。

让我们来看下程序执行的效果:上图!

image

首先我查看了数据为空,然后我先后插入了m,n,o三个节点(注:他们应该分别是树根的子节点,他们互为兄弟:见下图:

image

image

由上面的程序展示可见如下的存储结构

image

 

让我们来首先分析一下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咯。。

 

好,希望大家喜欢我这样细致的解释。。^-^..

posted @ 2012-01-23 22:03  Ю詺菛╀時代  阅读(256)  评论(0编辑  收藏  举报