沈阳航空航天大学
课 程 设 计 报 告
课程设计名称: | 算法分析课程设计 |
课程设计题目: | 二叉树深度优先遍历 |
学 院:
专 业:
班 级:
姓 名:
学 号:
指导教师:
完成时间: 2023 年 6 月 30 日
算法分析课程设计报告
序号 | 评价项目 | 评分 | |||||
满分 | 得分 | ||||||
1 | 课设态度,查阅资料和自我学习的能力。 | 10 | |||||
2 | 课设题目的理解情况和完成情况,以及程序的正确性与完善性。 | 40 | |||||
3 | 检查过程中问题回答的准确程度 | 20 | |||||
4 | 课程设计报告的格式和内容,侧重考虑内容充实度、图表齐全度、对设计和实现过程的描述详实度、运行和调试的全面度等方面。 | 30 | |||||
累计得分 | |||||||
指导教师评语: | |||||||
| |||||||
课设成绩: | 指导教师签名 | ||||||
日期 | 年 月 日 |
注:成绩评定采用五级记分制。优秀(90~100分)、良好(80~89分)、中等(70~79分)、及格(60~69分)、不及格(60分以下)
课程名称 | 算法分析课程设计 专业 | ||||||||||||||||||||
学生姓名 | 班级 | 学号 | |||||||||||||||||||
题目名称 | 二叉树深度优先遍历 | ||||||||||||||||||||
起止日期 | 2023 | 年 6 月 | 19日起 至 2023 | 年 | 6 月 30 日 止 | ||||||||||||||||
课设内容和要求:
一、课程设计内容
构建一个二叉树,对二叉树实现深度优先遍历,不允许使用递归,要求使用栈完成。具体要求:
1.构建一个二叉树,二叉树结点个数超过20个,并且二叉树左右节点分布均匀。
2.输出二叉数的先序遍历、中序遍历、后序遍历的结果。
二、课程设计要求
1.独立完成课程设计任务;
2.通过老师当场验收;
3.交出完整的课程设计报告。
参考资料:
[1] 张光河主编. 数据结构--Python语言描述. 北京:人民邮电出版社,2018
[2] 王硕等编著. Python算法设计与分析. 北京:人民邮电出版社,2020
[3] 裘宗燕编著. 数据结构与算法(Python语言描述). 北京:机械工业出版社,2021
- √ ) 不同意( ) 教研室主任签字:
指导教师(签名) | 2023年 | 6 | 月 | 27 | 日 | |||||||||
学生签名(签名) | 2023年 | 月 | 日 | |||||||||||
6 | 27 | |||||||||||||
课程设计总结
树是常用的数据结构。通过实验加深了我对树的遍历的认识,巩固了课本中所学的关于树的基本算法。按要求完成了实验内容。通过实验,有如下几点收获和体会:
- 通过实验还提高了一点改错能力,对于一些常见问题加深了印象。
2、编程需要有耐心,尤其实在单步调试的时候,更是马虎不得,有时候关键就是那么一步,错过了就得从头来过了。编程也需要勇气,要勇于发现自己的错误,也要勇于推翻自己之前的思路,要坚信"没有最好,只有更好"。编程,最好是一鼓作气,得天天"摸摸"它,时时想着它,要是过一阵再去碰它那就得先去读懂自己的程序了,一切的一切几乎都得从头开始。编程需要细心,有时一个不注意小错误就能引出大问题。编程也需要规范,不仅为了他人能看得懂程序,也为了方便自己以后程序的更改与进一步的完善。
3、程序由算法和数据结构组成,一个好的程序不仅算法重要,数据结构的设计也很重要。
4.以前在学 C++ 语言时总觉得函数的非递归调用是一样很复杂的算法,经常会理解不了而导致编程的错误。但在这次实验中,二叉树的先序、中序与后序的输出都用了非递归算法。而且用起来并不复杂,这使我更进一步学习理解了函数的递归调用并得到灵活的运用。经过本次实验基本上解决的一些所遇到的问题,我对二叉树的结构等有了较为深入的理解。我会继续我的兴趣编写程序的,相信在越来越好。
目 录
1 课程设计报告名称与项目背景
二叉树深度优先遍历
1.2 课程设计项目背景
树是数据结构中的重中之重,尤其以各类二叉树为学习的难点。先从整体上认识下二叉树及其他各种树的区别和用途。树有很多种,其中二拆树因为其特殊的结构和特点在计算上最为常用。常见的二叉树:二叉查找树,平衡二叉树(AVL),红黑树,字典树,满二叉树,完全二叉树,霍夫曼树,伸展树,最小堆,最大堆等,B+树,B-树则属于多路搜索树。在操作系统源程序中,树和森林被用来构造文件系统。我们看到的window和linux等文件管理系统都是树型结构。在编译系统中,如C编译器源代码中,二叉树的中序遍历形式被用来存放C 语言中的表达式。其次二叉树本身的应用也非常多,如哈夫曼二叉树用于JPEG编解码系统(压缩与解压缩过程)的源代码中,甚至于编写处理器的指令也可以用二叉树构成变长指令系统,另外二叉排序树被用于数据的排序和快速查找。用的最多的应该是平衡二叉树,有种特殊的平衡二叉树红黑树,查找、插入、删除的时间复杂度最坏为O(log n)Java集合中的TreeSet和TreeMap,C++ STL中的set、map,以及Linux虚拟内存的管理,都是通过红黑树去实现的。还有哈夫曼树编码方面的应用。B-Tree,B+-Tree在文件系统中的应用。Dijkstra算法是最短路径算法中为人熟知的一种,是单起点全路径算法。该算法被称为是“贪心算法”的成功典范。该算法如果不优化速度非常慢,如果用二叉树算法优化效率快很多,类似的,很多人工智能算法都是用二叉树优化的
2 课设内容与要求
2.1 课程设计内容
构建一个二叉树,对二叉树实现深度优先遍历,不允许使用递归,要求使用栈完成。具体要求:
1.构建一个二叉树,二叉树结点个数超过20个,并且二叉树左右节点分布均匀。
2.输出二叉数的先序遍历、中序遍历、后序遍历的结果。
2.2 课程设计要求
1.独立完成课程设计任务;
2.通过老师当场验收;
3.交出完整的课程设计报告。
3 模块设计
3.1 总体设计
首先申请节点空间,再通过先序建立的方法创建二叉树,二叉树采用链式存储结构,以链表和栈实现,每个节点里面有一个字符型数据和两个左右子树节点的指针,创建二叉树时先输入父节点的值,然后输入左子节点然后右子节点,空节点以#表示。前序遍历、中序遍历、后序遍历均非递归算法实现使用栈来实现。
图一程序流程图
3.2 具体设计
3.2.1 先序创建模块
图二 二叉树示例
算法的基本思路:为了能够确定新读入的结点链接到当前结点(双亲)的左子树还是右子树,引入了链接标志flag,其初始值为假。flag值为真,则表示需将新读入的结点s插入当前结点(双亲)p的右子树,否则插入p的左子树。
具体步骤主要包括如下三步:
(1) 如果新读入的数据非空,当flag值为假时,将新读入的结点链接到当前结点(双亲)p的左子树,同时当前结点(双亲)p入栈(作为待插入的右子树的双亲结点),然后把新读入的结点作为当前结点(双亲)p。当flag值为真时,将新读入的结点链接到当前结点(双亲)p的右子树,并将flag置为假;
(2) 如果新读入的数据为空,且flag为真,则栈顶元素出栈;
(3) 如果新读入的数据为空,且flag为假,则继续读入数据,此时数据若非空,则建立新结点s,并链接到当前结点的右子树,同时将当前结点(双亲)p指向s;若为空数据,则栈顶结点出栈给p,同时flag值置为1,表示左子树已经处理完毕,再读入新结点则放到双亲的右子树。若栈为空,则结束二叉树的创建。
2、按照图(2)所示,使用先序法创建二叉树,需要读入的数据依次为:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
A | B | # | C | D | # | # | # | E | # | F | G | H | # | # | K | # | # | # |
表1数据输入表
3.2.2 先序遍历模块
1.根节点A入栈,并打印该结点的值,P指向其左孩子。
图三 先序遍历过程图
- 继续遍历结点,此时B结点存在并打印该结点的值,则将该结点入栈,P指向B结点的左孩子但是左孩子为空,P指向B的右孩子。
图四 先序遍历过程图
3.继续遍历结点,此时C结点存在并打印该结点的值,则将该结点入栈,P指指向C节点的左孩子。
图五 先序遍历过程图
依次类推最终先序遍历输出为ABCDEFGHK。
3.2.3 中序遍历模块
算法思想:
1.从根节点开始检索,如果当前节点不为空,则将当前节点入栈,让当前节点成为其左孩子节点,再继续上一步的操作
2.加入当前节点为空了,说明其父节点已经没有左孩子了,那么将栈顶元素出栈并输入
3.判断栈顶元素是否有右孩子,如果有右孩子,则停止依次继续出栈的操作,并检索它的右孩子,重复第一步;如果没有右孩子,则继续将栈顶元素出栈
4.当栈为空,并且当前节点为空时,遍历完成,退出;
分析:
首先从根节点开始,沿着根的左孩子,将左孩子依次进行入栈。当B入栈之后,B的左子树遍历完成所以B出栈。
图六 中序遍历过程图
接着指针指到C,将C入栈继续遍历C的左子树指针指到D将入栈因为D没有左子树所以D出栈。
图七 中序遍历过程图
接着指针到C因为C没有右子树所以C出栈。
图八 中序遍历过程图
依次类推最终中序遍历输出为BDCAEHGKF。
3.2.4 后序遍历模块
算法思想:
1、沿着根的左孩子,依次入栈,直到左孩子为空;
2、读栈顶元素进行判定,若右孩子不空且未被访问,将右孩子执行第一步;
3、栈顶元素出栈。
分析:
首先从根节点开始,沿着根的左孩子,将左孩子依次进行入栈。当D入栈之后,由于D没有右孩子,所以将D出栈,此时r指向D。
图九 后续遍历过程图
D出栈之后读栈顶元素C,P指向C,发现C没有右孩子,然后将C出栈。
图十 后续遍历过程图
依次类推最终后序遍历输出DCBHKGFEA。
4 输出测试
1.运行改程序将如图的二叉数创建出来。
图十一 二叉树示例图
即输入:ABCD##E##FG##H##IJK##L##MN##O##。
回车将输出该二叉树的前序遍历、中序遍历、后序遍历如图所示:
图十二 运行结果图
2.若输入的二叉树为空树则会输出“树为空树”如图所示:
图十三 运行结果图
3.如果输入的树为仅有左子树如图所示:
图十四 二叉树示例图
则输出结果为:
图十五 运行结果图
4.如果输入的树为仅有右子树:
图十六 二叉树示例图
则输出结果为:
图十七 运行结果图
5.如果输入不符合二叉树的先序创建标准则会发生错误程序报错。
参考文献
[1] 桂劲松等.物联网系统设计(第2版)[M].北京:电子工业出版社,2017.
[2] 宋楠,韩广义.Arduino开发从零开始学:学电子的都玩这个[M].北京:清华大学出版社,2014.
[3] R. Schenkel, A. Theobald, G. Weikum. HOPI: An efficient connection index for complex XML document collections[C]. Proceedings of EDBT, Crete, Greece, 2004, 237-255.
[4] 谭浩强. C++程序设计(第3版)[M]. 清华大学出版社,2015
[5] 陆德旭. 人工智能教程----C++语言基础教程[M]. 青岛出版社,2018
[6] 张海龙. C++ Primer Plus(第6版)中文版[M]. 人民邮电出版社,2017
[7] 谭浩强. C程序设计(第五版)[M]. 清华大学出版社,2017
附录:
#include <iostream>
#include<stack>
#include<set>
#include<map>
using namespace std;
struct Btree {
char data;
Btree* lchild;
Btree* rchild;
};
void create(Btree*& root)//创建二叉树
{
char data;
cin >> data;
root = new Btree;
if (data == '#') root = NULL;
if (root != NULL)
{
root->data = data;
create(root->lchild);
create(root->rchild);
}
}
void NORecursionprd(Btree*& tree) {//前序非递归算法
Btree* p = tree;
stack<Btree*> s;
s.push(tree);
while (!s.empty())
{
p = s.top();//获取栈顶元素
s.pop();//弹出栈顶元素
cout << p->data << '\t';
if (p->rchild) s.push(p->rchild);//先将右子树入栈
if (p->lchild)s.push(p->lchild);//再将左子树入栈,栈后进先出,符合前序遍历规则
}
}
void NORecursionldr(Btree*& tree)//中序遍历非递归算法
{
stack<Btree*> s;//建立栈
Btree* p = tree;
while (p != NULL || !s.empty()) {
while (p != NULL) {//依次将每个结点的左子树放入栈
s.push(p);
p = p->lchild;
}
if (!s.empty()) {//在栈非空时找到栈顶元素的右子树
p = s.top();
s.pop();
cout << p->data << '\t';//弹出栈顶元素,实现中序遍历
p = p->rchild;
}
}
}
void NORecursionlrd(Btree*& tree)
{
map<Btree*, int>judge;//利用map记录访问次数
stack<Btree*>s;//利用栈来模拟后序遍历
Btree* p = tree;//临时保存结点
while (p != NULL || !s.empty())
{
if (p != NULL)//如果结点不为空,一直往下找其左子树
{
s.push(p);
p = p->lchild;
judge[p] = 1;
}
else {//结点为空
p = s.top();//取栈顶元素
if (judge[p] == 2) {//若访问过两次,说明左右子树均已经访问,直接输出其数据即可
s.pop();
cout << p->data << '\t';
p = NULL;
}
else {//否则将访问其右子树,将其访问次数置为2
judge[p] = 2;
p = p->rchild;
}
}
}
}
int main()
{
Btree* tree = new Btree;
cout << "请输入首根结点数据:";
if (tree == NULL) cout << "树为空树!\n";
else {
create(tree);
cout << endl << "该树非递归先序遍历的结果为:\n";
NORecursionprd(tree);
cout << endl << "该树非递归中序遍历的结果为:\n";
NORecursionldr(tree);
cout << endl << "该树非递归后序遍历的结果为:\n";
NORecursionlrd(tree);
}
}