您的位置:首页 > 理论基础 > 数据结构算法

数据结构 - 二叉树 - 遍历算法代码

2019-04-21 17:30 543 查看

说明:

二叉树的常见遍历有四种:前、中、后、层序遍历。
它们是最基本的,学会这些遍历后才能使用二叉树来做一些任务。
下面是遍历的代码部分。大概思路为:首先建立二叉树,用数组保存下来,然后在数组上进行操作;其次将数组保存的二叉树导入链表中,然后在链表上进行操作。

另外,还有一些地方未完成,等完成后在加上。

注:

(1)main函数首先定义了数组的最大长度MaxSize=100;
(2)将数组初始化全为-1(其实初始化为0最好,就是后面保存二叉树时结点空的表示方法,原因看代码就知道了);
(3)二叉树保存到数组中时,保存的是完全二叉树的形式,没有结点的地方设置为0。输入-1表示保存完成并退出。

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

// 定义相关不变量
using ElemType = int; // 树结点保存值的类型,可以修改
enum Flage{ LEFT, RIGHT };// 两次会晤法的后序遍历中使用
const int ROOT = 0;// 根节点的序号
const int INIT = -1;// 数组初始化值
const int EMPTY = 0;// 某结点为空的表示
const int MaxSize = 100;//数组最大值

/*****************************************定义结构**********************************************/
template<class T>//定义二叉树结点类型
struct TreeNode{
TreeNode(T val) :data(val)
{
lchild = nullptr;
rchild = nullptr;
}

T data;
TreeNode<T> *lchild;
TreeNode<T> *rchild;
};

template<class T>//定义栈中的每个结点的类型
struct StackElem{
TreeNode<T> * pnode;
Flage flage;
};

template<class T>//栈:不能将模板函数的定义和声明放在不同文件中
struct Stack{
Stack():top(-1) {}
inline bool empty(void)
{
return top == -1 ? true : false;
// or return top == -1;
}
inline bool full(void)
{
return top >= MaxSize - 1;
}
bool push(T pNode)
{
if (!full())
{
data[++top] = pNode;
return true;
}

return false;
}
T getop(void)
{
return data[top];
}
void pop(void)
{
if (!empty())
--top;
return;
}

T data[MaxSize];
int top;
};

template<class T> //非循环队列,建议用循环队列做,不然死的很惨!!!!
struct Quene{
Quene()
{
first = 0;
end = 0;
}
bool empty()
{
if (first == -1 || first == end)
return true;
else
return false;
}
bool full()
{
return  end >= MaxSize;
}
T getop()
{
if (first >= 0)
return data[first];
else
exit(-1);
}
bool pop()
{
if (empty())
return false;
++first;
return true;
}
void push(T val)
{
if (full())
exit(-1);
data[end++] = val;
return;
}

T data[MaxSize];
int first;
int end;//尾指针指向左后一个元素的下一个位置
};

/**********************************************generate tree***********************************************/
void InitSqBiTree(ElemType *tree)//初始化
{
for (int i = ROOT; i < MaxSize; ++i)
{
tree[i] = EMPTY;
}
return;
}
void CreateSqBiTree(ElemType *tree)//将树存入数组中
{
int i = ROOT;
while (i < MaxSize && cin >> tree[i] && tree[i] != INIT)
++i;
return;
}
inline bool IsEmpty(const ElemType *tree)//判断是否为空树
{
if (tree[ROOT] == EMPTY || tree[ROOT] == INIT)
return true;
return false;
}
void ShowSqBiTree(const ElemType *tree)//按数组序号输出
{
int i = ROOT;

while (tree[i] != INIT && i < MaxSize)
cout << tree[i++] << " ";
cout << endl;

return;
}
template<class T>
inline void Show(TreeNode<T> *pNode)
{
cout << pNode->data << " ";
return;
}

/**********************************************遍历二叉树***************************************************/
//遍历函数1
void Array_PreOrder_DiGui(const ElemType *tree, int n = 1);//递归算法;声明处写默认参数,定义处不能再写了,否则报错。
void Array_PreOrder_Stack(const ElemType *tree);//非递归算法
void Array_InOrder_DiGui(const ElemType *tree, int node = 1);
void Array_InOrder_Stack(ElemType *tree);
void Array_PostOrder_DiGui(ElemType *tree, int node = 1);
void Array_LevelOrder_Quene(ElemType *tree);

//遍历函数2,使用模板函数,使用范围是链表存储下的
template<class T>
TreeNode<T> * array2link(const ElemType *tree, int n=1); //array convert to linklist
template<class T>
void LinkList_PreOrder_DiGui(TreeNode<T> *pNode);
template<class T>
void LinkList_PreOrder_Stack(TreeNode<T> *ptree);
template<class T>
void LinkList_InOrder_DiGui(TreeNode<T> *pNode);
template<class T>
void LinkList_InOrder_Stack(TreeNode<T> *pNode);
template<class T>
void LinkList_PostOrder_DiGui(TreeNode<T> *pNode);
template<class T>
void LinkList_PostOrder_Stack(TreeNode<T> *pNode);

/*****************************************main()****************************************************/
int main()
{

cout << "--------------------生成树------------------------" << endl;

ElemType tree[MaxSize];

// 初始化数组,初始化方法可以用尾指针代替,这样更方便
cout << "tree is init..." << endl;
InitSqBiTree(tree);

// 在数组里创建二叉树
cout << "tree is created." << endl;
CreateSqBiTree(tree);

// 按顺序输出数组里的值
cout << "tree is : ";
ShowSqBiTree(tree);

cout << "--------------------数组遍历------------------------" << endl;

// 先序遍历+array+递归
cout << "Array_PreOrder_DiGui : ";
Array_PreOrder_DiGui(tree);
cout << endl;

// 先序遍历+array+栈
cout << "Array_PreOrder_Stack : ";
Array_PreOrder_Stack(tree);

// 中序遍历+array+递归
cout << "Array_InOrder_DiGui : ";
Array_InOrder_DiGui(tree);
cout << endl;

// 中序遍历+array+栈
cout << "Array_InOrder_Stack : ";
Array_InOrder_Stack(tree);
cout << endl;

// 后序遍历+array+递归
cout << "Array_PostOrder_DiGui : ";
Array_PostOrder_DiGui(tree);
cout << endl;

// 层序遍历+array+队列
cout << "Array_LevelOrder_Quene : ";
Array_LevelOrder_Quene(tree);
cout << endl;

while (1);
cout << "--------------------链表遍历------------------------" << endl;

// array -> linklist
TreeNode<int> * pTree = nullptr;
cout << "array to linklist..." << endl;
pTree = array2link<int>(tree);

// 先序 + 链表 + 递归
cout << "LinkList_PreOrder_DiGui : ";
LinkList_PreOrder_DiGui<int>(pTree);
cout << endl;
// 先序 + 链表 + 栈
cout << "LinkList+PreOrder+Stack : ";
LinkList_PreOrder_Stack<int>(pTree);
cout << endl;
// 中序 + 链表 + 递归
cout << "LinkList+InOrder+DiGui : ";
LinkList_InOrder_DiGui<int>(pTree);
cout << endl;
// 中序 + 链表 + 栈
cout << "LinkList+InOrder+Stack : ";
LinkList_InOrder_Stack<int>(pTree);
cout << endl;
// 后序 + 链表 + 递归
cout << "LinkList_PostOrder_DiGui : ";
LinkList_PostOrder_DiGui<int>(pTree);
cout << endl;
// 后序 + 链表 + 栈1
cout << "LinkList_PostOrder_Stack : ";
LinkList_PostOrder_Stack<int>(pTree);
cout << endl;

while (1);
return 0;
}
/****************************************************part1****************************************************/
//递归的说明:对于先序遍历,给你任意一个树中的结点
//(1)先访问输出这个结点值,
//(2)如果...遍历它的左子树,
//(3)如果...遍历它的右子树。
//先写出这三条命令,在添加细节。
void Array_PreOrder_DiGui(const ElemType *tree, int n)
{
//input:  tree  树的根结点指针
//        n     指第几个结点,根结点为第一结点
//output: none
//notice:   先序遍历先输出结点,在看左孩子,最后看右孩子

//1.先输出结点
cout << tree[n - 1] << " ";
//2.看左孩子
int lchild = 2 * n;
//不为空0,且没有超出范围则访问孩子结点。注意这里区别开数组下标和第几个结点
if ((tree[lchild-1] != EMPTY) && (tree[lchild-1] != INIT))
Array_PreOrder_DiGui(tree, lchild);
//3.看右孩子
int rchild = 2 * n + 1;
if (tree[rchild-1] != EMPTY && tree[rchild-1] != INIT)
Array_PreOrder_DiGui(tree, rchild);

return;
}
void Array_PreOrder_Stack(const ElemType *tree)
{
int stack[MaxSize];//stack里面存放的是树的序号(不是数组的序号,他们差1),在链表里就是指向链表的指针。
const int empty = -1;
int top = empty;
int node = 1, lchild, rchild;

if (IsEmpty(tree))
{
cout << "empty" << endl;
return ;
}
//先将根结点的序号1入栈,
stack[++top] = node;

while (top != empty)
{
//1.访问结点的值并出栈(访问时要减1)
node = stack[top--];
cout << tree[node-1] << " ";
//2.先入栈右孩子
rchild = 2 * node + 1;
if ((tree[rchild - 1] != EMPTY) && (tree[rchild - 1] != INIT))
stack[++top] = rchild;
//3.再入栈左孩子
lchild = 2 * node;
if ((tree[lchild - 1] != EMPTY) && (tree[lchild - 1] != INIT))
stack[++top] = lchild;
}
cout << endl;
return;
}
void Array_InOrder_DiGui(const ElemType *tree, int node)
{

//第一部分:先遍历左子树
int lchild = 2 * node;
if (tree[lchild-1] != EMPTY && tree[lchild-1] != INIT)
Array_InOrder_DiGui(tree, lchild);

//第二部分:再输出结点值
cout << tree[node - 1] << " ";

//第三部分:最后遍历右子树
int rchild = 2 * node + 1;
if (tree[
7ff7
rchild-1] != EMPTY && tree[rchild-1] != INIT)
Array_InOrder_DiGui(tree, rchild);

return;
}
void Array_InOrder_Stack(ElemType *tree)
{

Stack<ElemType> stack;
const int empty = -1;
int node = 1;

//思路:用循环代替递归
while (stack.top != empty || (tree[node - 1] != EMPTY && tree[node - 1] != INIT))
{
//1. 一直找左子树,直到左子树为空
while (tree[node-1]!=EMPTY && tree[node-1]!=INIT)
{
stack.data[++stack.top] = node;
node = 2 * node;
}

//为啥要加if判断一下??? 感觉可以把if里面的写到外面来
if (stack.top != empty)
{
//2. 访问中间结点
//不能直接使用前面的node,因为它已经超出边界,或者是空,这里就需要将新的值(栈中值)赋给node,将指针指向以前的结点
node = stack.data[stack.top];
cout << tree[node-1] << " ";
//中间结点出栈
stack.top--;

//3. node置右结点
node = 2 * node + 1;
}
}

return;
}
void Array_PostOrder_DiGui(ElemType *tree, int node)
{

if (tree[node - 1] == EMPTY || tree[node - 1] == INIT)
{
return;
}

Array_PostOrder_DiGui(tree, 2 * node);
Array_PostOrder_DiGui(tree, 2 * node + 1);
cout << tree[node - 1] << " ";

return;
}
//void Array_PostOrder_Stack(ElemType *tree) //没有写
void Array_LevelOrder_Quene(ElemType *tree)
{
// 层序遍历的思路:
// 先遇到的结点先访问,所以使用队列来完成操作。
// (1)从队列中抛出队首结点,访问结点的值
// (2)判断已抛出结点的孩子结点是否存在(不为空),存在,则按顺序先将左孩子入队,再将右孩子入队。
Quene<ElemType> quene;
int node = 1;//这里结点指针从一开始是必须的,不然在第一次求孩子的时候始终为0。并且保存在队列里的指针也是node,它和访问结点时(tree[node-1])之间差了1!!!
int lchild = 0;
int rchild = 0;

// 方法一:
// 先将根结点入队,再进入循环,这样就可以利用队列是否为空作为循环结束的条件。
if (tree == nullptr)//先判断二叉树是否为空,是则返回;不是则继续。
return;
quene.push(node);//先将根结点入队列,此时队里已经有了根结点。这也是第一种方法,后面第二种方法将不如队列。
while (!quene.empty())//判断队列是否为空,不空执行循环体,空则跳出。
{
node = quene.getop();//先得到队首结点
cout << tree[node - 1] << " ";//并访问结点
quene.pop();//抛出队首结点
lchild = 2 * node;//前面已经保存了抛出结点的指针,这里计算出抛出结点的孩子
if (tree[lchild - 1] != EMPTY && tree[lchild - 1] != INIT)//有左孩子,则将孩子入队
{
quene.push(lchild);
}
rchild = lchild + 1;
if (tree[rchild - 1] != EMPTY && tree[rchild - 1] != INIT)//有右孩子,则将孩子入队
{
quene.push(rchild);
}
}

// 求深度or某个结点的层数都可以用层序遍历来做
// 二叉树若存储在数组中,则为完全二叉树的存储方法。完全二叉树有计算深度的公式
// 在上面while退出后node一定指向最后一个结点
int depth = log(node)/log(2) + 1;
cout << endl << "Array_LevelOrder_Quene : depth is " << depth;

return;
}

/******************************************************part2**************************************************/
//将数组转化为链表存储后,进行的遍历

template<class T>
inline bool IsEmpty(const TreeNode<T> *ptree)
{
return ptree == nullptr ? true : false;
//return ptree==nullptr;
}

template<class T>
TreeNode<T> * array2link(const ElemType *tree, int n)
{
//产生结点并赋值,使用结构体的构造函数来初始化。这里有点坑,就是即用了面向对象有用了面向过程的思想,这里是为了熟练使用。
//注意内存的管理,即使删除new分配的内存,可以把new写到类或结构中,在析构函数里添加delete来释放动态内存。
TreeNode<T> * node = new TreeNode<T>(tree[n-1]);
//产生左结点并挂载
int arr_lchild = 2 * n;
if ((tree[arr_lchild - 1] != EMPTY) && (tree[arr_lchild - 1] != INIT))
{
//先调用递归创建孩子,再将父亲结点的左指针指向它。
//理解的时候不需要看array2link函数怎么实现,只需要知道它返回的是指向孩子结点的指针。
//将父亲结点的左指针指向孩子结点即可。
//这里需要理解递归思想,而不是递归的过程。只关心某一个,其他的调用(递归)函数即可。
node->lchild = array2link<int>(tree, arr_lchild);
}
//产生右结点并挂载,同理。
int arr_rchild = 2 * n + 1;
if ((tree[arr_rchild - 1] != EMPTY) && (tree[arr_rchild - 1] != INIT))
{
node->rchild = array2link<int>(tree, arr_rchild);
}

return node;
}

template<class T>
void LinkList_PreOrder_DiGui(TreeNode<T> *pNode)
{
if (pNode == nullptr)
return;

Show(pNode);
LinkList_PreOrder_DiGui(pNode->lchild);
LinkList_PreOrder_DiGui(pNode->rchild);

return;
}

template<class T>
void LinkList_PreOrder_Stack(TreeNode<T> *tree)
{
//不要使用这种方法,使用下面那个,目的是方便和中序和后序遍历一起记忆,形成一个整体,就像递归的三种方法一样。
#if 1
//使用数组栈,栈中保存指向结点的指针!!不是结点的值
Stack<TreeNode<T> *> stack;
TreeNode<T> *pnode;

if (IsEmpty<T>(tree))
{
cout << "empty" << endl;
return;
}
//先将指向根节点的指针入栈
stack.data[++stack.top] = tree;
//如果栈不空,循环每一个结点
//只写某个结点的情况,利用循环遍历所有结点。
while (stack.top != -1)
{
//1.出栈
pnode = stack.data[stack.top--];
cout << pnode->data << " ";
//2.先右入栈
if (pnode->rchild != nullptr)
stack.data[++stack.top] = pnode->rchild;
if (pnode->lchild != nullptr)
stack.data[++stack.top] = pnode->lchild;
}
#endif

#if 0
//未完成!!!
#endif

return;
}

template<class T>
void LinkList_InOrder_DiGui(TreeNode<T> *pNode)
{
// 递归结束条件
if (pNode == nullptr)
return;
//开始递归
LinkList_InOrder_DiGui(pNode->lchild);
Show(pNode);
LinkList_InOrder_DiGui(pNode->rchild);

return;
}

template<class T>
void LinkList_InOrder_Stack(TreeNode<T> *pNode)
{	//使用类设计方法实现的,类和结构一样的用法

Stack<TreeNode<T> *> stack;//创建栈,用来保存指向二叉树的指针。
TreeNode<T> *pnode = pNode;//用来遍历二叉树用

while (!stack.empty() || pnode != nullptr)  // 前者是终止判断条件,后者是起始判断条件
{
// 1.遍历所有左子树的左边, 退出while时为左节点为空
while (pnode != nullptr)//判断结点是否为空,非空则进入,空则说明到头了
{
stack.push(pnode);//入栈
pnode = pnode->lchild;//指向左节点
}
if (!stack.empty())//没明白这里为啥要判断一下,不判断感觉也可以运行。???
{
pnode = stack.getop();//拉回来,这里很关键,如果不从栈中取值,则pnode指的已经不是有效结点了,指的是空结点了,如果在输出就出错了。
Show(pnode);//2.访问
stack.pop();//访问完后就可以出栈了,即丢弃。但是丢弃后我们还需要它的右孩子,那怎么?
//(1)若先让中间结点不出栈,先让右孩子指针进栈,那么要想让中间结点再出栈,已经出不去了。
//(2)如果先让中间结点出栈,那怎么才能访问它的右孩子呢,因为出栈相当于删除,都没有了该结点,就不能访问右孩子。
//   解决办法是,在出栈前,将中间结点的指针保存起来,这里对应pnode=stack.getop(),再让中间结点出栈,通过保存的指针来访问右孩子
pnode = pnode->rchild;// 3.指向右孩子,重新开始。
}
}
return;
}

template<class T>
void LinkList_PostOrder_DiGui(TreeNode<T> *pNode)
{
if (pNode == nullptr)
return;

LinkList_PostOrder_DiGui(pNode->lchild);
LinkList_PostOrder_DiGui(pNode->rchild);
Show(pNode);

return;
}

template<class T>
void LinkList_PostOrder_Stack(TreeNode<T> *pNode)
{
#if 0
// 方法一:两个while, 一个rchild_cache一个flage
Stack<TreeNode<T> *> stack;
TreeNode<T> *pnode = pNode; //用来遍历二叉树的指针
TreeNode<T> *rchild_cache = nullptr;
int flage=1;

if (pnode == nullptr)
return;

//不能使用while (!stack.empty()||pnode!=nullptr)来做判断
//因为在后序遍历完成后,pnode指向root结点,虽然栈已经被置空,但是pnode指向root(不为空),所以循环又会执行,一直重复输出后序遍历。
//解决办法是(1)在while中去掉pnode!=nullptr,这句作用是判断一开始二叉树是否为空的,可以在while的前面加上判断空树的语句即可。
//(2)改成while(!stack.empty())后会发现一开始就跳过循环,因为一开始stack为空,进而使用了do-while()。
do //使用do-while;用while会一直循环下去
{
// 1.先找左边
while (pnode != nullptr)
{
stack.push(pnode);
pnode = pnode->lchild;
}

rchild_cache = nullptr;//将标记为置空。
flage = 1;//为1表示需要处理栈顶结点,好好理解这个标志位的设置

//2.左边找完后,使用if-else判断是遍历右边的结点,还是访问当前结点
while (!stack.empty() && flage == 1)//第一次必然满足,加while的目的是利用循环来处理当走到尽头后,之后就是一个一个的输出结点值(说的是if的情况)
{									//如果不加循环,程序会在叶结点附近死循环下去,不能向上访问结点。如果没有走到尽头,就继续走下去(说的是else情况)
pnode = stack.getop(); //拉回

// 2.1 访问当前结点
if (pnode->rchild == nullptr || pnode->rchild == rchild_cache)//右孩子为空或已访问的右结点,则访问当前结点。空表示右边到头了
{
Show(pnode);//访问结点
rchild_cache = pnode;//记录当前结点为已访问状态
stack.pop();//访问后就可以出栈了
}
// 2.2 遍历右结点
else//右孩子不为空 且 未访问过,则将指针指向右孩子,继续向左遍历,故把flage置0,使得跳出循环。
{
pnode = pnode->rchild;//指向右孩子
flage = 0;//为0表示栈顶结点处理完毕,也可以认为是
}
}
} while (!stack.empty());
//虽然后序遍历和前面两在代码形式上有很太的不同,但在后序遍历中还是存在先中遍历的思想,比如
//从头找向左一直找;else里面,左边找不下去,就往找它的右子树,但只找一次,找到之后又进行向左找到头的办法;
//不同点是在后序中加了两个变量rchild_cache和flage。rchild_cache表示:是遍历右孩子,还是访问我自己。即起到控制作用。
//flage表时:控制循环,这里也可以替换为 break语句,将它写在else里面。 但是使用break语句有一些问题,能用其他语句代替最好。

// 问题:查break的后台执行了什么。
#endif

#if 1
//方法二:两次会晤法(进栈)

Stack< StackElem<T> > stack;//建立一个栈,栈中每个元素都有两个值,一个是结点指针,一个是flage;这里将结构传入栈中!!!!
TreeNode<T> *pnode = pNode;//用来遍历二叉树的指针,它会不停的移动
StackElem<T> temp;//这个也是个关键点,每次将temp的值进栈,而不是将pnode的值进栈,进栈变量的类型必须和栈的类型一样!!!!

if (pnode == nullptr)
return;

while (true)
{
//一直向左遍历,直到结点为空
while (pnode != nullptr)
{
// 错误进栈法
// stack.push(pnode);//这里细节不再和之前的入栈相同,不过总体一样。

// 正确法
temp.pnode = pnode;//先设置进栈结点里的值,再进栈
temp.flage = LEFT;
stack.push(temp);
pnode = pnode->lchild;
}

if (!stack.empty())
{
temp = stack.getop();
stack.pop();
pnode = temp.pnode;

//如果是右
while (temp.flage == RIGHT) //上面方法中的while一样,都是当flage=RIGHT时一直向上访问结点
{
Show(pnode);// 访问
if (stack.empty()) // 相关处理,指向上一个点
return;
else
{
temp = stack.getop();//拉向上一节点
stack.pop();//抛出栈顶结点
pnode = temp.pnode;
}
}
//如果是左
if (temp.flage == LEFT)
{
temp.flage = RIGHT;//置右。没有修改栈里面的flage,temp是临时的变量而已。所以书上给出了抛出,输入。
stack.push(temp);//入栈。
pnode = pnode->rchild;//指向右孩子。
}
}
}

#endif
return;
}

参考

  1. 数据结构-联考辅导教程(2013版) 李春葆
  2. 数据结构-基于c++模板类的实现 余腊生
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: