数据结构实验——树与森林实验

一.实验任务

  1. 按先序、后序、层次遍历森林。

    实验测试数据基本要求:

    第一组数据: tree11.tre

    第二组数据: f20.tre

             2、求森林的高度。

        实验测试数据基本要求:

    第一组数据: tree11.tre

    第二组数据: f20.tre

         3、求森林叶子结点数。

        实验测试数据基本要求:

    第一组数据: tree11.tre

    第二组数据: f20.tre

        4、求森林的度。

        实验测试数据基本要求:

    第一组数据: tree11.tre

    第二组数据: f20.tre

        5、先序输出结点值及其层次号。

    例对图7-1所示森林,输出为:(A,1) (B,2) (E,3) (K,4) (F,3) (G,3) (C,2) (H,3) (I,3) (D,2) (J,3) (L,1) (M,2) (N,2) (O,1) (P,2)

        实验测试数据基本要求:

    第一组数据: tree11.tre

    第二组数据: f20.tre

  1. 输出广义表表示的树。

    例对图7-1所示森林,输出为:A( B(E(K),F,G),C(H,I),D(J)),  L(M,N),  O(P) )

        实验测试数据基本要求:

    第一组数据: tree11.tre

    第二组数据: f20.tre

二、数据结构设计

typedef struct csNode

{

    elementType data;

    struct csNode *firstChild, *nextSibling;

}csNode,*csTree;

typedef struct pNode

{

    elementType data;   //结点数据域

    int parent;         //父结点指针(下标)

}PTNode;

    //双亲表示的树(森林)结构

typedef struct pTree

{

    PTNode node[MAXLEN];   //结点数组

    int n;              //结点总数

}pTree;

三、算法设计

    从文件创建双亲表示的森林;从双亲表示的森林创建二叉链表表示的森林:

创建二叉链表表示的树(森林)分为2个步骤,第一步:读取文本文件,创建双亲表示的树(森林);第二步:从双亲表示转换为二叉链表表示的树(森林)。

考虑到文件内容有点不一样。文件扩展名没有限制,程序只根据标识“Tree or Forest”判定是否二叉树数据文件。文件中可以有空行。文件可以有注释行,注释行必须以“//”开始,且注释行必须是单独的行,不能混杂在标识行、数据行中。因此读文件的时候需要自定义一个去除空格的函数,然后再一个循环中,判断,去掉空格的第一个和第二个数据是’/n’和’//’的时候跳过此次循环,当不为空格或者注释的时候,说明空格注释已经遍历过了,到了标识的时候。此时再进行判断是否含有BinaryTree,若有则正确,否则文件格式错误。之后在每次循环的开始首先调用去空格的函数,然后再判断去掉空格的第一个和第二个数据是’/n’和’//’的时候跳过此次循环,若不是则进行读数,将文件数据读入到以双亲存储的森林中。最后关闭文件。之后再利用递归函数将以双亲存储的森林写入以孩子兄弟储存的森林中。结束。

  1. 先序遍历,后序遍历:递归函数遍历子树森林,在递归函数遍历兄弟森林。跟二叉树的先序后序遍历保持一致。

层次遍历森林算法思想:最外层循环处理森林中每一棵树,在二叉链表结构中,森林中的树通过根结点 T 的 nextSibling 指针找到。利用队列,下面算法定义队列元素包含 2 个分量,一 个是结点指针,另一个是结点层次。对每棵树,先将根结点入队。队列不空循环处理,队头 出队,找到队头的所有孩子结点入队,方法是通过 firstChild 找到第一个孩子,再通过此孩 子结点的 nextSibling 找到所有兄弟。孩子结点的层次在父结点(出队结点)层次上加 1。

为了操作方便,此次实验队列用数组模拟。

(2) 森林的高度算法思想:树(森林)以孩子兄弟链表表示。如果树不空,递归求第一个孩子结点为根的子树的高度,然后顺着此结点的右分支(兄弟结点),求每一棵子树的高度,取这些子树高度的最大值h,则树(森林)的高度即为 1+h。

(3)求森林叶子结点数:改造前序遍历算法,增设一个引用参数i为计数器,计算结点数,每次访问根节点时,如果T->firstChild==NULL时,i++。

(4) 求森林的度:改造层次遍历森林算法,增加两个变量min、max初始化皆为0。最外层循环处理森林中每一棵树,在二叉链表结构中,森林中的树通过根结点 T 的 nextSibling 指针找到。利用队列对每棵树,先将根结点入队。队列不空循环处理,队头出队,找到队头的所有孩子结点入队,方法是通过 firstChild 找到第一个孩子,再通过此孩子结点的 nextSibling 找到所有兄弟,并记录兄弟个数保存在min中,如果min>max,把min的值赋给max。最后返回max。

为了操作方便,此次实验队列用数组模拟。

(5) 先序输出结点值及其层次号:改造先序遍历算法,增加一个结点层次参数,当递归遍历 T->firstChild 子树时,结点层次为 T 的层次加 1;递归遍历 T->nextSibling 子树时,结点层次与T相同。输出形式:(结点值,层次)。初始调用 outPreOrder(T,1)。

(6)输出广义表表示的树:改造先序遍历算法完成,访问结点为打印结点值,判断如果 firstChild 不为空,说明结点有子树递归遍历,但在遍历之前先要打印“(”;判断 nextSibling 是否为空, 不空时,要打印“,”,为空时打印“)”表示子树遍历结束。这个算法有个缺点:根结点层 次最后会多出一个“)”,需要手工在调用函数前打印一个“(”,表示整个树(森林)。

#include<iostream>
#include<string.h>
using namespace std;

typedef char elementType;

typedef struct csNode
{
	elementType data;
	struct csNode *firstChild, *nextSibling;
}csNode,*csTree;

//先序遍历 
void Fist(csNode * T){
	if(T){
		cout << T->data << " ";
		Fist(T->firstChild);
		Fist(T->nextSibling);
	}
} 
//后序遍历
void Last(csNode * T){
	if(T){
		Last(T->firstChild);
		cout << T->data << " ";
		Last(T->nextSibling);
	}
}  
//层次遍历 
void LevelOrder(csNode *T)   // 二叉树的层序遍历
{
    csNode *Q[100];   // 数组模拟队列
    int front = 0;
    int rear = 0;
    csNode *p;
    int i = 1;
	while(T){
		cout << "第" << i++ << "颗数:"; 
		Q[++rear] = T;  // 根结点入队
    	while (front != rear) {   // 若队列不为空
        	// 队头结点出队,并访问出队结点
        	p = Q[++front];
        	cout << p->data << " ";
        	// 出队结点的非空左右孩子依次入队
        	while(p->firstChild != NULL) {
            	Q[++rear] = p->firstChild;
            	p->firstChild = p->firstChild->nextSibling;
        	}
        	//front++;
    	}
    	T = T->nextSibling;
    	cout << endl;
	}
    
}
//2求森林的高度
int height(csNode *T){
	int h,h1;
	if(T==NULL)
		return 0;
	else{
		h = 1 + height(T->firstChild); //第一棵子树高度
		h1 = height(T->nextSibling); //下一棵子树高度
		if(h > h1)
			return h;
		else
			return h1;
	}
} 
//3森林结点值
void outLeaf(csNode *T, int &i){
	if(T){
		if(T->firstChild == NULL) //叶子结点,输出
			i++; 
		outLeaf(T->firstChild, i); //递归搜索第一个孩子子树的叶子
		outLeaf(T->nextSibling, i); //递归搜索下一个兄弟树的叶子
	}
}
//4森林的度
int Du(csNode *T){
	csNode *Q[100];   // 数组模拟队列
    int front = 0;
    int rear = 0;
    csNode *p;
    int min = 0;
    int max = 0;
	while(T){
		Q[++rear] = T;  // 根结点入队
    	while (front != rear) {   // 若队列不为空
        	// 队头结点出队,并访问出队结点
        	p = Q[++front];
        	//cout << p->data << " ";
        	// 出队结点的非空左右孩子依次入队
        	while(p->firstChild != NULL) {
            	Q[++rear] = p->firstChild;
            	p->firstChild = p->firstChild->nextSibling;
            	min++;
        	}
        	if(min > max){
        		max = min;
			}
        	min = 0;
    	}
    	T = T->nextSibling;
	}
	return max;
    
}
//5 
void outPreOrder(csNode *T, int level){
	if(T){
		cout << "(" << T->data << "," << level << ") "; //访问根结点
		outPreOrder(T->firstChild, level+1); //T 的孩子层次为 T 的层次加 1	
		outPreOrder(T->nextSibling, level); //T 的兄弟层次与 T 相同
	}
}
//6
void outGList(csNode *T){
		if(T){
			cout<<T->data; //访问根结点
			if(T->firstChild){
				cout<<"("; //T 有孩子结点,打印子表开始标记
				outGList(T->firstChild);
			}
			if(T->nextSibling){
				cout<<","; //T 有兄弟结点,打印“,”分割
				outGList(T->nextSibling);
			}else //T 的兄弟结点遍历结束,打印子表结束标记“)”
				cout<<")"; 
		}
}
//树(森林)的双亲表示定义和算法--------------------------------------------
#define MAXLEN 100

typedef char elementType;
   //树的结点结构
typedef struct pNode
{
	elementType data;   //结点数据域
	int parent;         //父结点指针(下标)

}PTNode;

    //双亲表示的树(森林)结构
typedef struct pTree
{
	PTNode node[MAXLEN];   //结点数组
	int n;              //结点总数
}pTree;


//初始化树
void initialTree(pTree &T)
{
	T.n=0;   //结点数初始化为0
}

//求祖先结点
bool getAncestor(pTree &T, elementType x)
{
	int w=0;
	elementType y;
	y=x;
	
	for(w=0;w<T.n;w++)
	{
		if(T.node[w].data==y)
		{
			w=T.node[w].parent;     //取得x的父结点
			y=T.node[w].data;
			cout<<y<<"\t";
			break;
		}
	}
	if(w>=T.n)    //x不在树上,返回false
		return false;
	
	             //搜索x父结点之外的其它祖先结点
	while(w!=-1)
	{
		if(T.node[w].data==y)
		{
			w=T.node[w].parent;     //取得w的双亲结点下标
			y=T.node[w].data;
			cout<<y<<"\t";	
		}
		else
			w=(w+1)%T.n;
	}
	return true;
}

//求孩子结点
void getChildren(pTree &T, elementType x)
{
	int i,w;
	for(w=0;w<T.n;w++)     //获取x在结点数组中的下标
	{
		if(T.node[w].data==x)
			break;
	}
	if(w>=T.n)  //x不在表中
		return;
	for(i=0;i<T.n;i++)
	{
		if(T.node[i].parent==w)   //找到子结点,打印
			cout<<T.node[i].data<<"\t";	
	}
	cout<<endl;
}

//先序遍历
int firstChild(pTree &T,int v)    //搜索下标为v的结点的第一个孩子结点下标
{
	int w;
	if(v==-1)
		return -1;

	for(w=0;w<T.n;w++)
	{
		if(T.node[w].parent==v)
			return w;
	}
	return -1;
}
int nextSibling(pTree &T,int v,int w)  //搜索v的下标位于w之后的下一个孩子结点下标
{
	int i;
	for(i=w+1;i<T.n;i++)
		if(T.node[i].parent==v)
			return i;
	return -1;
}
void preOrder(pTree &T,int v)
{
	int w;
	cout<<T.node[v].data<<"\t";

	w=firstChild(T,v);
	while(w!=-1)
	{

		preOrder(T,w);
		w=nextSibling(T,v,w);	
	}
}


void preTraverse(pTree &T)
{
	int i;
	int visited[MAXLEN];
	for(i=0;i<T.n;i++)
	{
		visited[i]=0;	
	}
	    //搜索根结点,可能是森林,有多个根结点
	for(i=0;i<T.n;i++)
	{
		if(T.node[i].parent==-1)
			preOrder(T,i);
	}	
}

void postOrder(pTree &T,int v)
{
	int w;
	w=firstChild(T,v);
	while(w!=-1)
	{
		postOrder(T,w);
		w=nextSibling(T,v,w);	
	}
	cout<<T.node[v].data<<"\t";   //访问根结点
}

void postTraverse(pTree &T)
{
	int i;
	int visited[MAXLEN];
	for(i=0;i<T.n;i++)
	{
		visited[i]=0;	
	}
	    //搜索根结点,可能是森林,有多个根结点
	for(i=0;i<T.n;i++)
	{
		if(T.node[i].parent==-1)
			postOrder(T,i);
	}
}

//打印树
void printTree(pTree &T)
{
	int i;
	cout<<"下标\t结点\t双亲"<<endl;
	for(i=0;i<T.n;i++)
		cout<<i<<"\t"<<T.node[i].data<<"\t"<<T.node[i].parent<<endl;
}

//双亲表示定义、算法结束---------------------------------------------------------------
//孩子兄弟链表表示定义、创建算法开始---------------------------------------------------
    //树(森林)的孩子兄弟链表表示

//删除字符串、字符数组左边空格
void strLTrim(char* str)
{
	int i,j;
	int n=0;
	n=strlen(str)+1;
	for(i=0;i<n;i++)
	{
		if(str[i]!=' ')  //找到左起第一个非空格位置
			break;
	}
	    //以第一个非空格字符为手字符移动字符串
	for(j=0;j<n;j++)
	{
		str[j]=str[i];
		i++;
	}
}


//****************** 文件创建双亲表示的树(森林)********************//
//* 函数功能:从文本文件创建双亲表示的图                            *//
//* 入口参数  char fileName[],数据文件名                           *//
//* 出口参数:pTree &T,即创建的树                                  *//
//* 返 回 值:bool,true创建成功;false创建失败                     *//
//* 函 数 名:CreateTreeFromFile(char fileName[], pTree &T)         *//
//* 备注:本函数使用的数据文件格式以边(父子对)为基本数据          *//
//*******************************************************************//
bool CreateTreeFromFile(char fileName[], pTree &T)
{
	FILE* pFile;     //定义顺序表的文件指针
	char str[1000];  //存放读出一行文本的字符串
	char strTemp[10]; //判断是否注释行

    int i=0,j=0;


	pFile=fopen(fileName,"r");
	if(!pFile)
	{
		printf("错误:文件%s打开失败。\n",fileName);
		return false;
	}
	
	while(fgets(str,1000,pFile)!=NULL)  //跳过空行和注释行
	{
		//删除字符串左边空格
		strLTrim(str);
		if (str[0]=='\n')  //空行,继续读取下一行
			continue;

		strncpy(strTemp,str,2);
		if(strstr(strTemp,"//")!=NULL)  //跳过注释行
			continue;
		else  //非注释行、非空行,跳出循环
			break;
	}

    //循环结束,str中应该已经是文件标识,判断文件格式
	if(strstr(str,"Tree or Forest")==NULL)
	{
		printf("错误:打开的文件格式错误!\n");
		fclose(pFile); //关闭文件
		return false;
	}


	//读取结点数据,到str。跳过空行
	while(fgets(str,1000,pFile)!=NULL)
	{
		//删除字符串左边空格
		strLTrim(str);
		if (str[0]=='\n')  //空行,继续读取下一行
			continue;

		strncpy(strTemp,str,2);
		if(strstr(strTemp,"//")!=NULL)  //注释行,跳过,继续读取下一行
			continue;		
		else  //非空行,也非注释行,即图的顶点元素行
			break;
	}

	//结点数据存入树的结点数组		
	char* token=strtok(str," ");
	int nNum=0;	
	while(token!=NULL)
	{
		T.node[nNum].data=*token;
		T.node[nNum].parent=-1;       //父结点指针初始化为-1
		
		token = strtok( NULL, " ");
        
		nNum++;
	}
	
    //循环读取边(父子队)数据
	int nP;  //父结点数组下标
	int nC;  //子结点数组下标

	elementType Nf,Ns; //父子结点对的两个结点
	while(fgets(str,1000,pFile)!=NULL)
	{
		//删除字符串左边空格
		strLTrim(str);
		if (str[0]=='\n')  //空行,继续读取下一行
			continue;
		
		strncpy(strTemp,str,2);
		if(strstr(strTemp,"//")!=NULL)  //注释行,跳过,继续读取下一行
			continue;

		char* token=strtok(str," ");  //以空格为分隔符,分割一行数据,写入邻接矩阵
		
		if(token==NULL)  //分割为空串,失败退出
		{
			printf("错误:读取树的边数据失败!\n");
			fclose(pFile); //关闭文件
			return false;
		}
		Nf=*token;  //获取父结点
		
		token = strtok( NULL, " ");  //读取下一个子串,即子结点
		if(token==NULL)  //分割为空串,失败退出
		{
			printf("错误:读取树的边数据失败!\n");
			fclose(pFile); //关闭文件
			return false;
		}

		Ns=*token;  //获取边的第二个结点(子结点)
            //取得父结点下标		
		for(nP=0;nP<nNum;nP++)
		{
			if(T.node[nP].data==Nf)  //从顶点列表找到第一个顶点的编号
				break;
		}
           //获取子结点的数组下标		
		for(nC=0;nC<nNum;nC++)
		{
			if(T.node[nC].data==Ns)  //从顶点列表找到第二个顶点的编号
				break;
		}

		T.node[nC].parent=nP;        //nC的父结点的下标为nP
	}

    T.n=nNum;  //树的结点数,即顶点数组的实际大小

	fclose(pFile); //关闭文件
	return true;
}

//搜索双亲表示中,下标w的下一个兄弟结点,返回兄弟结点的下标
int next(pTree T,int w)  
{
	int i;
	for(i=w+1;i<T.n;i++)
	{
		if(T.node[w].parent==T.node[i].parent)
			return i;	
	}
	return -1;
}

//递归创建一棵孩子兄弟链表表示的树
void create(csNode *&T,pTree &T1,int v)
{
	int w;
	T=new csNode;
	T->data=T1.node[v].data;
	T->firstChild=NULL;
	T->nextSibling=NULL;
	w=firstChild(T1,v);  //搜索v的第一个孩子结点
	if(w!=-1)
	{
		create(T->firstChild,T1,w);
	}
	
	w=next(T1,v);       //搜索v的下一个兄弟结点
	if(w!=-1)
	{
		create(T->nextSibling,T1,w);
	}	
}

//从双亲表示的树(森林)创建孩子兄弟链表表示的树(森林)
void createCsTree(csNode *&T,pTree T1)
{
	int i;
	//搜索T1的第一个根结点
	for(i=0;i<T1.n;i++)
	{
		if(T1.node[i].parent==-1)   //找到第一个根结点
			break;		
	}
	if(i<T1.n)
		create(T,T1,i);
}


//孩子兄弟链表表示定义、创建算法结束---------------------------------------------------

  • 15
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1. 什么是二叉? 二叉是一种形结构,其中每个节点最多有两个子节点。一个节点的左子节点比该节点小,右子节点比该节点大。二叉通常用于搜索和排序。 2. 二叉的遍历方法有哪些? 二叉的遍历方法包括前序遍历、中序遍历和后序遍历。前序遍历是从根节点开始遍历,先访问根节点,再访问左子,最后访问右子。中序遍历是从根节点开始遍历,先访问左子,再访问根节点,最后访问右子。后序遍历是从根节点开始遍历,先访问左子,再访问右子,最后访问根节点。 3. 二叉的查找方法有哪些? 二叉的查找方法包括递归查找和非递归查找。递归查找是从根节点开始查找,如果当前节点的值等于要查找的值,则返回当前节点。如果要查找的值比当前节点小,则继续在左子中查找;如果要查找的值比当前节点大,则继续在右子中查找。非递归查找可以使用栈或队列实现,从根节点开始,每次将当前节点的左右子节点入栈/队列,直到找到要查找的值或者栈/队列为空。 4. 二叉的插入与删除操作如何实现? 二叉的插入操作是将要插入的节点与当前节点的值进行比较,如果小于当前节点的值,则继续在左子中插入;如果大于当前节点的值,则继续在右子中插入。当找到一个空节点时,就将要插入的节点作为该空节点的子节点。删除操作需要分为三种情况:删除叶子节点、删除只有一个子节点的节点和删除有两个子节点的节点。删除叶子节点很简单,只需要将其父节点的对应子节点置为空即可。删除只有一个子节点的节点,需要将其子节点替换为该节点的位置。删除有两个子节点的节点,则可以找到该节点的后继节点(即右子中最小的节点),将其替换为该节点,然后删除后继节点。 5. 什么是平衡二叉? 平衡二叉是一种特殊的二叉,它保证左右子的高度差不超过1。这种平衡可以确保二叉的查找、插入和删除操作的时间复杂度都是O(logn)。常见的平衡二叉包括红黑和AVL

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值