实验 树的表示
实验内容
实验指导书
实验 树的表示 (对应毕业要求:2.3)
预习要求:复习相关知识,熟悉树的存储与遍历方法。
实验方式:学生通过编写程序调试算法。
实验要求:学生应调试程序能得到正确的执行结果,并能回答与具体实验题目相关的问题。
内容: 从输入文件读入数据,在内存中分别构建树的 “双亲表示”、“孩子兄弟表示”,并在屏幕上输出“凹凸表示”,在输出文件输出“括号表示”
输入文件格式,input.txt
- - - - - - - - - - - - - - - -
25
1,23
2,22
…
- - - - - - - - - - - - - - - -
说明:第一行为顶点数目N,顶点名为1 到 N,且1为root顶点
后面的N-1行,每行表示一条边的两个顶点
屏幕输出,例
1
5
6
7
12
13
15
8
20
…
文件输出,output.txt
(1,(5,(6)(7,(12,13,15))),(8,(20))
程序要点:
1、顶点结构、边结构
Struct vertex{
Int name;
}
Struct edge{
Int v1,v2;
}
2、文件读写 :fopen , fscanf , fprintf, fclose
3、顺序表构建顶点集合、边集合
4、构建双亲表示:
算法描述:
(1)从root开始,遍历边集合找到root的所有孩子节点,并在双亲表示法中为这些节点设置父节点。
(2)对顶点集合中每个节点重复上述“找孩子”操作。
(3)直到已经为N个节点找了孩子,算法结束
5、依据前面双亲表示法中的节点存储顺序,构建孩子兄弟法的二叉树表示
算法描述:
从root开始,依此构建孩子兄弟法。
6、凸凹表示法输出:
(1)从双亲表示法,计算每个节点的层数k
(2)按双亲表示法顺序,依此输出k个tab和节点名
7、文件输出符号表示法
算法描述:
(1)从上面的“孩子兄弟表示法”递归生成每颗子树的描述串。
如对某个节点i,函数i返回以他为根的子树对应的描述串
Void fun(node * root,int i,char * result){
Strcat(result,”(”);
Strcat(result,itoa(i));
//遍历i的孩子,并为每个孩子子树生成描述串
For each child x{
Char buf[1000]=””;
fun(root,x,buf);
Strcat(result,buf);
}
Strcat(result,”)”);
}
树示例:
已知输入文件的内容:
20
1,2
1,3
4,1
5,1
2,15
2,14
2,13
12,3
11,4
9,4
5,6
5,7
5,8
14,16
16,18
16,19
16,17
11,20
11,10
具体实现
关于顶点集和边集
老师这里要求我们采用图形存储我们所接收到的数据
- 顶点集存放所有顶点的名字,用顺序表存放顶点,下标从0开始
- 边集存放着顶点之间的信息,同样用顺序表存放边,表中每个结点由两个数据组成,分别是每条边的两个顶点
- 另外设置两个参数,计算顶点的个数和边的数量
#define N 100
struct Vertex{
int Vname;
};
int VertexCount = 0;
Vertex VertexSet[N];
struct Edge{
int v1;
int v2;
};
int EdgeCount = 0;
Edge EdgeSet[N];
读取数据
- 进行对文件的读取操作
- 在读取文件的过程中对顶点集和边集进行初始化
- 偷偷说一句这个老师真的好爱用文件操作。。。这个难道是以后出去工作的基操(?)
void loadDate(){
FILE *fp;
fp = fopen("TreeInput.txt","r");
if(fp == NULL){
printf("ERROR!");
}
fscanf(fp,"%d\n",&VertexCount);//第一行是顶点个数
int i;
for(i = 0 ; i < VertexCount ; i++){
VertexSet[i].Vname = i+1;//顶点名字从1开始
}
while(!feof(fp)){
fscanf(fp,"%d,%d\n",&EdgeSet[EdgeCount].v1,&EdgeSet[EdgeCount].v2);
EdgeCount++;
}
}
构建双亲表示
- 双亲表示法实际上是用线性表来存放一棵树
- 每个结点都包含两个基本的内容:结点本身的信息Vname以及其双亲结点的下标parentIndex
- 由于下面要采用凸凹法输出这棵树,所以在每个结点的结构体定义中加上一个层数k
/
//建立双亲表示法结点结构
struct parentTree{
int Vname;
int parentIndex;
int k;
};
parentTree parentNode[N];
在前面老师让我们用图形存储了数据,按理说每个结点的遍历就应该用图的方法去遍历,为此我还特意去学习了图的遍历深度优先搜索邻接矩阵存图什么的,但是这节实验之前老师都还没有上到这部分的内容,所以直接就按最朴素的方法去做了
算法描述:
(1)从root开始,遍历边集合找到root的所有孩子节点,并在双亲表示法中为这些节点设置父节点。
(2)对顶点集合中每个节点重复上述“找孩子”操作。
(3)直到已经为N个节点找了孩子,算法结束
-
先用循环遍历给这棵树的每个结点赋值,并且每个结点的双亲下标都先设置一个默认值
-
第一层循环按顶点集遍历每个顶点,设为顶点A
-
第二层循环按顶点集遍历A之后的其他顶点,设为顶点B
-
第三层循环遍历边集的每条边
-
先将根节点的双亲设为-1
-
根节点已经设置了双亲,往下判断一条边的两个顶点哪个是双亲哪个是儿子就看这个顶点的双亲是否有改变
-
如果A和B是这条边的两个顶点,即分别与这条边的v1和v2相等,并且A顶点已经找到了双亲而B顶点还没有,则将A顶点设为B顶点的双亲
-
求层数:这个操作是在网上看到的,觉得很神奇就拿来用了,具体的不知道该怎么描述,直接看代码吧
#define unknow -999
void BuildParentTree(){
int i,j,k;
for(i = 0 ; i < VertexCount ; i++){
parentNode[i].Vname = VertexSet[i].Vname;
parentNode[i].parentIndex = unknow;
}
parentNode[0].parentIndex = -1;
for(i = 0 ; i < VertexCount ; i++){
for(j = 1 ; j < VertexCount ; j++){
for(k = 0 ; k < EdgeCount ; k++){
if(
(EdgeSet[k].v1 == parentNode[i].Vname&&
EdgeSet[k].v2 == parentNode[j].Vname&&
parentNode[i].parentIndex != unknow&&
parentNode[j].parentIndex == unknow)
||
(EdgeSet[k].v1 == parentNode[j].Vname&&
EdgeSet[k].v2 == parentNode[i].Vname&&
parentNode[i].parentIndex != unknow&&
parentNode[j].parentIndex == unknow)
){
parentNode[j].parentIndex = i;
}
}
}
}
//
//求层数
/
for(i = 0 ; i < VertexCount ; i++){
parentNode[i].k = 0;
for(j = i ; parentNode[j].parentIndex != -1;j = parentNode[j].parentIndex){
parentNode[i].k++;
}
}
}
凸凹表示法输出双亲表示法的树
要求要一棵树一棵树地输出,这里就用到了递归
-
一棵树一棵树地输出,本质上就是由一个结点去找它的孩子结点,找到以后输出,接着由这个孩子结点去找它的孩子结点进行输出
-
向函数中传入一个顶点A,如果这个顶点是根节点,则直接输出
-
如果不是根节点,则遍历所有的顶点,找到双亲结点为A的顶点,根据这个顶点的层数k输出相应数量的tab,接着将这个顶点作为参数进行递归,去找这个顶点的其他孩子
void printParentTree(int v){
if(v == 0){
printf("%d\n",parentNode[v].Vname);
}
int i,j;
for(i = 0 ; i < VertexCount ; i++){
if(parentNode[i].parentIndex == v){
for(j = 0 ; j < parentNode[i].k ; j++){
printf("\t");
}
printf("%d\n",parentNode[i].Vname);
printParentTree(i);
}
}
}
构建孩子兄弟二叉树表示
5、依据前面双亲表示法中的节点存储顺序,构建孩子兄弟法的二叉树表示
算法描述:
从root开始,依此构建孩子兄弟法。
-
构建结点
struct BrotherChildTree{ int Vname; BrotherChildTree *firstchild; BrotherChildTree *nextsibling; };
-
思路:遍历每个结点,分别进行找孩子和找兄弟的操作
-
遍历结点——递归
-
找孩子——传入一个结点A,根据双亲表示法,循环遍历每个结点,找到双亲结点为A的结点,新建一个结点P并分配空间,Vname为找到的这个结点的Vname,修改A的孩子指针域指向这个新结点P,新结点的两个指针域全部置空,break结束循环不再往下找孩子,将新结点P传入这个函数进行递归
-
找兄弟——传入一个结点A,根据双亲表示法,循环遍历A之后的结点,找到双亲节点与A相同的结点,新建一个结点P并分配空间,Vname为找到的这个结点的Vname,修改A的兄弟指针域指向这个新结点P,新结点的两个指针域全部置空,break结束循环不再往下找孩子,将新结点P传入这个函数进行递归
void buildBCTree(int index,BrotherChildTree *T){
int tIndex;
int i;
//找孩子
for(i = 0 ; i < VertexCount ; i++){
if(parentNode[i].parentIndex == index){
BrotherChildTree *p = (BrotherChildTree*)malloc(sizeof(BrotherChildTree));
p->Vname = parentNode[i].Vname;
p->firstchild = NULL;
p->nextsibling = NULL;
T->firstchild = p;
T->nextsibling = NULL;
tIndex = i;
break;
}
}
if(T->firstchild != NULL){
buildBCTree(tIndex,T->firstchild);
}
//找兄弟
for(i = index+1 ; i < VertexCount ; i++){
if(parentNode[i].parentIndex == parentNode[index].parentIndex){
BrotherChildTree *p = (BrotherChildTree *)malloc(sizeof(BrotherChildTree));
p->Vname = parentNode[i].Vname;
p->firstchild = NULL;
p->nextsibling = NULL;
T->nextsibling = p;
tIndex = i;
break;
}
}
if(T->nextsibling != NULL){
buildBCTree(tIndex,T->nextsibling);
}
}
括号表示法输出孩子兄弟二叉树
- 原本看到这里以为是要用到广义表输出二叉树,但是实验的时候老师好像还没讲到广义表,所以是真·括号表示法输出
- 每一棵树按照(根,孩子,兄弟)的形式输出
- 同样用递归
- 每传入一个结点就输出一个括号和结点本身:(A
- 当孩子结点不为空时递归传入孩子结点
- 找完孩子以后找兄弟,当兄弟结点不为空时递归传入兄弟结点
- 最后输出括号:)-----这个不是微笑的表情
- 老师要求最后从一整个字符串数组中输出:
- 在主函数定义一个char类型的字符串数组string,在调用函数的时候传进去
- 使用strcat函数进行字符串的拼接:strcat(string,“要拼进去的字符串”)
- 因为每个结点的Vname类型是int,用isoa()函数进行int类型到char*类型的转换,在此之前还要新建立一个字符串数组buf放置转换后的字符:isoa(要转换的int数据,buf,进制)
- 这里有个小插曲,我一开始想着能不能直接把string放到buf的位置直接把转换后的数字放进数组里面,但是在另一个程序尝试了一下,发现如果执行多次isoa()函数,传进函数的那个字符串数组中的内容会被覆盖,所以还是老老实实再新建一个数组放好字符以后拼进主串再转换一个
- 还有在设置buf的大小时,我不想浪费太多的空间,原本只想开一个长度为1的数组,但是在进行char buf[1] = " "的时候报错了,查了以后才知道数组的大小设置至少要比数组元素总和多1,因为数组最后还有个\0占位
void printBCTree(BrotherChildTree *root,char *string){
strcat(string,"(");
char buf[2]=" ";
itoa(root->Vname,buf,10);
strcat(string,buf);
if(root->firstchild != NULL){
strcat(string,",");
printBCTree(root->firstchild,string);
}
if(root->nextsibling != NULL){
strcat(string,",");
printBCTree(root->nextsibling,string);
}
strcat(string,")");
}
源代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define N 100
struct Vertex{
int Vname;
};
int VertexCount = 0;
Vertex VertexSet[N];
struct Edge{
int v1;
int v2;
};
int EdgeCount = 0;
Edge EdgeSet[N];
/
//加载数据
void loadDate(){
FILE *fp;
fp = fopen("TreeInput.txt","r");
if(fp == NULL){
printf("ERROR!");
}
fscanf(fp,"%d\n",&VertexCount);
int i;
for(i = 0 ; i < VertexCount ; i++){
VertexSet[i].Vname = i+1;
}
while(!feof(fp)){
fscanf(fp,"%d,%d\n",&EdgeSet[EdgeCount].v1,&EdgeSet[EdgeCount].v2);
EdgeCount++;
}
}
/
//打印读入的数据
void printDate(){
printf("VertextCount is %d\n",VertexCount);
int i;
for(i = 0 ; i < EdgeCount ; i++){
printf("%d,%d\n",EdgeSet[i].v1,EdgeSet[i].v2);
}
}
/
//建立双亲表示法结点结构
struct parentTree{
int Vname;
int parentIndex;
int k;
};
parentTree parentNode[N];
#define unknow -999
void BuildParentTree(){
int i,j,k;
for(i = 0 ; i < VertexCount ; i++){
parentNode[i].Vname = VertexSet[i].Vname;
parentNode[i].parentIndex = unknow;
}
parentNode[0].parentIndex = -1;
for(i = 0 ; i < VertexCount ; i++){
for(j = 1 ; j < VertexCount ; j++){
for(k = 0 ; k < EdgeCount ; k++){
if(
(EdgeSet[k].v1 == parentNode[i].Vname&&
EdgeSet[k].v2 == parentNode[j].Vname&&
parentNode[i].parentIndex != unknow&&
parentNode[j].parentIndex == unknow)
||
(EdgeSet[k].v1 == parentNode[j].Vname&&
EdgeSet[k].v2 == parentNode[i].Vname&&
parentNode[i].parentIndex != unknow&&
parentNode[j].parentIndex == unknow)
){
parentNode[j].parentIndex = i;
}
}
}
}
//
//求层数
/
for(i = 0 ; i < VertexCount ; i++){
parentNode[i].k = 0;
for(j = i ; parentNode[j].parentIndex != -1;j = parentNode[j].parentIndex){
parentNode[i].k++;
}
}
}
//
//凸凹表示法输出
/
void printParentTree(int v){
if(v == 0){
printf("%d\n",parentNode[v].Vname);
}
int i,j;
for(i = 0 ; i < VertexCount ; i++){
if(parentNode[i].parentIndex == v){
for(j = 0 ; j < parentNode[i].k ; j++){
printf("\t");
}
printf("%d\n",parentNode[i].Vname);
printParentTree(i);
}
}
}
//
//孩子兄弟表示法树的建立
/
struct BrotherChildTree{
int Vname;
BrotherChildTree *firstchild;
BrotherChildTree *nextsibling;
};
void buildBCTree(int index,BrotherChildTree *T){
int tIndex;
int i;
//找孩子
for(i = 0 ; i < VertexCount ; i++){
if(parentNode[i].parentIndex == index){
BrotherChildTree *p = (BrotherChildTree*)malloc(sizeof(BrotherChildTree));
p->Vname = parentNode[i].Vname;
p->firstchild = NULL;
p->nextsibling = NULL;
T->firstchild = p;
T->nextsibling = NULL;
tIndex = i;
break;
}
}
if(T->firstchild != NULL){
buildBCTree(tIndex,T->firstchild);
}
//找兄弟
for(i = index+1 ; i < VertexCount ; i++){
if(parentNode[i].parentIndex == parentNode[index].parentIndex){
BrotherChildTree *p = (BrotherChildTree *)malloc(sizeof(BrotherChildTree));
p->Vname = parentNode[i].Vname;
p->firstchild = NULL;
p->nextsibling = NULL;
T->nextsibling = p;
tIndex = i;
break;
}
}
if(T->nextsibling != NULL){
buildBCTree(tIndex,T->nextsibling);
}
}
//括号表示法输出
///
void printBCTree(BrotherChildTree *root,char *string){
strcat(string,"(");
char buf[2]=" ";
itoa(root->Vname,buf,10);
strcat(string,buf);
if(root->firstchild != NULL){
strcat(string,",");
printBCTree(root->firstchild,string);
}
if(root->nextsibling != NULL){
strcat(string,",");
printBCTree(root->nextsibling,string);
}
strcat(string,")");
}
int main(){
loadDate();
BuildParentTree();
printParentTree(0);
BrotherChildTree *root =
(BrotherChildTree *)malloc(sizeof(BrotherChildTree));
root->Vname = parentNode[0].Vname;
root->firstchild = NULL;
root->nextsibling = NULL;
buildBCTree(0,root);
char string[1000] = " ";
printBCTree(root,string);
printf("%s",string);
}