文章目录
1.定义
二叉树(Binary tree)是树形结构的一个重要类型。许多实际问题抽象出来的数据结构往往是二叉树形式,即使是一般的树也能简单地转换为二叉树,而且二叉树的存储结构及其算法都较为简单,因此二叉树显得特别重要。
二叉树特点是每个结点最多只能有两棵子树,且有左右之分。(有序树)
二叉树是n个有限元素的集合,该集合或者为空、或者由一个称为根(root)的元素及两个不相交的、被分别称为左子树和右子树的二叉树组成 。当集合为空时,称该二叉树为空二叉树。在二叉树中,一个元素也称作一个结点。
注意:这里的节点是逻辑结构上的表示,也就是数据元素,而不是物理结构上的表示
2.特点
我们发现,二叉树的定义好像和数组的定义几乎一样,都是n个有限元素的集合。但实际上二叉树它最明显的特点就是层次关系!
在二叉树的基本操作中,遍历操作是最重要的操作。因为它是其它许多操作的基础,同时递归是二叉树操作的特征,但是递归对我们把控栈深浅程度不够友好,并且任何递归都可以以迭代的方式实现,因此在实现二叉树的行为的时候,可以使用递归和迭代两种方式实现。
3.树的遍历
树的遍历是其它基于树这种数据结构操作的基础,因此我们在此实现多种遍历方式使我们树这种数据结构认识更加深刻。
遍历的方式有两种:递归和迭代
递归算法的优点是简洁,但一般而言,其执行效率不高,因为系统要维护一个工作栈以保证递归函数的正确执行。因此,有时需要把递归算法转化为非递归算法。
在使用迭代的方法进行树的遍历的时候,需要用到线性栈和线性队列,因此先实现这两个数据结构。
准备工作
线性栈实现
# define STACK_INIT_SIZE 100 // 顺序栈 (默认的)的初始分配最大容量
# define STACKINCREMENT 10 // (默认的)增补空间量
typedef struct {
ElemType* stack; // 存储数据元素的一维数组
int top; // 栈顶指针
int stacksize; // 当前分配的数组容量(以ElemType为单位)
int incrementsize; // 增补空间量(以ElemType为单位)
}SqStack;
void InitStack_Sq(SqStack& S, int maxsize = STACK_INIT_SIZE,
int incresize = STACKINCREMENT)
{
S.stack = (ElemType*)malloc(maxsize * sizeof(ElemType)); // 为顺序栈分配初始存储空间
if (!S.stack) exit(1); // 存储空间分配失败
S.top = -1; // 置栈空
S.stacksize = maxsize; // 顺序栈的当前容量
S.incrementsize = incresize; // 增补空间
}// InitStack_Sq
int StackLength_Sq(SqStack S)
{
return S.top + 1;
}// StackLength_Sq
bool Push_Sq(SqStack& S, ElemType e)
{ //在顺序栈的栈顶插入元素e
if (S.top == S.stacksize - 1) {
S.stack = (ElemType*)realloc(S.stack, (S.stacksize + S.
incrementsize) * sizeof(ElemType)); // 栈满,给顺序栈增补空间
if (!S.stack) return false; // 分配存储空间失败
S.stacksize += S.incrementsize;
}
S.stack[++S.top] = e; // 栈顶指针上移,元素e进栈
return true;
}// Push_Sq
bool Pop_Sq(SqStack& S, ElemType& e)
{ // 删除顺序栈栈顶元素,并让e返回其值
if (S.top == -1) return false;
e = S.stack[S.top--];
return true;
}// Pop_Sq
bool GetTop_Sq(SqStack S, ElemType& e)
{ //取顺序栈栈顶元素,并让e返回其值
if (S.top == -1) return false;
e = S.stack[S.top];
return true;
}// GetTop_Sq
bool StackEmpty_Sq(SqStack S)
{
if (S.top == -1) return true;
else return false;
}// StackEmpty_Sq
void DestroyStack_Sq(SqStack& S)
{
free(S.stack);
S.stack = NULL;
S.stacksize = 0;
S.top = -1;
}// DestroyStack_Sq
线性队列实现
#define QUENU_INIT_SIZE 100
#define QUENUINCREMENT 10
typedef struct {
ElemType* queue;
int front;
int rear;
int queuesize;
int incrementsize;
}SqQueue;
//初始化操作
void InitQueue_Sq(SqQueue& Q, int maxsize = QUENU_INIT_SIZE, int incresize = QUENUINCREMENT) {
Q.queue = (ElemType*)malloc(maxsize * sizeof(ElemType));
if (!Q.queue) exit(01);
Q.front = Q.rear = 0;
Q.queuesize = maxsize;
Q.incrementsize = incresize;
}
//求队列的长度
int QueueLength_Sq(SqQueue Q) {
return (Q.rear - Q.front + Q.queuesize) % Q.queuesize;
}
//进队操作
bool EnQueue_Sq(SqQueue& Q, ElemType e) {
//从队尾插入元素,成功则返回true
if ((Q.rear + 1) % Q.queuesize == Q.front) //队满给顺序队列增补空间
{
Q.queue = (ElemType*)realloc(Q.queue, (Q.queuesize + Q.incrementsize) * sizeof(ElemType));
if (!Q.queue) return false;
if (Q.front > Q.rear) //队首指针在队尾指针后面,重新确定位置
{
for (int i = Q.queuesize; i >= Q.front; i--) {
Q.queue[i + Q.incrementsize] = Q.queue[i];
}
Q.front += Q.incrementsize;
}
Q.queuesize += Q.incrementsize;
}
Q.queue[Q.rear] = e;
Q.rear = (Q.rear + 1) % Q.queuesize;
return true;
}
//出队操作
bool DeQueue_Sq(SqQueue& Q, ElemType& e) {
if (Q.front == Q.rear) return false;
e = Q.queue[Q.front];
Q.front = (Q.front + 1) % Q.queuesize;
return true;
}
//取队首元素操作
bool GetHead_Sq(SqQueue Q, ElemType& e) {
if (Q.front == Q.rear) return false;
e = Q.queue[Q.front];
return true;
}
bool QueueEmpty_Sq(SqQueue Q) {
return Q.front == Q.rear;
}
//撤销循环队列操作
void DestroyQueue_Sq(SqQueue& Q) {
free(Q.queue);
Q.queuesize = 0;
Q.front = Q.rear = 0;
}
3.1.先序遍历
遍历的终止方式:
- 访问根节点
- 先序遍历根的左子树
- 先序遍历根的右子树
3.1.1.迭代版本
void NRPreOrderBiTree(BiTree BT, void Visit(TElemType))
{ // 先序非递归遍历二叉树BT(利用栈)
SqStack S;
InitStack_Sq(S, MAX_BITREE_SIZE, 10); // 初始化栈S
while (BT || !StackEmpty_Sq(S)) // 当BT不空或者栈不空
{
if (BT) // BT不空
{
Visit(BT->data); // 访问根结点
Push_Sq(S, BT); //根指针进栈
BT = BT->lchild; // BT指向其左孩子
}
else // 根指针退栈,访问根结点,遍历右子树
{
Pop_Sq(S, BT); //根指针出栈
BT = BT->rchild; // BT指向其右孩子
}
}
}// NRPreOrderBiTree
3.1.2.递归版本
void PreOrderBiTree(BiTree BT, void Visit(TElemType))
{ // 先序递归遍历二叉树BT
if (BT) // BT不空
{
Visit(BT->data); // 先访问根结点
PreOrderBiTree(BT->lchild, Visit); // 再先序遍历左子树
PreOrderBiTree(BT->rchild, Visit); // 最后先序遍历右子树
}
}// PreOrderBiTree
3.2.中序遍历
遍历的终止方式:
- 中序遍历根的左子树
- 访问根节点
- 中序遍历根的右子树
3.2.1.迭代版本
void NRInOrderBiTree(BiTree BT, void Visit(TElemType))
{ // 中序非递归遍历二叉树BT(利用栈)
SqStack S;
InitStack_Sq(S, MAX_BITREE_SIZE, 10); // 初始化栈S
while (BT || !StackEmpty_Sq(S)) // 当BT不空或者栈不空
{
if (BT) // BT不空
{ // 根指针进栈,遍历左子树
Push_Sq(S, BT); // 根指针进栈
BT = BT->lchild; // BT指向其左孩子
}
else // 根指针退栈,访问根结点,遍历右子树
{
Pop_Sq(S, BT); //根指针出栈
Visit(BT->data); // 访问根结点
BT = BT->rchild; // BT指向其右孩子
}
}
}// NRInOrderBiTree
3.2.2.递归版本
void InOrderBiTree(BiTree BT, void Visit(TElemType))
{ // 中序递归遍历二叉树BT
if (BT) // BT不空
{
InOrderBiTree(BT->lchild, Visit); // 先中序遍历左子树
Visit(BT->data); // 再访问根结点
InOrderBiTree(BT->rchild, Visit); // 最后中序遍历右子树
}
}// InOrderBiTree
3.3.后序遍历
遍历的终止方式:
- 后序遍历根的左子树
- 后续遍历根的右子树
- 访问根节点
3.3.1.迭代版本
void NRPostOrderBiTree(BiTree BT, void Visit(TElemType))
{ // 后序非递归遍历二叉树BT(利用栈)
SqStack S;
InitStack_Sq(S, MAX_BITREE_SIZE, 10); // 初始化栈S
BiTree p, q;
int flag;
if (!BT) return;
p = BT;
do
{
while (p) // 为非空二叉树,向左走到尽头
{
Push_Sq(S, p); // p进栈
p = p->lchild; // p指向其左孩子
}
q = NULL; flag = 1;
while (!StackEmpty_Sq(S) && flag)
{
GetTop_Sq(S, p); // 取栈顶元素
if (p->rchild == q) // 其右孩子不存在或已访问
{
Pop_Sq(S, p); //栈顶元素出栈
Visit(p->data); // 访问p所指结点
q = p; // q指向刚刚访问的结点
}
else
{
p = p->rchild; // p指向其右孩子
flag = 0;
}
}
} while (!StackEmpty_Sq(S));
}// NRPostOrderBiTree
3.3.2.递归版本
void PostOrderBiTree(BiTree BT, void Visit(TElemType))
{ // 后序递归遍历二叉树BT
if (BT) // BT不空
{
PostOrderBiTree(BT->lchild, Visit); // 先后序遍历左子树
PostOrderBiTree(BT->rchild, Visit); // 再后序遍历右子树
Visit(BT->data); // 最后访问根结点
}
}// PostOrderBiTree
3.4.层序遍历
3.4.1.迭代版本
void LevelOrderBiTree(BiTree BT, void Visit(TElemType))
{ // 层序递归遍历BT(利用队列)
SqQueue Q;
BiTree p;
if (BT) // BT不空
{
InitQueue_Sq(Q, MAX_BITREE_SIZE, 10); // 初始化队列Q
EnQueue_Sq(Q, BT); // 根指针入队
while (!QueueEmpty_Sq(Q)) // 队列不空
{
DeQueue_Sq(Q, p); // 出队元素(指针),赋给p
Visit(p->data); // 访问p所指结点
if (p->lchild != NULL) // p有左孩子
EnQueue_Sq(Q, p->lchild); // 入队p的左孩子
if (p->rchild != NULL) // p有右孩子
EnQueue_Sq(Q, p->rchild); // 入队p的右孩子
}
}
}// LevelOrderBiTree
4.测试程序
本博客所有的数据结构的实现和测试程序代码均打包好,下载压缩文件即可直接使用。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdlib>
using namespace std;
typedef char TElemType; //定义树中元素类型TElemType为字符型
TElemType Nil = '#'; //用'#'表示空
# define MAX_BITREE_SIZE 100 // 二叉树中的最大结点数
#include "BiTree.h"
void Visit(TElemType e) // 访问函数定义为输出操作
{
cout << e << ' ';
}
void main()
{
int i;
BiTree BT, p, c;
TElemType e1, e2;
InitBiTree(BT); // 初始化二叉树BT
cout << "构造空二叉树后,树空否?" << BiTreeEmpty(BT);
cout << "(1:是 0:否)。树的深度=" << BiTreeDepth(BT) << endl;
cout << "请按先序输入二叉树(用'#'表示子树为空):" << endl;
CreateBiTree(BT); // 建立二叉树BT
cout << "建立二叉树后,树空否?" << BiTreeEmpty(BT);
cout << "(1:是 0:否)。树的深度=" << BiTreeDepth(BT) << endl;
cout << "先序递归遍历二叉树:";
PreOrderBiTree(BT, Visit); // 先序递归遍历二叉树BT
cout << endl << "中序递归遍历二叉树:";
InOrderBiTree(BT, Visit); // 中序递归遍历二叉树BT
cout << endl << "后序递归遍历二叉树:";
PostOrderBiTree(BT, Visit); // 后序递归遍历二叉树BT
cout << endl << "先序非递归遍历二叉树:";
NRPreOrderBiTree(BT, Visit); // 先序非递归遍历二叉树BT
cout << endl << "中序非递归遍历二叉树:";
NRInOrderBiTree(BT, Visit); // 中序非递归遍历二叉树BT
cout << endl << "后序非递归遍历二叉树:";
NRPostOrderBiTree(BT, Visit); // 后序非递归遍历二叉树BT
cout << endl << "层序遍历二叉树:";
LevelOrderBiTree(BT, Visit); // 层序递归遍历二叉树BT
cout << endl << "用凹入表的形式打印二叉树BT:" << endl;
PrintBiTree(BT, 1); // 输出二叉树BT
system("pause");
}
程序是以先序的形式初始化,因此测试程序的输入为:
ABD#G###CE##F##
测试程序输入以上命令后就可以看到不同遍历方式结果的不同!
5.总结
之所以专门用一篇博文来讲述二叉树这些概念是因为在查找算法中数表查找涉及到很多的概念和相关代码,如果直接用代码去讲述可能还是不能够解决你的问题。
遍历算法非常简单,在这里就不做过多详细的解答,无非就是一些数据结构的组合。
先在逻辑结构把它想清楚,然后再把它映射到物理结构上。