课内实验记录|二叉树的基本操作(非递归与递归遍历,结点计数)

二叉树的基本操作

一、实验目的

1、掌握用指针类型描述、访问和处理二叉树的运算;
2、掌握二叉树的结构特征,以及各种存储结构的特点及适用范围;
3、熟练掌握递归程序设计方法、以及栈的应用

二、实验内容

以二叉链表作存储结构,编写程序,实现如下的功能:
1、根据输入的数据建立一个二叉树;
2、分别采用前序、中序、后序的遍历方式显示输出二叉树的遍历结果
3、采用非递归的编程方法,分别统计二叉树的节点个数、度为1、度为2和叶子节点的个数,以及数据值的最大值和最小值。
4、(选作内容)试编写按层次顺序遍历二叉树的算法。参考算法思想:要采用一个队列q,先将二叉树根结点入队列,然后退队列,输出该结点;若它有左子树,便将左子树根结点入队列;若它有右子树,便将右子树根结点入队列,直到队列空为止。因为队列的特点是先进先出,从而达到按层次顺序遍历二叉树的目的。

三、 实验要求

1.认真阅读和掌握本实验的算法。
2.上机将本算法实现。
3.在程序的编写中尽量与专业的编程规范靠拢,系统代码采用结构化的编程方式,力求设计代码以及注释等规范,
4.保存和打印出程序的运行结果,并结合程序进行分析。

四、实验报告

1、需求分析
(1)程序设计任务

设计一个程序:
a.以先根的顺序创建一个二叉树;
b.完成递归和非递归操作的四大遍历:先序遍历,中序遍历,后序遍历,层次遍历;
c.根据非递归操作实现对所有结点、叶子结点,度为1的结点、度为2的结点的计数并输出。
d.统计结点数据的最大值和最小值

(2)输入值和输入范围

a.结点的输入
若结点非空,则输入一个Char类型的字符;若结点为空,则输入‘#’字符表示,以先根的顺序进行输入(先输入根节点);
如:
A B C ## D E #G ## # F ##
其代表的二叉树为
在这里插入图片描述
b.输入范围
char类型字符或‘#’字符。

(3)输出形式

a.遍历输出
以一个无重复的字符串输出,其代表遍历的先后访问顺序,分递归和非递归输出,其中递归与非递归结果应该一致。
b.结点计数输出
直接对应输出每类结点的数。
c.最大值和最小值输出
输出结点值的最大值和最小值
如:ABC##DE#G###F##的输出结果为
在这里插入图片描述

2、程序设计
(1)抽象数据类型定义

a.树的结构体的定义
typedef struct tree{
char data;
struct tree *lchild;
struct tree *rchild;
int count;
}Tree;
b.栈结构的实现:用于非递归的先序,中序,后序遍历
c.队列结构的实现:用于层次遍历

(2)主程序流程

//方法调用
提示输入树的结点
Tree *t = createTree(t);
调用函数输出每类结点的值
调用函数输出结点值的最大值和最小值
调用函数输出递归遍历的结果
调用函数输出非递归遍历的结果

(3)详细设计(源代码)
#include<iostream>
#include<stdio.h>
#include<malloc.h>
#include<stack>
#include<queue>
using namespace std;

//树的结构体
typedef struct tree{
	public:
	char data;
	struct tree *lchild;
	struct tree *rchild;
	int count;
}Tree;

//递归创建二叉树
Tree *createTree(Tree *t){
	char ch;
	cin >> ch;
	if(ch == '#')t = NULL;
	else{
		if(!(t = (Tree*)malloc(sizeof(Tree))))exit(-1);
		t->data = ch;
		t->lchild = createTree(t->lchild);
		t->rchild = createTree(t->rchild); 
	}
	return t;
} 
//递归进行先序,中序,后序遍历
//递归先序遍历 
void dpreOrder(Tree *t)
{
	if(t != NULL){
		cout << t->data; 
		dpreOrder(t->lchild);
		dpreOrder(t->rchild);
	}
}
//递归中序遍历
void dinOrder(Tree *t)
{
	if(t != NULL){
		dinOrder(t->lchild);
		cout << t->data; 
		dinOrder(t->rchild);
		
	}
}
//递归后序遍历
void dpostOrder(Tree *t)
{
	if(t != NULL){
		dpostOrder(t->lchild);
		dpostOrder(t->rchild);
		cout << t->data; 
	}
}
//非递归进行先序,中序,后序遍历
//非递归先序遍历
void  fpreOrder(Tree *t){
	//以下均使用栈的C++库函数,简化代码 
	stack<Tree*>s1;
	//当t非空且栈非空时进行遍历 
	while(t || !s1.empty()){
		//当t非空时,先进行访问,再进行处理 
		while(t)
		{
			//先访问根节点内容 
			cout << t->data;
			s1.push(t);
			//转到左子树 
			t = t->lchild;
		}
		//当t空时,将栈顶元素弹出,转到右子树进行同样操作 
		if(!s1.empty())
		{
			t = s1.top();
			s1.pop();
			t = t->rchild;
		}
	}
}

//非递归中序遍历
void finOrder(Tree *t){
	stack<Tree*>s2;
	//思路和先序基本一致
	//判断条件 
	while(t || !s2.empty()){
		//当t非空时,将根节点存栈,转到左子树,只到左子树空 
		if(t){
			s2.push(t);
			t = t->lchild;
		} 
		//当t空时,栈顶结点左子树为空,弹出并访问后转到右子树 
		else{
			t = s2.top();
			s2.pop();
			cout << t->data;
			t = t->rchild; 			 
		}
	}
}
//非递归后序遍历 
void fpostOrder(Tree *t){ 
	stack<Tree*>s3;
	//由于根节点要访问三次,所以先规定一个计数器,当访问到三次时访问结束,输出该节点的值 
	t->count = 1;
	//先存入根节点,便于下面的条件判断 
	s3.push(t);
	    //条件判断 
		while(t || !s3.empty()){
			//弹出栈顶元素进行处理 
			t = s3.top();
			s3.pop();
			if(t->count == 1){
				//如果计数器显示1,为最开始访问,转到左子树,即将左子树的根节点计数器初始化并存栈 
				t->count++;
				s3.push(t);
				if(t->lchild){
					t->lchild->count = 1;
					s3.push(t->lchild);
				}
			}
			else if(t->count == 2){
				//如果计数器显示2,为第二次访问,转到右子树,即将右子树的根节点计数器初始化并存栈 
				t->count++;
				s3.push(t);
				if(t->rchild){
					t->rchild->count = 1;
					s3.push(t->rchild);
				}
			}
			else{
				//如果计数器显示3,访问结束,输出该节点的值 
				cout << t->data;
			}
		}
	}
	
//以下计数均以非递归中序和先序遍历的思想为基础 
int countNode(Tree *t){ 
	//首先判断t是否为空 
	if(t == NULL)return 0;
	int num = 0;
	stack<Tree*>s2;
	while(t != NULL|| !s2.empty()){
		if(t){
			s2.push(t);
			t = t->lchild;
		} 
		else{
			t = s2.top();
			s2.pop();
			//每个结点元素弹出一次,每弹出一次计数器+1
			num++;
			t = t->rchild; 			 
		}
	}
	return num;
}
int countOneNode(Tree *t){
	if(t == NULL)return 0;
	int num = 0;
	stack<Tree*>s2;
	while(t || !s2.empty()){
		if(t){
			s2.push(t);
			t = t->lchild;
		} 
		else{
			t = s2.top();
			s2.pop();
			//如果一个结点的左子树和右子树中只有一个非空,这个结点度为1,计数器+1 
			if((t->lchild == NULL && t->rchild != NULL) || (t->lchild != NULL && t->rchild == NULL))num++;
			t = t->rchild; 			 
		}
	}
	return num;
}
int countTwoNode(Tree *t){
	if(t == NULL)return 0;
	int num = 0;
	stack<Tree*>s2;
	while(t || !s2.empty()){
		if(t){
			s2.push(t);
			t = t->lchild;
		} 
		else{
			t = s2.top();
			s2.pop();
			//如果一个结点的左子树和右子树均非空,这个结点度为2,计数器+1 
			if(t->lchild != NULL && t->rchild != NULL)num++;
			t = t->rchild; 			 
		}
	}
	return num;
}
int countLeafNode(Tree *t){
	if(t == NULL)return 0;
	int num = 0;
	stack<Tree*>s2;
	while(t || !s2.empty()){
		if(t){
			s2.push(t);
			t = t->lchild;
		} 
		else{
			t = s2.top();
			s2.pop();
			//如果一个结点的左子树和右子树均空,这个结点为叶子结点,计数器+1 
			if(t->lchild == NULL && t->rchild == NULL)num++;
			t = t->rchild; 			 
		}
	}
	return num;
}
void maxAndMin(Tree *t){
	if(t == NULL)exit(-1);
	int num = 0;
	stack<Tree*>s2;
	//创建一个保存所有结点的栈,在遍历时不向外弹出元素 
	stack<Tree*>s4;
	while(t || !s2.empty()){
		if(t){
			s2.push(t);
			//在遍历同时将结点存入保存栈 
			s4.push(t);
			t = t->lchild;
		} 
		else{
			t = s2.top();
			s2.pop();
			t = t->rchild; 			 
		}
	}
	//max结点为最大值结点,min结点为最小值结点,temp结点为中间值结点,进行比较 
	Tree *max,*min,*temp;
	//当t的内容>=2时,分别给max和min赋一个初值 
	if(!s4.empty() ){
		max = s4.top();
		s4.pop();
	}
	if(!s4.empty()){
		min = s4.top();
		s4.pop();
	}
	//当只有一个元素时,max == min 
	else min = max; 
	while(true){
		if(s4.empty()){
			if(max->data < min->data)
			{
				temp = min;
				min = max;
				max = temp;
			} 
			break;
		}
		else{
			//将每个元素先存到temp中,分别和max,min作比较 
			temp = s4.top();
			s4.pop() ;
			if(max->data < temp->data)max = temp;
			if(min->data > temp->data)min = temp;
		}
	}
	cout << "数值最大值为" << max->data << endl;
	cout << "数值最小值为" << min->data << endl; 
}
void levelOrder(Tree *t){
	//以队列作为结构基础展开 
	queue<Tree*>q;
	//先存入一个元素,以便于下面的判断 
	q.push(t);
	//判断条件 
	while(t && !q.empty()){
			//如果q非空,弹出队首元素并访问,将其左子树和右子树分别存入队尾 
			if(!q.empty()){
			t = q.front();
			q.pop();
			cout << t->data;
			if(t->lchild != NULL)q.push(t->lchild);
			if(t->rchild != NULL)q.push(t->rchild);
		}
	}
}
int main()
{
	//方法调用 
	cout << "开始建树" <<endl;
	cout << "请按左右子树的规则将树的结点输入,按根节点,左子树,右子树的规则输入,以#作为空结点" << endl;
	Tree *t = createTree(t);
	int n1 = countNode(t);
	cout << "该树的结点个数为:   " << n1 << endl; 
	int n2 = countOneNode(t);
	cout << "该树度为1的结点个数为:   " << n2 << endl; 
	int n3 = countTwoNode(t);
	cout << "该树度为2的结点个数为:   " << n3 << endl; 
	int n4 = countLeafNode(t);
	cout << "该树的叶子结点个数为:   " << n4 << endl; 
	maxAndMin(t);
	cout << "先序遍历结果为:   "; 
	dpreOrder(t); 
	cout << endl;
	cout << "中序遍历结果为:   "; 
	dinOrder(t);
	cout << endl;
	cout << "后序遍历结果为:   "; 
	dpostOrder(t);
	cout << endl;
	cout << "非递归先序遍历结果为:   ";
	fpreOrder(t);
	cout << endl;
	cout << "非递归中序遍历结果为:   ";
	finOrder(t);
	cout << endl;
	cout << "非递归后序遍历结果为:   ";
	fpostOrder(t);
	cout << endl;
	cout << "层次遍历结果为:   ";
	levelOrder(t);

	return 0; 
}
(4)函数调用和过程关系图

在这里插入图片描述

3、调试分析
(1)调试过程中的问题和程序设计时的回顾

A.对库函数中的栈和队列的初应用;
B.创建二叉树的方法
用递归的方法实现先根顺序,从而按顺序创建函数;
C.非递归遍历中后序遍历的实现
在树结构体中插入一个计数器,在访问时计算访问次数,当访问三次时输出其结点值;
D.计算结点数目
由于每个结点都有且只有一次出栈,在出栈时对结点类型进行判断并计数即可
E.记最大值和最小值
取一个保存栈,在遍历的同时保存所有结点,然后一个个出栈并进行比对,直到栈为空时完成最大值最小值查找;
F.层次遍历算法
取一个栈,在访问一个结点时按顺序将左结点和右结点入队即可(见DFS深度优先算法)

(2)算法的时空分析

O(n),其中n为结点个数

(3)改进设想

将结点的类型扩展,可以使用字符串的方法将类型扩展到复合文本(如多位数字,英文单词等);

(4)经验和体会

本程序的代码量较多,而且涉及到的算法较多,在实现的时候存在困难,但克服这些困难后对树结构的整体理解都会提高一层,脑中也会有对算法的一个整体印象,总的来说对树结构的学习起了十分重要的作用;
同时在实现过程中也和其他同学讨论了一些实现的巧妙之处,这种合作的感觉十分优秀;
数据结构的学习还有很长一段路要走。

4、用户使用说明
(1)使用步骤

首先根据提示,以先根的方法将结点的值输入,空结点用‘#’表示
在这里插入图片描述
回车后即可显示结果
在这里插入图片描述

5、测试结果

A.简单树:
在这里插入图片描述在这里插入图片描述
B.一般树
在这里插入图片描述
在这里插入图片描述
C.复杂树
在这里插入图片描述
在这里插入图片描述

发布了36 篇原创文章 · 获赞 11 · 访问量 2918

猜你喜欢

转载自blog.csdn.net/yeweij226/article/details/89855679