目录
近来有空闲,把前几个学期做的实验上传上来。如有错误的地方欢迎大佬批评指正,有更好的方法也期待您的分享~
实验内容
1.编写函数,输入字符序列,建立二叉树的二叉链表。
2.编写函数,实现二叉树的中序递归遍历算法。(最好也能实现前缀和后缀遍历算法)
3.编写函数,实现二叉树的中序非递归遍历算法。
4.编写函数,借助队列实现二叉树的层次遍历算法。
5.编写函数,求二叉树的高度。
6.编写函数,求二叉树的结点个数。
7.编写函数,求二叉树的叶子个数。
8.编写函数,交换二叉树每个结点的左子树和右子树。
9.编写一个主函数,在主函数中设计一个简单的菜单,分别调试上述算法。
一、实验目的
1.熟悉二叉树遍历的各种算法;
2.掌握采用递归实现二叉树遍历算法的方法;
3.深刻理解栈在递归中的作用,进而学会递归转为非递归的方法;
4.训练在编程上控制复杂结构的能力,为今后控制更为复杂结构,进而解决有一定难度的复杂问题奠定基础。
二、问题分析及数据结构设计
本次开发任务要求我们编写一个能调试几种表示和操作二叉树算法的系统。该任务需要实现以下功能:建立二叉链表、二叉树的递归遍历(中序、前缀和后缀)、非递归的中序遍历、层次遍历、计算二叉树的高度、结点数、叶子数、交换每个节点的左右子树等。
其次是数据结构设计,为了实现这些功能,我们需要构建数据模型。二叉树的链式存储结构是指用一个链表存储一棵二叉树,二叉树中的每个结点用链表中的一个链结点存储。在二叉树中,标准存储方式的结点结构为(lchild,data,rchild),其中,data 为值成员变量,用于存储对应的数据元素,lchild 和 rchild 分别为左右指针变量,分别用于存储左孩子结点和右孩子结点(即左、右子树的根结点)的地址。
对于每个功能,我们需要设计相应的函数,函数的输入参数和返回值根据具体的需求进行设计。例如,建立二叉链表的函数使用字符序列x作为输入,返回一个根节点的指针;非递归遍历函数使用节点指针p作为输入,输出遍历的结果等。另外,在实现层次遍历功能的过程中,需要使用辅助的数据结构队列 enqueue() 来存储当前层次的节点。从根节点开始,调用 queue() 将其入队,然后不断从队列中取出节点,将其所有子节点入队,直到队列为空,这样就可以保证按照层次顺序遍历整个树或图。
三、算法设计
1.二叉链的链式存储结构BiTNode:
(1)使用 #define 定义二叉树结点值 ElemType 的类型为字符型;
(2)定义二叉链表结构体 BiTNode
(3)定义结点数据元素 data,结构体中的 lchild 指向左孩子结点, rchild 指向右孩子结点
(4)声明二叉链表变量 BiTNode。
图(a)所示的二叉树对应的二叉链存储结构如图(b)所示,整颗二叉树通过根结点r唯一标识。
![](https://img-blog.csdnimg.cn/direct/d9698c389e4e4c92a65a80b3744db35b.png)
2.建立二叉树的二叉链表 create_tree():
(1)输入一个字符串,若某一字符为“#”,则将该位置的结点设为空;
(2)将该结点的数据域置为该位置的字符,初始化指向父结点的指针为空。
(3)若该结点的左孩子不为空,用递归的形式建立其左孩子,并记下左孩子的父亲,即为该结点。同理对右孩子。
(4)最后return 根结点,此时便建立了一个二叉树。
3.二叉树的中序递归遍历算法 inorder():
(1)从根节点t出发,判断当前结点是否为null;
(2)如果不为null,则通过递归调用 inorder(t->lchild) 中序遍历左子树;
(3)t->data访问根节点;
(4)通过递归调用 inorder(t->rchild) 中序遍历右子树。
对于图1(a)所示的二叉树,其中序遍历过程如图2所示,在每个结点底部画一条线与虚线相交,该相交点表示访问点,然后按虚线遍历次序列出相交点得到中序遍历序列,即DGBAECF。
![](https://img-blog.csdnimg.cn/direct/ee11af5b59f044d682875344d90e5216.png)
4.二叉树的前缀递归遍历算法 preorder():
(1)从根节点t出发,判断当前结点是否为null;
(2)t->data访问根节点;
(3)如果不为null,则通过递归调用 inorder(t->lchild) 中序遍历左子树;
(4)通过递归调用 inorder(t->rchild) 中序遍历右子树。
对于图1(a)所示的二叉树,其先序遍历过程如图3所示,图中虚线表示这种遍历过程,在每个结点左边画一条线与虚线相交,该相交点表示访问点,然后按虚线遍历次序列出相交点得到先序遍历序列,即ABDGCEF。
![](https://img-blog.csdnimg.cn/direct/0b59984d7d7d4233a01c168b6bfc97a9.png)
5.二叉树的后缀递归遍历算法 pasorder():
(1)从根节点t出发,判断当前结点是否为null;
(2)如果不为null,则通过递归调用 inorder(t->lchild) 中序遍历左子树;
(3)通过递归调用 inorder(t->rchild) 中序遍历右子树。
(4)t->data访问根节点;
对于图1(a)所示的二叉树,其后序遍历过程如图4所示,在每个结点右边画一条线与虚线相交,该相交点表示访问点,然后按虚线遍历次序列出相交点得到后序遍历序列,即GDBEFCA。
![](https://img-blog.csdnimg.cn/direct/819126832d3b4a2f819835925c584101.png)
6.二叉树的中序非递归遍历算法 inorder1():
(1)BiTNode* s[N]初始化一个空栈S,指针p指向根结点;
(2)申请结点空间q,存放栈顶弹出的元素;
(3)当p非空或栈S非空时,执行以下循环:
(4)如果p非空,则访问根结点,p进栈,p指向该结点左孩子;左子树遍历结束后,访问根结点;然后将p指向该结点右子树,继续中序遍历它的右子树。
7.借助队列实现二叉树的层次遍历算法 levelorder():
(1)初始化一个空队列queue(),指针t指向根结点,根结点入队;
(2)申请结点空间p,存放队列中出队的元素;
(3)当队列非空时,执行以下循环:
(4)队尾元素出队;访问根结点;
(5)如果该结点的左子树非空,左子树入队;
(6)如果该结点的右子树非空,右子树入队。
对于下图所示的二叉树,其层次遍历层次遍历次序为:ABCDEF,层次遍历过程如图5所示:
![](https://img-blog.csdnimg.cn/direct/26fe77c3d8f14985922266b1dd908c2b.png)
8.求二叉树的高度 hight_bit():
(1)如果是空树,递归结束,深度为0,否则执行以下操作:
(2)递归调用hight_bit()计算左子树的深度记为l;
(3)递归调用hight_bit()计算右子树的深度记为h;
(4)判断左右子树哪个深度较大,如果l>h,二叉树的深度为l+1,否则为h+1。
9.基于中序遍历求二叉树的结点个数 NodeCount():
(1)若树为空,直接返回0;
(2)NodeCount(t->lchild)遍历求左子树的结点个数;
(3)根结点计数1;
(4)NodeCount(t->rchild)遍历右子树的结点个数.
10.求二叉树的叶子个数 count_leaf():
(1)BiTNode* s[N]申请一个栈空间,初始化栈为空;
(2)判断当前树的根节点是否为空或栈是否为空;
(3)如果当前树的根节点不为空或栈不为空,则说明需要继续遍历;判断当前根节点的左右子树都是否为空;
(4)若当前根节点的左右子树都为空,则是叶子节点,入栈;
(4)若当前根节点的左右子树都不为空,则依次访问该结点的左右子树,递归判断左右子树的左右子树是否为空。
11.基于后序遍历交换二叉树每个结点的左、右子树 exchange_tree():
(1)如果是空树,递归结束,否则执行以下操作:
(2)递归交换结点的左子树;
(3)递归交换结点的右子树;
(4)运用中间值p交换根节点t的左右孩子指针。
12.主函数部分main():
(1)调用 create_tree() 函数输入并建立一个二叉树;
(2)输入想要路径的结点值;
(3)使用 switch 选择器选择调用inorder()、preorder()、pasorder()、inorder1()、levelorder()、hight_bit()、NodeCount()、count_leaf()、exchange_tree()函数以输出已建立二叉树的前序递归遍历,中序递归遍历,后序递归遍历,中序非递归遍历,层序遍历,树的高度,结点数,叶子数,交换左右子树。
(4)完成程序:return 0;
三、功能模块程序流程图
1.菜单部分:
![](https://img-blog.csdnimg.cn/direct/0810dfe6e52c4bcebb37ad0c4a7b4305.png)
2.建立二叉树的二叉链表 create_tree():
![](https://img-blog.csdnimg.cn/direct/e0ae413c6cb9478c8854b32806b373a7.png)
3.二叉树的中序递归遍历算法 inorder():
![](https://img-blog.csdnimg.cn/direct/6fa4f1acaf0a454c83103819122b93ee.png)
4.二叉树的前缀递归遍历算法 preorder():
![](https://img-blog.csdnimg.cn/direct/9a1dca3511b04649b77bba507eafc8a7.png)
5.二叉树的后缀递归遍历算法 pasorder():
![](https://img-blog.csdnimg.cn/direct/537ed36c7c8a493d924ef94eef6a8c71.png)
6.二叉树的中序非递归遍历算法 inorder1():
![](https://img-blog.csdnimg.cn/direct/603df1b1862a46d9bc102bc6ed8c16f8.png)
7.借助队列实现二叉树的层次遍历算法 levelorder():
![](https://img-blog.csdnimg.cn/direct/52d421ba5374431a876aa97d1328211e.png)
8.求二叉树的高度 hight_bit():
![](https://img-blog.csdnimg.cn/direct/09a4487c1f6c4eb78d8a1345b762e68a.png)
9.基于中序遍历求二叉树的结点个数 NodeCount():
![](https://img-blog.csdnimg.cn/direct/3f01efec1c4d40ab87703ec561074f0a.png)
10.求二叉树的叶子个数 count_leaf():
![](https://img-blog.csdnimg.cn/direct/3625bd59ae9944aabcef66f9b8896263.png)
11.基于后序遍历交换二叉树每个结点的左、右子树 exchange_tree():
![](https://img-blog.csdnimg.cn/direct/10b2aee76bf44d709176f65c2141f8da.png)
五、实验结果
1.输入数据:ABDH##I###CF#K##G##
该输入对应的树如图所示:
![](https://img-blog.csdnimg.cn/direct/23e6223a35ab4e5fbbd02189e0dcc54c.png)
2.实验结果
![](https://img-blog.csdnimg.cn/direct/bc78ba6bc445458f9c446f68ea5fbb5c.png)
(1)先序递归 屏幕输出 A B D H I C F K G;
(2)中序递归 屏幕输出 H D I B A F K C G;
(3)后序递归 屏幕输出 H I D B K F G C A;
(4)中序非递归 屏幕输出 H D I BA F K C G;
(5)层序遍历 屏幕输出 A B C D F G H I K;
(6)树的高度 屏幕输出 4;
(7)结点个数 屏幕输出 9;
(8)叶子个数 屏幕输出 4;
(9)交换结点的左右子树 屏幕输出 A C G F K B D I H。
与图17核对,实验结果均正确。
六、算法分析
1.递归遍历
(1)时间复杂度O(n):其中 n 为二叉树节点的个数。二叉树的遍历中每个节点会被访问一次且只会被访问一次;
(2)空间复杂度O(n):空间复杂度取决于递归的栈深度,而栈深度在二叉树为一条链的情况下会达到 O(n) 的级别。
2.非递归遍历(辅助栈)
(1)时间复杂度O(n):其中n是结点数,由于每个节点都要进栈和出栈;
(2)空间复杂度O(n):其中n是结点数。
3.层序遍历
(1)时间复杂度O(n):其中n是结点数,每个点进队出队各一次,故渐进时间复杂度为 O(n);
(2)空间复杂度O(n):其中n是结点数,队列中元素的个数不超过 nnn 个,故渐进空间复杂度为 O(n) 。
4.求二叉树高度
(1)时间复杂度 O(n):其中n是结点数,需要调用函数2n+1次访问其扩充二叉树全部结点,所以时间复杂度为O(n);
(2)空间复杂度O(n):其中n是结点数,尾递归方式开辟临时空间n个用于变量存储,所以空间复杂度为O(n)。
5.求二叉树结点个数
(1)时间复杂度 O(n):其中n是结点数,层序遍历二叉树,将该完全二叉树看作是一个普通的二叉树进行遍历,记录下结点的数量;
(2)空间复杂度O(n):其中n是结点数。
6.求二叉树的叶子结点个数
(1)时间复杂度 O(n):其中n是结点数,层序遍历二叉树,将该完全二叉树看作是一个普通的二叉树进行遍历,记录下结点的数量;
(2)空间复杂度O(n):其中n是结点数。
7.交换二叉树左右子树:
(1)时间复杂度 O(n):其中n是结点数,每个元素都必须访问一次,所以是 O(n);
(2)空间复杂度O(n):最坏的情况下,需要存放 O(h) 个函数调用(h是树的高度)。
七、操作说明
1.进入二叉树线性次序系统后,会看见如下提示:
☆☆☆☆☆☆欢迎使用二叉树小程序!☆☆☆☆☆☆
1. 输入字符序列,建立二叉链表
2. 先序遍历二叉树:递归算法
3. 中序遍历二叉树:递归算法
4. 后序遍历二叉树:递归算法
5. 中序遍历二叉树:非递归算法
6. 借助队列实现二叉树的层次遍历
7. 求二叉树的高度
8. 求二叉树的结点个数
9. 求二叉树的叶子个数
10.交换二叉树每个结点的左子树和右子树
11. 退出
2.若想建立二叉链表,则输入1,然后按照从左子树开始接收数据的方式输入(空结点用字符‘#’表示,例如ABC##DE#G##F###)。
3.若想实现二叉树的前序递归遍历算法,则输入2。
4.若想实现二叉树的中序递归遍历算法,则输入3。
5.若想实现二叉树的后序递归遍历算法,则输入4。
6.若想实现二叉树的中序非递归遍历算法,则输入5。
7.若想借助队列实现二叉树的层次遍历算法,则输入6。
8.若想求二叉树的高度,则输入7。
9.若想求二叉树的结点个数,则输入8。
10.若想求二叉树的叶子个数,则输入9。
11.若想交换二叉树每个结点的左子树和右子树,则输入10。
12.若想退出程序,则输入11。
八、源代码
#include <iostream>//数据流输入/输出:cin>>,cout<<的头文件
#include <string.h>//字符串操作的头文件
#include <stdio.h>//字符串操作的头文件
#include <algorithm>//算法类函数:sort().....的头文件
#pragma warning(disable:4703)
using namespace std;//命名空间
#define NULL 0
#define N 10
//二叉链表存储
typedef char ElemType;//定义二叉树结点值的类型为字符型
typedef struct BiTNode//二叉链表的定义
{
ElemType data;//数据元素
struct BiTNode* lchild;//指向左孩子结点
struct BiTNode* rchild;//指向右孩子结点
} BiTNode;
//建立二叉树 ,二叉树类的设计,在二叉链中通过根节点r唯一标识二叉树
void create_tree(BiTNode** t)
{
ElemType x;
cin >> x;
if (x == '#') {//置空,在输入数据的时候对于没有数据的节点,输入‘#’
*t = NULL;//如果等于字符等于‘#’,就返回NULL
}
else
{
*t = new BiTNode;//申请一个Tnode类型变量的地址空间
(*t)->data = x;//结点权值为x
create_tree(&((*t)->lchild));//往左子树搜索
create_tree(&((*t)->rchild));//往右子树搜索
}
}
//递归遍历算法
//递归先序遍历二叉树
void preorder(BiTNode* t)
{
if (t)//判断结点是否存在
{
cout << t->data << " ";//访问根节点
preorder(t->lchild);//先序遍历左子树
preorder(t->rchild);//先序遍历右子树
}
}
// 递归中序遍历二叉树
void inorder(BiTNode* t)
{
if (t)//判断结点是否存在
{
inorder(t->lchild);//中序遍历左子树
cout << t->data << " ";//访问根节点
inorder(t->rchild);//中序遍历右子树
}
}
//递归后序遍历二叉树
void pasorder(BiTNode* t)
{
if (t)//判断结点是否存在
{
pasorder(t->lchild);//后序遍历左子树
pasorder(t->rchild);//后序遍历右子树
cout << t->data << " ";//访问根节点
}
}
//队列
#define M 100
BiTNode* que[M];
int front = 0, rear = 0;
void enqueue(BiTNode* T)//存储出队的结点
{
if (front != (rear + 1) % M)
{
rear = (rear + 1) % M;
que[rear] = T;
}
}
BiTNode* queue()
{
if (front == rear)
return NULL;
front = (front + 1) % M;
return(que[front]);
}
void levelorder(BiTNode* t) //层次遍历二叉树
{
BiTNode* p;
if (t)
{
enqueue(t);//把树根入队
while (front != rear) {//借助队列,首先将根节点p入队
p = queue();
cout << p->data; //当队列不空时,获得队首元素并出队,赋给p
if (p->lchild != NULL)enqueue(p->lchild);//如果p左节点存在,则左孩子入队
if (p->rchild != NULL)enqueue(p->rchild);//如果p右节点存在,则右孩子入队
}
}
}
//非递归中序求二叉树叶子结点数
int count_leaf(BiTNode* t)
{
int n, top;
BiTNode* s[N], * p;// 申请一个栈空间
n = 0;
top = 0;// 栈为空
p = t;
while (p || top > 0)// 当前树的根节点不为空 或 栈不为空(说明需要继续遍历)
{
while (p)// 当前树的根节点不为空
{
top++; // 若当前根节点的左右子树都为空,则是叶子节点
s[top] = p;//将当前的根节点入栈
p = p->lchild;// 然后访问当前根节点的左子树
}
p = s[top];// 先获取当前的根节点(即让当前的根节点出栈),然后 top--;
top--;
if (p->lchild == NULL && p->rchild == NULL) 若此时栈不为空(此时跳出了上面的while(T != NULL),说明了当前根节点的左子树为空)
n++;
p = p->rchild;// 然后访问当前根节点的右子树
}
return n;
}
//求二叉树结点个数
int NodeCount(BiTNode* t) {
int m, n, k;
if (t != NULL) {
m = NodeCount(t->lchild);//遍历左子树的结点个数
k = 1;//根结点计数1
n = NodeCount(t->rchild);//遍历右子树的结点个数
return m + k + n;
}
else return 0;//空树的结点个数为0
}
//递归算法求二叉树的高度
int hight_bit(BiTNode* t)
{
int l, h;
if (t)//判断是否空树
{
l = hight_bit(t->lchild);//左子树高度
h = hight_bit(t->rchild);//右子树高度
if (l > h)//判断左右子树哪个高度较大。+1返回
return l + 1;//左子树高度较大
else
return h + 1;//右子树高度较大
}
else
return 0;
}
//递归算法将二叉树的左右子树交换
void exchange_tree(BiTNode** t)
{
BiTNode* p;
if (*t)//判断根结点是否存在
{
exchange_tree(&((*t)->lchild));//递归交换左子树
exchange_tree(&((*t)->rchild));//递归交换右子树
p = (*t)->lchild;//交换根节点t的左右孩子指针
(*t)->lchild = (*t)->rchild;
(*t)->rchild = p;
}
}
//二叉树中序非递归算法
void inorder1(BiTNode* t)
{
BiTNode* p, * s[N];//创建栈、结点p
int top;//栈顶
top = 0;//初始化栈
p = t;
while (p || top > 0)//判断是否空树
{
//如果结点不存在或者栈为空,则遍历结束
while (p)//遍历结点是否存在,若结点存在,则入栈
{
top++;//栈顶+1
s[top] = p;//将当前的P置为栈顶结点
p = p->lchild;//指向结点左孩子
}
if (top > 0)
{
p = s[top];//将栈顶结点的置为当前的结点P
top--;
cout << p->data << " ";//输出结点的值
p = p->rchild;//指向其右孩子
}
}
}
//主函数菜单
int main()
{
BiTNode* t;
int num;
cout << "☆☆☆☆☆☆欢迎使用二叉树小程序!☆☆☆☆☆☆" << 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 << "9. 求二叉树的叶子个数" << endl;
cout << "10.交换二叉树每个结点的左子树和右子树" << endl;
cout << "11. 退出";
cout << endl;
while (true)
{
cout << "☆请输入一个数字选项:";
cin >> num;
switch (num)
{
case 1: //建立二叉链表
{
cout << "请输入二叉树各结点值:";
create_tree(&t);
cout << endl;
}break;
case 2:
{
cout << "先序遍历二叉树(递归算法):";
preorder(t);
cout << endl;
}break;
case 3:
{
cout << "中序遍历二叉树(递归算法):";
inorder(t);
cout << endl;
}break;
case 4:
{
cout << "后序遍历二叉树(递归算法):";
pasorder(t);
cout << endl;
}break;
case 5:
{
cout << "中序遍历二叉树(非递归算法):";
inorder1(t);
cout << endl;
}break;
case 6:
{
cout << "层次遍历二叉树:";
levelorder(t);
cout << endl;
}break;
case 7:
{
cout << "二叉树的高度为:";
cout << hight_bit(t);
cout << endl;
}break;
case 8:
{
cout << "二叉树的结点个数为:";
cout << NodeCount(t);
cout << endl;
}break;
case 9:
{
cout << "二叉树的叶子结点数为:";
cout << count_leaf(t);
cout << endl;
}break;
case 10:
{
cout << "交换二叉树每个结点的左子树和右子树为:";
exchange_tree(&t);
preorder(t);
cout << endl;
}break;
case 11:
{
cout << "退出成功,欢迎下次使用!" << endl;
return 0;
}break;
default:
cout << "输入错误!请重新输入!" << endl;
}
}
}