二叉树的基本操作
一、实验目的
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.复杂树