树和二叉树
-
实验目的
1.熟悉二叉树的二叉链表存储结构;
2.掌握构造二叉树的方法;
3.加深对二叉树的遍历的理解。
二.需求分析
本程序演示用C++编写,完成按先序遍历生成二叉树,先序遍历打印输出二叉树, 后序遍历打印输出二叉树, 中序遍历打印输出二叉树, 层序遍历打印输出二叉树, 先序遍历方法交换左右子树,求树的高度,求树的叶子结点个数.
输入值的范围:建立一棵二叉时,要求结点的的左右孩子为空时输入0代替.所有输入值必须为整形.输入的结点个数:若为单边二叉树(全只有左结点或全只有右结点)则为任意个数;若非单边二叉则要求结点最多的层个数不得超过MAXSIZE的值.
输出形式:输出时如果结点的左右指针这空则以" #"代替输出.
程序所能完成的功能:程序能完成按先序遍历生成二叉树,先序遍历打印输出二叉树, 后序遍历打印输出二叉树, 中序遍历打印输出二叉树, 层序遍历打印输出二叉树, 先序遍历方法交换左右子树,求树的高度,求树的叶子结点个数.操作.
测试数据
A建立二叉树中输入1230000 生成一棵树123####
B交换左右子树得到一棵二叉树1#2#3##
C按层序遍历树得到一棵二叉树1#2#3##
D求二叉树的高度得到高度3
E求叶子结点个数得到个数1
三.设计概要
(1)为了实现上述程序的功能,需要定义二叉树的抽象数据类型:
ADT BiTree is{
数据对象:D={ai|ai∈IntegerSet,i=0,1,2,…,n,n≥0}
数据关系:R={<ai,ai+1>|ai,ai+1 ∈D}
基本操作:
creat()
操作结果:建立一棵二叉树
preorder()
初始条件:二叉树已经存在
操作结果:先序遍历显示二叉树
preordertswap()
初始条件: 二叉树已经存在
操作结果:交换二叉树的左右子树
theight()
初始条件: 二叉树已经存在
操作结果:输出二叉树的高度
tleaves()
初始条件:
操作结果:输出二叉树的叶子结点个数
levelorder()
初始条件: 二叉树已经存在
操作结果:层序遍历显示二叉树
}ADT BiTree
(2) 本程序包含两个类和一个结构体类型
A基类:队列类SeQueue有5个函数
1.构造函数 SeQueue()
2.析构函数 ~SeQueue()
3.进队函数 AddQ(NodeType x)
4.出队函数 DelQ()
5.判断非空函数 IsEmpty()
B派生类:二叉树类BiTree有20个函数
1主函数 main()
2. 构造函数 BiTree()
3. 析构函数 ~BiTree()
4. 建立树函数 creat0()
5. 先序遍历函数 void preorder()
6. 中序遍历函数 inorder()
7. 后序遍历函数 postorder()
8.层序遍历函数 levelorder()
9. 交换左右子树函数 preordertswap()
10. 求二叉树高度函数 theight()
11. 求二叉树叶子结点个数函数 tleaves()
12. 建立二叉树递归算法函数 *creat()
13. 先序遍历递归算法函数 preorder(NodeType *p)
14. 中序遍历递归算法函数 inorder(NodeType *p)
15. 后序遍历递归算法函数 postorder(NodeType *p)
16. 按层遍历算法函数 levelorder(NodeType *p)
17. 先序遍历方法交换左右子树函数 preorderswap(NodeType *p)
18. 求二叉树高度递归算法函数 height(NodeType *p)
19. 求二叉树叶子结点个数的递归算法函数 leaves(NodeType *p)
20. 删除二叉树所有结点函数 destroy(NodeType* &p)
C结构体类型NodeType
(3)本程序的三个文件
1.头文件BiTree.h
2.源文件 Queue.cpp
BiTree.cpp
(4)函数之间的关系
四.详细设计
1
//BiTree.h
2
//#include <iostream.h>
3
#include <iostream> // Visual Studio 2008中要求的
4
#include "stdlib.h"
5
#define MAXSIZE 2
6
typedef int ElemType;
7
using namespace std; // Visual Studio 2008中要求的
8
9
struct NodeType //定义结点结构体
10

{
11
ElemType data;
12
NodeType *lch,*rch;
13
};
14
15
//队列
16
class SeQueue //定义队列类SeQueue
17

{
18
private:
19
NodeType elem[MAXSIZE]; //存储队列的数组个数
20
int front,rear; //队头,队尾
21
public:
22
SeQueue();
23
~SeQueue();
24
void AddQ(NodeType x); //入队函数
25
NodeType DelQ(); //出队函数
26
int IsEmpty() //判断队列非空函数
27
{
28
return front == rear;
29
}
30
};
31
32
//二叉树
33
class BiTree:public SeQueue //定义二叉树类BiTree 作为队列类SeQueue的派生类
34

{
35
public:
36
BiTree()
{ root = NULL; } //构造函数
37
~BiTree()
{ destroy(root); } //析构函数
38
void preorder() //先序遍历
39
{ preorder(root); }
40
void inorder() //中序遍历
41
{ inorder(root); }
42
void postorder() //后序遍历
43
{ postorder(root); }
44
45
void preordertswap() //交换左右子树
46
{ preorderswap(root); }
47
int theight() //求二叉树高度
48
{ return height(root); }
49
int tleaves() //求二叉树叶子结点个数
50
{ return leaves( root ); }
51
void levelorder() //按层遍历
52
{ levelorder(root); }
53
void creat0(); //建立树函数
54
private:
55
NodeType *root; //数据成员,根结点
56
NodeType *creat(); //建立二叉树递归算法
57
void preorder(NodeType *p); //先序遍历递归算法
58
void inorder(NodeType *p); //中序遍历递归算法
59
void postorder(NodeType *p); //后序遍历递归算法
60
void levelorder(NodeType *p); //按层遍历算法
61
void preorderswap(NodeType *p); //利用先序遍历方法交换左右子树
62
int height(NodeType *p); //求二叉树高度递归算法
63
int leaves(NodeType *p); //求二叉树叶子结点个数的递归算法
64
void destroy(NodeType* &p); //删除二叉树所有结点
65
};
66
67
//BiTree.cpp
68
#include "BiTree.h"
69
70
void BiTree::creat0() //建立树函数,
71
{
72
cout << "请按照树的先序遍历顺序组织数据" << endl;
73
cout << "若结点信息是int,把每个结点的空孩子以输入;" << endl;
74
cout << "一个结点的二叉树,输入:0 0;" << endl;
75
root = creat(); //调用私有creat()(工具函数);
76
}
77
NodeType * BiTree::creat() //递归建立二叉树算法
78

{
79
NodeType *p;
80
ElemType x;
81
cout << "\n 输入数据:";
82
cin >> x;
83
if( x == 0 ) //若左或右孩子为空,置当前指针这空.
84
p = NULL;
85
else
{
86
p = new NodeType;
87
p->data = x;
88
p->lch = creat(); //递归调用自身
89
p->rch = creat();
90
}
91
return p;
92
}
93
94
//先序遍历递归算法
95
void BiTree::preorder(NodeType *p) //先序遍历显示
96

{
97
if( p != NULL)
98
{
99
cout << p->data;
100
preorder( p->lch ); //递归调用自身
101
preorder( p->rch ); //递归调用自身
102
}
103
else
104
cout <<"#";
105
}
106
//中序遍历递归算法
107
void BiTree::inorder(NodeType *p) //中序遍历显示
108

{
109
110
if( p != NULL )
111
{
112
inorder( p->lch ); //递归调用自身
113
cout << p->data;
114
inorder( p->rch ); //递归调用自身
115
}
116
else
117
cout << "#";
118
119
}
120
//后序遍历递归算法
121
void BiTree::postorder(NodeType *p)
122

{
123
if( p != NULL )
124
{
125
126
postorder( p-> lch ); //递归调用自身
127
postorder( p-> rch ); //递归调用自身
128
cout << p->data;
129
}
130
else
131
cout <<"#";
132
}
133
void BiTree::preorderswap(NodeType *p) //利用先序遍历方法交换左右子树
134

{
135
if( p != NULL )
136
{
137
NodeType *r;
138
r = p->lch;
139
p->lch = p->rch;
140
p->rch = r;
141
//上面几条语句可以认为对结点的访问(交换左右孩子)
142
//替换了原来的:cout<<p->data<<" "; 语句
143
preorderswap( p->lch ); //递归调用自身
144
preorderswap( p->rch );
145
}
146
}
147
void BiTree::destroy(NodeType* &p) //删除二叉树所有结点
148

{
149
if(p != NULL)
150
{ destroy( p->lch );
151
destroy( p->rch );
152
delete p;
153
p = NULL;
154
}
155
}
156
int BiTree::height(NodeType *p) //求二叉树高度递归算法
157

{
158
int hl,hr;
159
if( p != NULL )
160
{
161
hl = height( p->lch );
162
hr = height( p->rch );
163
return ( hl > hr ? hl : hr ) + 1; //当前结点高度是以最大的(左右)子树的高度加得到
164
165
}
166
else
167
return 0;
168
169
}
170
//求二叉树叶子结点个数的递归算法
171
int BiTree::leaves(NodeType *p)
172

{
173
if( p == NULL ) //当前结点是否为空,当为空时就没有左右孩子
174
return 0;
175
if( ( p-> lch == NULL ) && ( p-> rch == NULL )) //当前结点是否有左右孩子
176
{
177
return 1;
178
}
179
return leaves( p-> lch ) + leaves( p-> rch ); //递归调用自身累加叶子结点个数
180
}
181
182
//按层遍历算法
183
void BiTree::levelorder(NodeType *p)
184

{
185
SeQueue q; //初始化一个队列
186
NodeType *t = p;
187
if (p)
188
{
189
q.AddQ(*p); //根结点非空则入队
190
}
191
while (!q.IsEmpty())
192
{
193
t = &(q.DelQ()); //队非空则将结点指针出队
194
cout << t->data; //并打印输出结点的数据值
195
if (t->lch)
196
{
197
q.AddQ(*(t->lch)); //存在左孩子则将其进队
198
}
199
else
200
cout << "#";
201
202
if (t->rch)
203
{
204
q.AddQ(*(t->rch)); //存在右孩子则将其进队
205
}
206
else
207
cout << "#";
208
}
209
}
210
211
//---------------------------------------------------------------------------
212
int main()
213

{
214
int k;
215
BiTree root0; //声明创建二叉树对象,调用构造函数
216
do
{ cout<<"\n\n";
217
cout<<"\n\n 1. 建立二叉树";
218
cout<<"\n\n 2. 交换左右子树";
219
cout<<"\n\n 3. 按层序遍历树";
220
cout<<"\n\n 4. 求二叉树深度 ";
221
cout<<"\n\n 5. 求叶子结点个数";
222
cout<<"\n\n 6. 结束程序运行";
223
cout<<"\n======================================";
224
cout<<"\n 请输入您的选择(0,1,2,3,4,5,6):";
225
cin>>k;
226
switch(k)
227
{
228
case 1:
{
229
cout << "\n\t 输入(0)结束:";
230
root0.creat0();
231
cout << "\n\t 先序遍历结果:";
232
root0.preorder();
233
cout << "\n\t 中序遍历结果:";
234
root0.inorder();
235
cout << "\n\t 后序遍历结果:";
236
root0.postorder();
237
} break;
238
case 2:
{
239
cout <<"\n\t 交换左右子树结果:";
240
root0.preordertswap();
241
cout << "\n\t 先序遍历结果:";
242
root0.preorder();
243
cout << "\n\t 中序遍历结果:";
244
root0.inorder();
245
cout << "\n\t 后序遍历结果:";
246
root0.postorder();
247
} break;
248
case 3:
{
249
cout << "\n\t 按层序遍历结果:";
250
root0.levelorder();
251
} break;
252
case 4:
{
253
int deep;
254
deep = root0.theight();
255
cout << "\n\t 树的深度是:" << deep;
256
} break;
257
case 5:
{
258
int leaf;
259
leaf = root0.tleaves();
260
cout << "\n\t 树的叶子结点个数是:" << leaf;
261
} break;
262
case 6: exit(0);
263
} // switch
264
cout<<"\n ----------------";
265
} while(k>=0 && k<6);
266
return 0;
267
}//-----
268
269
//Queue.cpp
270
#include "BiTree.h"
271
SeQueue::SeQueue() //初始化队列
272

{
273
front=0;
274
rear=0;
275
}
276
277
SeQueue::~SeQueue()
{};
278
//进队
279
void SeQueue::AddQ(NodeType x)
280

{
281
if((rear+1) % MAXSIZE==front)
282
cout<<"QUEUE IS FULL\n";
283
else
284
{
285
rear=(rear+1) % MAXSIZE;
286
elem[rear]=x; //将结点指针入队
287
}
288
}
289
290
//出队
291
NodeType SeQueue::DelQ()
292

{
293
NodeType q;
294
NodeType *t = NULL;
295
if(front==rear)
296
{
297
return *t; //返回空指针
298
}
299
300
else
301
{
302
front = (front+1) % MAXSIZE;
303
q = elem[front];
304
return q; //返回出栈的指针结点
305
}
306
五.心得:
这次实验不仅能巩固理解递归的方法,另一方面很能考验运用指针的能力,还有就是数据类型要使用得当.从levelorder(NodeType *p)函数来看,首先, DelQ()的返回类型要与BiTree二叉树结点指针的类型一至.其次,在递归过程中,传入AddQ(NodeType x)是整个结点的内容而不是一个指针.值得讨论的是,在定义存储队列的数组时如何节省存储空间?因为输入的结点个数:若为单边二叉树(全只有左结点或全只有右结点)则为任意个数(实验上这时只需要两个存储空间);若非单边二叉则要求结点最多的层个数不得超过MAXSIZE的值.后者时MAXSIZE的值(由性质2得)应该为2(h-1) (h为树的高度).这一点如何实现呢?BiTree类是SeQueue类的子类,height()函数是BiTree类的函数,如果能把高度传入SeQueue类的构造函数就可以通过一个for()语句来求得一个合适的值用以初始化MAXSIZE.那么就大大的节省了内存空间,并且还能实现输入节点的个数为任意的.但把高度传入SeQueue类的构造函数如何实现?还有其他要考虑的问题,我在今后的学习中深入了解.
六.使用说明
程序名为No4.exe运行环境为DOS,执行后显示:
在" 请输入您的选择(0,1,2,3,4,5,6):"后输入数字选择执行的功能.
测试结果:
1)选择1.后输入1240003500600.(如右图的一棵二叉树)
2)选择4得
3)选择5得
4)选择2得
5)选择3得
6)选择6或输入非"0,1,2,3,4,5,6"的数字
七.调试过程
本程序主要对按层序遍历操作功能进行调试.用Visual Studio 2008演示.首先把BiTree.h中的" #define MAXSIZE 30"改成" #define MAXSIZE 4",目的是从Visual Studio 2008的监视(Watch)窗口中得到更好的观察效果.
- 将光标移置BiTree.cpp文件的void BiTree::levelorder(NodeType *p)和第一条语句处Ctrl+F10开始调试
- 选择1.后输入1240003500600.(如右图的一棵二叉树)
再选择3.
-
这时Debugger仍停留在void BiTree::levelorder(NodeType *p)的第一条语句上.可以从自动监视窗口中看到已经建立了一棵二叉树,指针P指向二叉树的根结点.
-
在监视窗口1中输入变量名:q,t进行观察,F10单步调试,这时Debugger停留在 SeQueue q; 语句处

可以在监视1窗口中看到对象q的指针t都未合法初始化.
-
断续F10单步调试两步,这时Debugger停留在if(p)语句处,如图:

在监视1窗口输入!p进行观察,这时!p值为0,即if(p)判断语句值为真.
可以进入f(p)执行,F10断续,当Debugger停留在q.AddQ(*p)语句时可以从调用堆栈窗口中看到现在调用的函数为F11步入SeQueue类的AddQ(*p)函数.
-
这进Debugger停留在AddQ(*p)函数的第一条语句上.同可以从调用堆栈窗口中看到现在调用的函数为AddQ(*p)
在监视2窗口中输入rear, elem[rear]进行观察.同时在自动窗口中可以看到变量X已经指向二叉树的根结点.
-
断续F10真到AddQ(NodeType x)结束语句处,可以从监视2窗口看到根结点已经入队
-
F10后可以看到调用堆栈窗口中当前调用的是levelorder(NodeType *p)函数,同时在监视1窗口中输入!q.IsEmpty()观察到值为true,即可以进入while()控制语句执行.

F10单步至 t = &(q.DelQ())处,并F11步入q.DelQ(),这时Debugger停留在DelQ()的第一条语句上,可以从调用堆栈窗口中看到当前调用的是DelQ()函数.
-
在监视3窗口中输入q进行观察.断续F10到Debugger停留在DelQ()函数的return q语句时可以从监视窗口3中看到根结点已经出队
同时在自动窗口中可以看到从DelQ()函数返回了出队结点并赋给了指针t
-
F10单步调试直到Debugger停留在levelorder(NodeType *p)函数的cout << t->data语句上,这时可以在调用堆栈窗口中看到当前调用的是levelorder(NodeType *p)函数,在监视窗口1中输入t->date进行观察,已经取得根结点数据域的值
-
F10单步调试一步可以在DOS窗口中看到已经输出根结点
- Shift+F5退出调试,完成调试演示.