数据结构学习5.0
1、树的相关概念
1、树的定义:树是n个节点的有限集。当n为空时,称为空树。在任意一棵非空树中应满足:
(1)、有且仅有一个特定的称为根的节点;
(2)、当n>1时,其余节点可分为m个互不相交的有限集T1,T2,T3……,其中每个集合本身又是一棵树,并且称为根的子树。
2、树的特点:除了根节点外,其他节点都只有一个前驱节点,除叶子节点以外,都有一个或多个后继节点。
3、叶子节点(终端节点):没有后继的节点。
4、分支节点(非终端节点):有后继的节点。
5、祖先节点:从当前节点出发一直往上走到根节点,这条路径上的所有节点都是祖先节点。
6、子孙节点:当先节点后的分支上的所有节点
7、双亲节点(父节点):当前节点的直接前驱
8、孩子节点:当前节点的直接后继
9、兄弟节点:有相同的双亲的节点
10、节点的度:节点孩子的个数
11、树的度:树中节点的最大度数
12、节点的层次:从根节点开始为第一层,他的子节点为第二层,以此类推。
13、节点的深度:从根节点开始自顶向下逐层累加的
14、树的高度(深度):树中节点的最大层数。
15、有序树:树中节点的各子树从左到右是有次序的,不能互换,该树为有序树
16、无序树:树中节点的各子树从左到右是无次序的,可以互换。
2、树的代码表示
(1)、用孩子兄弟表示法
#define ElementType char*
struct LinkTreeNode
{
ElementType data;//数据域
struct LinkTreeNode *firstNode;//指向第一个孩子
struct LinkTreeNode *nextSibling;//右兄弟指针
struct LinkTreeNode *parent;//父节点
};
typedef struct LinkTreeNode LTNode;
struct LinkTree
{
LTNode *root;//根节点
};
typedef struct LinkTree LTree;
(2)、创造一个新节点
LTNode* CreateTreeNode(ElementType element)
{
//为新节点申请堆上空间
LTNode* newNode = (LTNode*)malloc(sizeof(LTNode));
if(newNode == NULL)//判断申请是否成功
{
printf("Create malloc error!\n");//失败打印错误信息,并返回空
return NULL;
}
newNode->data = element;//初始化节点的数据为element
newNode->firstChild = NULL;//初始化新节点的孩子兄弟以及父节点都为空
newNode->nextSibling = NULL;
newNode->parent = NULL;
}
(3)、将节点插入树中,与树中节点建立联系
void ConnectBranch(LTNode *parent,LTNode *child)
{
//parent代表要被插入的节点,child代表要插入的节点
if(parent == NULL || child == NULL)//先判断两者其中一个是否为空
{
printf("parent or child is NULL!\n");//为空则两者不需要进行连接
return;
}
child->nextSibling = parent->firstChild;//将父节点的第一个孩子指针指向child的兄弟指针
parent->firstChild = child;//父节点的第一个孩子节点指向child
child->parent = parent;//child的父节点指向parent
}
(4)、断开联系
void DisConnectBranch(LTNode *parent,LTNode *child)
{
//判断父节点、孩子节点以及父节点是否有孩子
if(parent == NULL || child == NULL || parent->firstChild == NULL)
{
printf("Both of them don't have reletionship!\n");//没有说明两者之间没有连接,则不要断开
return;
}
LTNode *TravelPoint = parent->firstChild;//设置一个指针指向第一个孩子节点
if(TravelPoint == child)//判断当前节点是否为要断开连接的节点
{
parent->firstChild = child->nextSibling;//将父节点的第一个孩子节点指向当前要断的节点的兄弟节点
child->parent = NULL;//child的父节点置空
child->nextSibling = NULL;//child的兄弟节点置空
return;
}
while(TravelPoint->next != NULL)//判断当前节点的后继节点是否为空
{
if(TravelPoint->next == child)//判断当前节点的后继节点是否为我们要断开的节点
{
Travel->nextSibling = child->nextSibling;//当前节点的兄弟节点指向要断开的节点的后记节点
child->parent = NULL;//child的父节点置空
child->nextSibling = NULL;//child的兄弟节点置空
return ;
}
TravelPoint = TravelPoint->nextSibling;//继续往后寻找节点
}
}
(5)、初始化树
int InitLTree(LTree *tree)
{
tree->root = CreateTreeNode("");//初始化根节点为空字符
if(tree->root == NULL)//判断根节点是否创建成功
{
return false;//失败返回false
}
else
{
return true;//成功返回true
}
}
(6)、获取树的高度
int GetNodeheight(LTNode *treeNode)//获取当前节点的高度
{
if(treeNode == NULL)//判断当前节点是否为空
{
return 0;//为空返回高度0
}
int height = 0;//定义一个变量记录树的高度
LTNode *TravelPoint = treeNode->firstChild;//从第一个孩子节点开始遍历,寻找子树的高度
while(TravelPoint != NULL)//当前节点不为空
{
int childHeight = GetNodeheight(travelPoint);//定义一个变量记录子树高度
height = height > childHeight ? height : childHeight;//更新最高的子树高度
travelPoint = travelPoint->nextSibling;//指向下一个兄弟节点
}
return height+1;//返回子树的高度加上自身的高度
}
int GetTreeHeight(LTree *tree)//获取树的高度
{
return GetNodeheight(tree->root);//获取根节点的高度即为树的高度
}
(7)、查找某个节点
方法一:函数体内用一个递归函数
LTNode* FindNode(LTNode *node,ElementType element)
{
if(node == NULL)//判断当前节点是否为空
{
return NULL;//为空返回NULL
}
if(strcmp(node->data,element) == 0)//判断当前节点是否为我们所要寻找的值
{
return node;//是返回该节点
}
LTNode *childNode = node->firstChild;//设置一个标志为第一个孩子节点,开始往后查找
LTNode *targetNode = NULL;//用一个函数用来记录递归函数返回的值
while(childNode != NULL)//当前节点不为空
{
targetNode = FindNode(childNode,element);//开始递归调用
if(targetNode != NULL)//判断递归返回的值是否为空
{
return targetNode;//不为空返回targetNode给上一层递归
}
childNode = childNode->nextSibling;//孩子节点往后移指向兄弟
}
return targetNode;//第一个孩子节点为空,返回targetNode NULL指针
}
方法二:函数体内用两个递归函数
LTNode* FindNode(LTNode *node,ElementType element)
{
if(node == NULL)//判断当前节点是否为空
{
return NULL;//为空返回NULL
}
if(strcmp(node->data,element) == 0)//判断当前节点是否为我们所要寻找的值
{
return node;//是返回该节点
}
LTNode *targetNode = NULL;//用一个函数用来记录递归函数返回的值
if((targetNode = FindNode(node->firstChild,element)) != NULL)//判断当前节点的孩子节点返回值是否为空
{
return targetNode;//非空返回该节点
}
if((targetNode = FindNode(node->nextSibling,element) != NULL)//判断当前节点的兄弟节点返回值是否为空
{
return targetNode;//非空返回该节点
}
return targetNode;//返回空节点
}
LTNode *FindTreeNode(LTNode *tree,ElementType element)
{
return FindNode(tree->root,element);//从根节点开始查找
}
(8)、遍历某一节点,实现打印输出一棵树
void TravelTreeNode(LTNode *node,int deepth)
{
if(node == NULL)//判断当前节点是否为空
{
return;//空节点返回空
}
for(int i=0;i<deepth;i++)//打印空格
{
printf(" ");//第0层不打印,第一层打印一次,依次递推实现阶梯的效果
}
if(node->parent != NULL)//判断当前节点是否为根节点
{
printf("|——");//不是则打印输出 |--
}
printf("%s\n",node->data);//打印输出当前节点的值
// LTNode* travelPoint = node->firstChild;//从第一个孩子节点开始依次遍历输出
// while(travelPoint != NULL)//当前节点不为空
// {
// TravelTreeNode(travelPoint);//继续递归遍历输出
// travelPoint = travelPoint->nextSibling;//当它的孩子遍历结束指向它的兄弟节点
// }
TravelTreeNode(node->firstChild,deepth+1);//遍历输出它的孩子节点
TravelTreeNode(node->nextSibling,deepth);//遍历输出它的兄弟节点
}
void TravelLTree(LTree *tree)
{
TravelTreeNode(tree->root,0);//从根节点第0层开始遍历
}
(9)、删除整棵树
void FreeLTNode(LTNode *treeNode)//删除某一节点及其子树和兄弟
{
if(treeNode == NULL)//判断当前节点是否为空
{
printf("treeNode is NULL!\n");//为空则直接返回
return ;
}
FreeLTNode(treeNode->firstChild);//删除当前节点的孩子
FreeLTNode(treeNode->nextSibling);//删除当前节点的兄弟
// LTNode *travelPoint = treeNode->firstChild;//从第一个孩子节点开始依次遍历删除
// while(travelPoint != NULL)//判断标志的那个节点是否为空
// {
// LTNode *nextNode = travelPoint->nextSibling;//设置标志指向它的兄弟节点
// FreeLTNode(travelPoint);//释放当前节点
// travelPoint = nextNode;//指向它的兄弟节点
// }
free(treeNode);//删除它本身
}
void FreeTree(LTree *tree)//删除一棵树
{
FreeLTNode(tree->root);//从根节点开始删除
tree->root = NULL;//将根节点置空
}
主函数实现功能测试
#include"LinkTree.h"
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define true 1
#define false 0
int main()
{
LTree tree;//创建一棵树
InitLTree(&tree);//初始化树
LTNode *node1 = CreateTreeNode("247class");//新建节点
LTNode *node2 = CreateTreeNode("cherie");//新建节点
LTNode *node3 = CreateTreeNode("Alex");//新建节点
LTNode *node4 = CreateTreeNode("Mark");//新建节点
ConnectBranch(node1,node2);//连接节点1,2
ConnectBranch(node1,node3);//连接节点1,3
ConnectBranch(node1,node4);//连接节点1,4
ConnectBranch(tree.root,node1);//连接根节点和节点1
TravelLTree(&tree);//遍历输出节点
printf("height : %d\n",GetTreeHeight(&tree));//输出树的高度
LTNode *classNode = FindTreeNode(&tree,"247class");//查找某个节点
if(classNode != NULL)
{
LTNode *newNode = CreateTreeNode("Susam");//创建一个新节点加入刚刚的找到的节点中
ConnectBranch(classNode,newNode);//连接两者
}
FreeTree(&tree);
return 0;
}
//输出结果
// |--247class
// |--Susam
// |--Mark
// |--Alex
// |--cherie
3、实现Tree的功能
实现功能将当前文件夹或其他路径下的文件以树的形式显示出来;
相关知识补充:
dirent.h
库
三个主要函数:opendir
readdir
closedir
#include <dirent.h>
DIR *opendir(const char *dirname);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);
(1)、opendir
打开目录流,返回一个指向DIR结构体的指针,如果不能打开返回NULL;参数***dirname
**是一个字符数组或者字符串常量
//DIR结构体定义
struct __dirstream
{
void *__fd;
char *__data;
int __entry_data;
char *__ptr;
int __entry_ptr;
size_t __allocation;
size_t __size;
__libc_lock_define (, __lock)
};
typedef struct __dirstream DIR;
(2)、readdir
读取目录,只有一个参数,就是**opendir
**返回的结构体指针。这个函数也返回一个结构体指针 dirent *
;
struct dirent
{
long d_ino; //该文件的索引节点号inode
off_t d_off; //文件在目录中的编移
unsigned short d_reclen; //文件名长,这里的长度并不是指文件大小
unsigned char d_type; //文件类型
char d_name [NAME_MAX+1]; //文件名称
}
(3)、closedir
关闭文件夹
//文件类型
enum
{
DT_UNKNOWN = 0, //未知类型
DT_FIFO = 1, //first in, first out 类似于管道, 有名管道
DT_CHR = 2, //字符设备文件
DT_DIR = 4, //目录
DT_BLK = 6, //块设备文件
DT_REG = 8, //普通文件
DT_LNK = 10, //连接文件
DT_SOCK = 12, //套接字类型
DT_WHT = 14
};
算法思想:要实现Tree的功能,首先,使用 opendir
函数打开目录a,返回指向目录 a 的 DIR 结构体 dir
;其次,调用 readdir(dir)
函数读取目录a下所有文件(包括目录),返回指向目录a的结构体**entry
**,再判断当前结构体的名字是否是根目录是跳过,不是则用一个节点存储当前结构体的名字,将当前节点连接到当前文件夹下的根节点中,继续判断当前文件是否也是一个文件夹,是则继续打开当前文件夹调用递归函数;当前文件夹的路径为:父节点的路径 + / + 文件夹名。
#include"LinkTree.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<dirent.h>
void ExplorDirectory(const char *path,LTNode *parentNode)//parentNode当前文件下的根节点
{
DIR *dir = opendir(path);//打开一个文件流放到DIR中
if(dir == NULL)//判断当前文件夹是否为空
{
return ;
}
struct dirent *entry = NULL;//用entry结构体指针指向目录的结构体
while((entry=readdir(dir)) != NULL)//读取当前文件是否为空
{
//判断当前文件夹名是否为根文件夹
if(strcmp(entry->d_name,".") == 0 || strcmp(entry->d_name,"..") == 0)
{
continue;//支持跳过当前循环,到下一次循环
}
//创建一个新节点存放文件夹名
LTNode *fileNode = CreateTreeNode(entry->d_name);
ConnectBranch(parentNode,fileNode);//连接当前节点和根节点
if(entry->d_type == 4)//判断当前文件是否是一个文件夹
{
char filepath[100]={0};//记录路径
strcpy(filepath,path);//复制父路径
strcat(filepath,"/");//拼接路径
strcat(filepath,entry->d_name);//拼接路径
ExplorDirectory(filepath,fileNode);//继续寻找当前文件夹下文件
}
//entry = readdir(dir);
}
closedir(dir);//关闭文件夹
}
int main(int argc,char *argv[])
{
if(argc != 2)//判断终端输入是否是2个
{
printf("invalid input numbers!\n");//不是则打印错误信息返回0
return 0;
}
LTree FileTree;//初始化一棵树
InitLTree(&FileTree);
FileTree.root->data = argv[1];//根节点为当前输入路径
ExplorDirectory(argv[1],FileTree.root);//调用函数
TravelLTree(&FileTree);//遍历输出
return 0;
}
4、优化树的代码
2中树的代码实现的功能中:树节点存放的数据为字符串型,但在实际应用中要想实现万能树存储不同的数据结构比如想要结构体类型和字符串类型同时存在,则要对2中的代码进行优化,2中我们使用char*
要接住存储的字符串,要想实现存储不同数据类型我们可以使用 void*
万能指针来接住所存储的数据。但是我们再用void*
万能指针去接数据的时候,一些对数据的调用的函数会出现问题,比如树的初始化函数、查找节点函数以及遍历函数……我们都要对其进行修改。
//LinkTree.h中的修改,添加结构体存储万能指针以及万能指针所接数据类型,用结构体指针接数据类型
#define ElementType struct UniversalType
enum Type//枚举可能出现的类型
{
charPoint,//字符型指针
intPoint,//整型指针
structPoint//结构体型指针
};
struct UniversalType
{
void *value;//万能指针
enum Type type;//万能指针所接数据类型
};
初始化函数的修改:原来初始化函数对根节点的初始化是直接赋值为空字符串,但现在根节点所指数据类型变成了自定义的结构体类型struct UniversalType
,所以传入CreatetreeNode
参数的时候变成了struct UniversalType
,所以我们要对其修改定义一个结构体将其传入
int InitLTree(LTree *tree)
{
struct UniversalType typedata = {NULL,-1};//初始化void*为空,enum枚举型为-1
tree->root = CreateTreeNode(typedata);//对根节点初始化
if(tree->root == NULL)//判断根节点是否创建成功
{
return false;//失败返回false
}
else
{
return true;//成功返回true
}
}
查找节点的修改:原来查找节点的函数是根据传入的参数 element
然后使用字符串比较函数strcmp
进行对比,但是现在传入的数据类型为 void*
型,void *
类型不可直接解引用操作,所以我们并不能对里面的数据进行处理,使用需要我们将数据处理部分用回调函数写成一个接口,由主函数用户决定怎么去处理具体的数据比较。
LTNode *FindTreeNode(LTree *tree,ElementType element, int (*func)(ElementType,ElementType))
{
return FindNode(tree->root,element,func);//从根节点对数据进行查找,传入参数
}
LTNode* FindNode(LTNode *node,ElementType element,int (*func)(ElementType,ElementType))
{
if(node == NULL)//判断当前节点是否为空
{
return NULL;//为空返回NULL
}
if(func != NULL && func(node->data,element) == true)//判断回调函数是否为空,并且对数据的查找返回的是否符合我们想要的值,具体函数功能的实现我们并不知道
{
return node;//符合则返回该节点
}
LTNode *childNode = node->firstChild;//设置一个标志为第一个孩子节点,开始往后查找
LTNode *targetNode = NULL;//用一个函数用来记录递归函数返回的值
if((targetNode = FindNode(node->firstChild,element,func)) != NULL)//判断当前节点的孩子节点返回值是否为空
{
return targetNode;//非空返回该节点
}
if((targetNode = FindNode(node->nextSibling,element,func)) != NULL)//判断当前节点的兄弟节点返回值是否为空
{
return targetNode;//非空返回该节点
}
return targetNode;//第一个孩子节点为空,返回targetNode NULL指针
}
遍历树函数的修改:和查找函数类似,修改对数据处理的部分即可
void TravelTreeNode(LTNode *node,int deepth,void(*func)(ElementType))
{
if(node == NULL)//判断当前节点是否为空
{
return ;//空节点返回;
}
if(func != NULL)//判断回调函数是否为空
{
func(node->data);//对数据进行处理
}
TravelTreeNode(node->firstChild,deepth+1,func);//遍历输出它的孩子节点
TravelTreeNode(node->nextSibling,deepth,func);//遍历输出它的兄弟节点
}
void TravelLTree(LTree *tree,void (*func)(ElementType))
{
TravelTreeNode(tree->root,0,func);//从根节点第0层开始遍历
}
在主函数中增加相应的数据处理接口
#include"LinkTree.h"
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define true 1
#define false 0
struct student//设置学生结构体想要加入树中
{
int id;//学号
char *name;//姓名
int score;//分数
};
//创建学生对象,在堆上申请空间判断是否成功
struct student* CreateStudent(int id,const char *name,int score)
{
struct student* stu = (struct student*)malloc(sizeof(struct student));
if(stu == NULL)//判断申请是否成功
{
return NULL;//失败返回0
}
stu->id = id;//初始化学生的学号
stu->name = (char *)name;//姓名
stu->score = score;//分数
}
void FreeStudent(struct student* stu)//释放结构体
{
if(stu == NULL)//判断是否已经释放
{
return ;//已经释放返回
}
free(stu);//释放
}
void Print(ElementType data)//打印函数
{
if(data.type == structPoint)//判断当前数据类型是否为结构体类型
{
//定义一个指针指向数据的value,由于value是void*型,若将void *类型对其他类型赋值,需要将void * 类型进行强制类型转换;
struct student *stu = (struct student *)data.value;
if(stu != NULL)//判断当前指针是否为空
{
printf("name: %s ,id: %d ,score: %d \n",stu->name,stu->id,stu->score);//不为空打印具体的数据
}
}
else//判断当前数据类型是否为其他类型
{
if(data.type == charPoint)//判断当前数据类型是否为字符串类型
{
char *temp = (char *)data.value;//定义一个指针指向数据,原理和上面类似
printf("%s\n",temp);//打印数据
}
}
}
//判断连两个数据是否相同
int IsEqual(ElementType data1,ElementType data2)
{
//判断两个数据类型是否相同且都是结构体型
if(data1.type == structPoint && data2.type == structPoint)
{
//定义两个结构体指针指向数据的value,由于value是void*型,若将void *类型对其他类型赋值,需要将void * 类型进行强制类型转换;
struct student *stu = (struct student *)data1.value;
struct student *stu1 = (struct student *)data2.value;
if(stu->id == stu1->id)//判断两者学号是否相同,相同则两个结构体相同
{
return true;//相同返回true
}
else
{
return false;//不同返回false
}
}
else
{
//判断两个数据类型是否相同且都是字符串型
if(data1.type == charPoint && data2.type == charPoint)
{
//定义两个字符指针指向数据的value,由于value是void*型,若将void *类型对其他类型赋值,需要将void * 类型进行强制类型转换;
char *temp = (char *)data1.value;
char *temp1 = (char *)data2.value;
if(strcmp(temp,temp1) == 0)//判断两个字符串是否相同,相同则返回0
{
return true;//相同返回true
}
return false;//不同返回false
}
}
}
int main()
{
LTree tree;//创建一棵树
InitLTree(&tree);//初始化树
struct UniversalType TypeValue;//初始化节点类型
TypeValue.value = "247class";//节点内容
TypeValue.type = charPoint;//节点类型
LTNode *node1 = CreateTreeNode(TypeValue);//创建节点
TypeValue.value = CreateStudent(1,"Lisa",100);//节点内容
TypeValue.type = structPoint;//节点类型
LTNode *node2 = CreateTreeNode(TypeValue);//创建节点
TypeValue.value = CreateStudent(2,"Tom",90);//节点内容
TypeValue.type = structPoint;//节点类型
LTNode *node3 = CreateTreeNode(TypeValue);//创建节点
ConnectBranch(node1,node2);//连接节点1,2
ConnectBranch(node1,node3);//连接节点1,3
ConnectBranch(tree.root,node1);//连接根节点和节点1
TravelLTree(&tree,Print);//遍历树并打印输出
TypeValue.type = charPoint;//节点类型
TypeValue.value = "247class";//节点内容
LTNode *findNode = FindTreeNode(&tree,TypeValue,IsEqual);//查找节点数据为247class的节点
if(findNode != NULL)//判断是否查找到
{
char *className = "243class";//修改节点数据为243class
findNode->data.value = className;
}
TravelLTree(&tree,Print);//遍历数并打印输出
TypeValue.type = structPoint;//节点类型
TypeValue.value = CreateStudent(1,"",0);//节点内容
LTNode *findNode1 = FindTreeNode(&tree,TypeValue,IsEqual);//查找节点数据中学号为1的节点
if(findNode != NULL)//判断是否查找到
{
//定义一个结构体指针指向节点的数据,由于value是void*型,若将void *类型对其他类型赋值,需要将void * 类型进行强制类型转换;
struct student *stu = (struct student*)findNode1->data.value;
stu->score = 60;//修改1号学生的分数为60
}
free(TypeValue.value);//释放申请的临时变量
TravelLTree(&tree,Print);//打印输出修改后的树
return 0;
}