树与森林(C++实现)

一、实验要求

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

   (2)求森林的高度。

   (3)求森林结点总数。

   (4)求森林叶子结点数。

   (5)求森林的度。

   (6)先序输出结点值及其层次号。

   (7)输出广义表表示的树。

二、数据结构设计

typedef char elementType;
typedef struct csNode
{
	elementType data;					//数据域
	struct csNode *firstChild;		//孩子指针
struct csNode *nextSibling;		//兄弟指针
}csNode;

三、算法设计

  1. 层次遍历森林算法思想:最外层循环处理森林中每一棵树,在二叉链表结构中,森林中的树通过根结点 T nextSibling 指针找到。利用队列对每棵树,先将根结点入队。队列不空循环处理,队头出队,找到队头的所有孩子结点入队,方法是通过 firstChild 找到第一个孩子,再通过此孩子结点的 nextSibling 找到所有兄弟。
  2. 森林的高度算法思想:假定树(森林)以孩子兄弟链表表示。如果树不空,递归求第一个孩子结点为根的子树的高度,然后顺着此结点的右分支(兄弟结点),求每一棵子树的高度,取这些子树高度的最大值 h,则树(森林)的高度即为 1+h
  3. 森林结点总数算法思想:改造先序遍历算法,增加一个计算结点个数的变量NodeNum,初始化为0。每次访问根节点NodeNum++。
  4. 森林叶子结点数算法思想:改造前序、后序遍历算法,增加一个计算结点数的变量leafNodeNum,每次访问根节点时,如果T->firstChild==NULL时,leafNodeNum++
  5. 森林的度算法思想:改造层次遍历森林算法,增加两个变量min、max初始化皆为0。最外层循环处理森林中每一棵树,在二叉链表结构中,森林中的树通过根结点 T nextSibling 指针找到。利用队列对每棵树,先将根结点入队。队列不空循环处理,队头出队,找到队头的所有孩子结点入队,方法是通过 firstChild 找到第一个孩子,再通过此孩子结点的 nextSibling 找到所有兄弟,并记录兄弟个数保存在min中,如果min>max,把min的值赋给max。最后返回max。
  6. 先序输出结点值及其层次号算法思想:改造先序遍历算法,增加一个结点层次参数,当递归遍历 T->firstChild子树时,结点层次为 T 的层次加 1;递归遍历 T->nextSibling 子树时,结点层次与 T 相同。输出形式:(结点值,层次)。初始调用 outPreOrder(T,1)。假定采用二叉链表结构。
  7. 输出广义表算法思想:改造先序遍历算法完成,访问结点为打印结点值,判断如果 firstChild不为空,说明结点有子树递归遍历,但在遍历之前先要打印“(;判断 nextSibling 是否为空,不空时,要打印“,,为空时打印“)表示子树遍历结束。这个算法有个缺点:根结点层次最后会多出一个“),需要手工在调用函数前打印一个“(,表示整个树(森林)。

四、代码实现

#ifndef _CREATETREE_H_
#define _CREATETREE_H_
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;
//树(森林)的双亲表示定义和算法--------------------------------------------
#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;            //w1上移
	}
	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)     //搜索下标(parent)为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;
}

//双亲表示定义、算法结束---------------------------------------------------------------

//孩子兄弟链表表示定义、创建算法开始---------------------------------------------------
//树(森林)的孩子兄弟链表表示
typedef char elementType;

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

//删除字符串、字符数组左边空格
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)         *//
//* 备注:本函数使用的数据文件格式以边(父子对)为基本数据          *//
//*******************************************************************//
int 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);         //将str中的前两个字符拷贝到strTemp中
		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);
}


//孩子兄弟链表表示定义、创建算法结束---------------------------------------------------
#endif // _CREATETREE_H_
#ifndef _OPERATETREE_H
#define _OPERATETREE_H
#include <iostream>
#include "createTree.h"
#include <queue>

using namespace std;

//操作菜单
void menu()
{
    cout<<"*************************************************"<<endl;
    cout<<"0退出并销毁森林"<<endl;
    cout<<"1数据文件创建森林"<<endl;
    cout<<"2按先序、后序、层次遍历森林"<<endl;
    cout<<"3求森林的高度"<<endl;
    cout<<"4求森林结点总数"<<endl;
    cout<<"5求森林叶子结点数"<<endl;
    cout<<"6求森林的度"<<endl;
    cout<<"7先序输出结点值及其层次号"<<endl;
    cout<<"8输出广义表表示的树"<<endl;
    cout<<"*************************************************"<<endl;
}
//先序遍历森林
void perOrderTraverse(csNode *T)
{
    if(T)
    {
        cout<<T->data<<" ";           //访问根节点
        perOrderTraverse(T->firstChild);  //递归调用先序遍历左子树
        perOrderTraverse(T->nextSibling);  //递归调用先序遍历右子树
    }
}
//后序遍历森林
void postOrderTraverse(csNode *T)
{
    if(T)
    {
        postOrderTraverse(T->firstChild);  //递归调用先序遍历左子树
        postOrderTraverse(T->nextSibling);  //递归调用先序遍历右子树
        cout<<T->data<<" ";            //访问根节点
    }
}
//层次遍历森林
void levelOrderTraverse(csNode *T)
{
    queue<csNode *> q;
    csNode * u,*n,*p;
    if(T==NULL)
    {
        return;
    }
    n=T;
    while(n)
    {
        p=n;
        q.push(p);
        while(!q.empty())
        {
            p=q.front();
            cout<<p->data<<" ";
            u=p->firstChild;
            while(u)
            {
                p=u;
                q.push(p);
                u=u->nextSibling;
            }
            q.pop();
        }
        n=n->nextSibling;
    }
}
//(2)求树的高度
int height(csNode *T)
{
    int h,h1;
    csNode *p;
    if(T==NULL)
    {
        return 0;
    }
    else{
        p=T->firstChild;   //T->firstChild
        h=height(p);        //求根节点第一个孩子节点为根的子树高度
        if(p)
            p=p->nextSibling;
        while(p)
        {
            h1=height(p);   //求其兄弟节点的高度
            if(h<h1)
                h=h1;       //h保存最高子树高度
            p=p->nextSibling;
        }
        return 1+h;         //返回森林的高度
    }
}
//求森林的高度
int forestHeight(csNode *T)
{
    int min,max=0;           //max初始化为0
    csNode *p;
    p=T;
    min=height(p);
    while(p)                //循环控制访问其他树的根节点
    {
        if(min>max)
            max=min;        //保存各树的最大高度
        p=p->nextSibling;
        min=height(p);      //返回最大高度
    }
    return max;
}
//(3)求森林结点总数
void getNodeNumber(csNode *T,int &NodeNum)
{
    if(T)
    {
        NodeNum++;
        getNodeNumber(T->firstChild,NodeNum);
        getNodeNumber(T->nextSibling,NodeNum);
    }
}
//(4)求森林叶子结点数
void leafNodeNum(csNode *T,int &leafNum)
{
    if(T)
    {
        if(T->firstChild==NULL)
            leafNum++;
        leafNodeNum(T->firstChild,leafNum);
        leafNodeNum(T->nextSibling,leafNum);
    }
}
//(5)求森林的度
int degreeOfForest(csNode *T)
{
    queue<csNode *> q;
    csNode * u,*n,*p;
    int min=1,max=0;
    if(T==NULL)
    {
        return 0;
    }
    n=T;
    while(n)
    {
        p=n;
        q.push(p);
        while(!q.empty())
        {
            p=q.front();
            u=p->firstChild;
            while(u)
            {
                p=u;
                q.push(p);
                u=u->nextSibling;
                min++;
            }
            if(min>max)
            {
                max=min;
            }
            min=1;
            q.pop();
        }
        n=n->nextSibling;
    }
    return max-1;
}
//(6)先序输出结点值及其层次号
void outPreOrder(csNode *T,int level)
{
    if(T)
    {
        cout<<"("<<T->data<<","<<level<<")"<<" ";
        outPreOrder(T->firstChild,level+1);
        outPreOrder(T->nextSibling,level);
    }
}
//(7)输出广义表表示的树
void outGList(csNode *T)
{
    if(T)
    {
        cout<<T->data;
        if(T->firstChild)
        {
            cout<<"(";
            outGList(T->firstChild);
        }
        if(T->nextSibling)
        {
            cout<<",";
            outGList(T->nextSibling);
        }
        else{
            cout<<")";
        }
    }
}
//销毁森林
void destroy(csNode *&T)
{
    if(T)
    {
        destroy(T->firstChild);
        destroy(T->nextSibling);
        delete T;
    }
}
#endif // _OPERATETREE_H
#include <iostream>
#include <Cstdlib>
#include "createTree.h"
#include "OperateTree.h"

using namespace std;

int main()
{
    pTree T1;
    csNode *T;
    int i;
    char fileName[100];         //保存文件名
    int high;                   //保存树的高度
    int NodeNum=0;              //保存节点个数,初始化为0
    int leafNum=0;              //保存叶子个数,初始化为0
    int degree;                 //保存森林的度
    int level=1;                //森林层数,初始化为1
    menu();
    cout<<"请输入执行序号:";
    cin>>i;
    while(i)
    {
        switch(i)
        {
            case 1:
                cout<<"请输入打开的文件名:";
                cin>>fileName;
                if(CreateTreeFromFile(fileName, T1))
                {
                        cout<<"数据处理完毕!"<<endl;
                }
                createCsTree(T,T1);
            break;
            case 2:
                cout<<"先序序列为:";
                perOrderTraverse(T);
                cout<<endl;
                cout<<"后序序列为:";
                postOrderTraverse(T);
                cout<<endl;
                cout<<"层次序列为:";
                levelOrderTraverse(T);
                cout<<endl;
            break;
            case 3:
                high=forestHeight(T);
                cout<<"树的高度为:"<<high<<endl;
            break;
            case 4:
                getNodeNumber(T,NodeNum);
                cout<<"节点总数为:"<<NodeNum<<endl;
            break;
            case 5:
                leafNodeNum(T,leafNum);
                cout<<"叶子节点总数为:"<<leafNum<<endl;
            break;
            case 6:
                degree=degreeOfForest(T);
                cout<<"森林的度为:"<<degree<<endl;
            break;
            case 7:
                outPreOrder(T,level);
                cout<<endl;
            break;
            case 8:
                cout<<"广义表为:"<<"(";
                outGList(T);
                cout<<endl;
            break;
        }
        system("PAUSE");
        system("CLS");
        menu();
        cout<<"请输入执行序号:";
        cin>>i;
    }
    destroy(T);
    return 0;
}

五、运行和测试

六、实验截图

发布了46 篇原创文章 · 获赞 48 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_39559641/article/details/89069842
今日推荐