掌握树的存储结构,熟练使用树遍历算法进行问题求解。
某大学的组织结构如表1所示,要求采用树的孩子链存储结构存储它,并完成如下功能:
(1)从aba.txt文件读数据到R数组中。
(2)由数组R创建树t的孩子链存储结构。
(3)采用括号表示输出树t。
(4)求计算机学院的专业数。
(5)求计算机学院的班数。
(6)求电信学院的学生数。
(7)销毁树。
表1 某大学的组织结构
单位 |
下级单位或人数 |
单位 |
下级单位或人数 |
中华大学 |
计算机学院 |
物联网 |
物联班 |
中华大学 |
电信学院 |
物联班 |
38 |
计算机学院 |
计算机科学 |
电信学院 |
电子信息类 |
计算机学院 |
信息安全 |
电信学院 |
信息工程 |
计算机学院 |
物联网 |
电子信息类 |
电信1班 |
计算机科学 |
计科1班 |
电子信息类 |
电信2班 |
计算机科学 |
计科2班 |
电子信息类 |
电信3班 |
计算机科学 |
计科3班 |
电信1班 |
40 |
计科1班 |
32 |
电信2班 |
38 |
计科2班 |
35 |
电信3班 |
42 |
计科3班 |
33 |
信息工程 |
信息1班 |
信息安全 |
信安1班 |
信息工程 |
信息2班 |
信息安全 |
信安2班 |
信息1班 |
38 |
信安1班 |
36 |
信息2班 |
35 |
信安2班 |
38 |
按照实验要求分析来看树每个节点都可能有着很多个子树,因此采用树的孩子链存储结构存储来存储大学的组织结构更为合适。
如图2-1孩子表示法所示孩子链表存储结构的具体办法:把每个结点的孩子排列起来,以单链表作存储结构,则n个结点有n个孩子链表,如果是叶子结点则此单链表为空。然后n个头指针有组成一个线性表,采用顺序存储结构,存进一个一维数组。
2-1孩子表示法 |
对于该实验我觉得最难的地方在(1)从aba.txt文件读数据到R数组中。(2)由数组R创建树t的孩子链存储结构这两个步骤。
对于(1)从abc.txt文件读数据到R数组中。这个要求,我先是创建了一个含有两个字符数组的结构体,用这个结构体存储abc.txt中的数据,对数据的双亲节点和孩子节点分别存储以便于后续的创建树的过程。对于读取txt文件这个过程我采用的是C语言中的标准库函数fopen()、fscanf()和fclose()。首先需要使用fopen()函数打开txt文件,该函数返回一个指向文件的指针,如果文件不存在则返回NULL。读取文件数据可以使用fscanf()函数,该函数可以按照指定的格式从文件中读取数据并存储到变量中。最后需要使用fclose()函数关闭文件指针,释放文件资源。
如图2-2由数组创建树所示对于(2)由数组R创建树t的孩子链存储结构。该操作是将步骤(1)读取到的数组转化为树的存储形式。数组中的数据分为两个部分,双亲节点和孩子节点,为创建树提供了很大的便利,对于树的创建过程我采用的是递归的方式,每次通过传递参数root也就是数组存放的双亲节点通过循环来查找双亲节点的孩子节点,并为其开辟对应的空间和进行初始化操作。递归结束后函数会返回创建完成的树的根节点。此时表示建树完成。
2-2由数组R创建树t |
2-2由数组R创建树t |
2-3 程序流程图 |
实验还要求计算对应学院的专业数,班级数和人数,其中要有一个通过学院的名字来查找对应的节点位置,以便于计算以该节点为根节点的树的叶子树,节点数等。为了实现这个操作我学会了一个新的函数strcmp,它的功能是功能:比较字符串s1和s2。可以判断这两个字符串是否相同,当字符串相同时也就意味着查找到了对应的节点位置。
如图2-3程序流程图所示读取文本文档存进数组R中,然后根据数组的信息递归建树,输出树,输出计算机学院的专业数,输出计算机学院的班级数,输出电信学院的学生数,销毁树,结束。
如图2-4运行结果为顺序执行以下操作:
(1)从aba.txt文件读数据到R数组中。
(2)由数组R创建树t的孩子链存储结构。
(3)采用括号表示输出树t。
(4)求计算机学院的专业数。
(5)求计算机学院的班数。
(6)求电信学院的学生数。
(7)销毁树。
2-4实验运行结果 |
我对大学的数据统计做了(1)从txt文件读取数据并将其存储在数组中的操作,(2)通过从文件读取的数据来创建树,(3)然后进行了输出树的操作,(4)做了查找树的某一结点的操做用来实现对相应信息的统计,(5)同时也实现了对某一结点的子结点进行了统计操作,为统计计算机学院的班级数和专业数的实现起到了很大作用。(6)最后进行了销毁树的操作。
学会了如何创建一个树的链式结构,并且知道如何输出链式树的各个结点的数据,如何精准的找到一个结点,并求该节点的叶子结点的和,学会了如何计算一个结点的孩子结点的数量,实现以上功能都用到了递归的操作,加深了我对递归的理解,更加清楚的了解了递归的过程。对于实验要求的建树和查找过程中用到的递归我采用了输出每一次递归的运行状况,我清晰的了解了递归的每一步是如何进行。
对于该实验我还复习了许多遗忘的知识,比如对字符串进行复制操作的strcpy ()函数,和比较字符串是否相同的strcmp ()函数。其中strcmp()函数首先将str1字符串的第一个字符的ACSII值减去str2第一个字符的ACSII值(自左向右逐个字符相比,直到出现不同的字符或遇'\0'为止) ,差值为零则继续比较下去;若差值不为零,则返回差值。在文件读取过程中我用到了feof()函数,它的作用是检测流上的文件结束符的函数,如果文件结束,则返回非0值,否则返回0。在计算班级人数时用到了atoi()函数,该会扫描参数字符串,跳过前面的空白字符(例如空格,tab缩进等),直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时才结束转换,并将结果返回。函数返回转换后的整型数;如果参数不能转换成int或者参数为空字符串,那么将返回 0。
4、代码
#include
#include
#include
#include //含有atoi函数,可以将字符串转换成整型数,用于计算班级人数
typedef char ElemType;
#define Max 10 //最大孩子节点
int n; // 用于读取文件时记录读取的行数,也就是树的节点个数
typedef struct node
{
ElemType data[15]; //结点的值
struct node* son[Max]; //指向孩子结点
}Node; //孩子链存储结构中的结点类型
typedef struct
{
char f[15]; //上级
char s[15]; //下级
}RecType;
void ReadFile(RecType R[]) { //读取文件
FILE* fp;
n = 0;
if ((fp = fopen("abc.txt", "r")) == NULL) {
printf("无法打开文件\n");
return;
}
while (!feof(fp)) { //feof()是检测流上的文件结束符的函数,如果文件结束,则返回非0值,否则返回0
fscanf(fp, "%s", &R[n].f);
fscanf(fp, "%s", &R[n].s);
n++;
}
fclose(fp);
}
Node* CreateTree(char root[], RecType R[]) { // 递归建树
Node* t = (Node*)malloc(sizeof(Node));
strcpy(t->data, root); //strcpy,即string copy(字符串复制)的缩写。
for (int k = 0; k < Max; k++) // 初始化当前树的孩子结点数组为空
t->son[k] = NULL;
int i = 0, j = 0;
while (i < n) { //每有一个节点就循环一次,比较双亲是否相同,如果相同就为他创建孩子节点
if (strcmp(R[i].f,root) == 0) { //strcmp(R[i].N==root)则返回0
//strcmp()函数首先将str1字符串的第一个字符的ACSII值减去str2第一个字符的ACSII值(自左向右逐个字符相比,直到出现不同的字符或遇'\0'为止)
//若差值为零则继续比较下去;若差值不为零,则返回差值。
// 递归调用 CreateTree 函数,构建当前孩子结点,并将其赋给当前树的孩子数组
t->son[j] = CreateTree(R[i].s, R);
j++;
}
// 移动到下一个信息
i++;
}
// 返回当前树的根节点
return t; //只返回一次,最后返回时树已建完整
}
void DispTree(Node* t) { //输出树
if (t == NULL) {
printf("树为空");
return;
}
int i = 0;
printf("%s",t->data);
// 如果当前结点有孩子结点
if (t->son[i] != NULL) {
// 输出左括号,表示子树的开始
printf("(");
// 遍历当前结点的孩子结点数组
for (i = 0; i < Max; i++) {
// 递归调用 DispTree 函数,输出当前孩子结点的子树
DispTree(t->son[i]);
// 如果下一个孩子结点不为空,输出逗号分隔符
if (t->son[i + 1] != NULL)
printf(",");
else
break;
}
// 输出右括号,表示子树的结束
printf(")");
}
}
Node* FindNode(Node* t, char name[]) //查找这个名字(name)的节点
{
Node* p;
if (t == NULL)
return NULL;
else
{
if (strcmp(t->data, name) == 0) //名字相同,找到该节点,返回节点
return t;
else
{
for (int i = 0;i < Max;i++) //查找孩子节点
{
if (t->son[i] != NULL) //孩子节点不为空,递归查找
{
p = FindNode(t->son[i], name);
if (p != NULL) //p!=NULL 表示查找成功,如果查找不到会一直查找到NULL时结束
return p;
}
}
return NULL; //找不到,不存在
}
}
}
int ChildCount(Node* t) { // 查找某个结点的孩子数
int count = 0;
for (int i = 0; i < Max; i++) {
if (t->son[i] != NULL)
count++;
else
break;
}
return count;
}
int LeafCount(Node* t) { //求班级数
int count = 0;
if (t == NULL)
return 0;
// 如果当前结点的第一个孩子结点为空,表示当前结点是叶子结点
if (t->son[0] == NULL)
return 1;
// 遍历当前结点的孩子结点数组
for (int i = 0; i < Max; i++) {
// 如果当前孩子结点不为空,递归调用 LeafCount 函数,并将结果加到 count 中
if (t->son[i] != NULL)
count += LeafCount(t->son[i]);
else
break;
}
return count;
}
int LeafSum(Node* t) { //班级人数
int sum = 0;
if (t == NULL)
return 0;
if (t->son[0] == NULL)
return atoi(t->data); //返回节点的data的整数形式,用于计算人数
for (int i = 0; i < Max; i++) {
if (t->son[i] != NULL) {
sum += LeafSum(t->son[i]);
}
else
break;
}
return sum;
}
void DestroyTree(Node*& t) { //销毁树
if (t == NULL) {
printf("树为空\n");
return;
}
for (int i = 0; i < Max; i++) {
if (t->son[i] != NULL)
DestroyTree(t->son[i]);
else
break;
}
free(t);
}
int main() {
RecType R[Max];
Node* t;
ReadFile(R);
t = CreateTree(R[0].f, R);
if (t != NULL) {
printf("建树成功\n");
}
printf("采用括号表示输出树t: \n");
DispTree(t);
printf("\n");
char name1[15] = "计算机学院";
Node* jisuan = FindNode(t, name1);
int count_zhuan = ChildCount(jisuan);
printf("%s的专业数为:%d \n",name1, count_zhuan);
int count_class = LeafCount(jisuan);
printf("%s的班级数为:%d \n",name1,count_class);
char name2[15] = "电信学院" ;
Node* dianxin = FindNode(t, name2);
int sum_stu = LeafSum(dianxin);
printf("%s的学生数为:%d \n",name2,sum_stu);
printf("销毁树t,释放内存空间");
DestroyTree(t);
return 0;
}