文章目录
二叉树的遍历及应用
二叉树的遍历就是尊从某种顺序,访问二叉树中的所有节点,使得每一个节点被访问有且仅有1次。二叉树是一种非线性结构,因此按照某种规则遍历二叉树实际就是:试图找到二叉树节点的一个线性序列。
一、递归遍历算法
根据访问节点的顺序,我们将遍历分为前序,中序,后序三种。
先定义二叉树节点:
#include <stdio.h>
#include <stdlib.h>
#include <stack>
using namespace std;
typedef struct node {
char data;
struct node* lchild;
struct node* rchild;
}BTNode;
1.前序
前序遍历按照以下的步骤递归执行:
- 访问根节点
- 先序遍历左子树
- 先序遍历右子树
通俗的理解为:遇到一个节点就访问一个数据,随后一路往左下走,左下没路了,再往右下走。
void PreOrder_recur(BTNode* bt) {
//DLR
if (bt != NULL) {
printf("%c", bt->data);
PreOrder_recur(bt->lchild);
PreOrder_recur(bt->rchild);
}
}
2.中序
中序遍历按照以下的步骤递归执行:
- 中序遍历左子树
- 访问根节点
- 中序遍历右子树
通俗的理解为:一路往左下走,直到没路可走时,访问数据,随后在看看右下能不能走。
void InOrder_recur(BTNode* bt) {
//LDR
if (bt != NULL) {
InOrder_recur(bt->lchild);
printf("%c", bt->data);
InOrder_recur(bt->rchild);
}
}
3.后序
后序遍历按照以下的步骤递归执行:
- 后序遍历左子树
- 后序遍历右子树
- 访问根节点
通俗的理解为:一路往左下走,再一路往右下走,知道左右都走不下去了,再访问数据。
void PostOrder_recur(BTNode* bt) {
//LRD
if (bt != NULL) {
PostOrder_recur(bt->lchild);
PostOrder_recur(bt->rchild);
printf("%c", bt->data);
}
}
二、递归遍历算法的应用举例
1.创建二叉树
首先我们需要扩充二叉树,在空子树处添加虚节点
∗
*
∗。如下图所示
要采用先序递归遍历算法构建这样一棵树,我们需要往函数传入一个数组str=['A','B','D','*','F','*','*','*','C','E','*','*','*']
其实*号是为了告诉程序这里应置空,下面没有元素了。
void Recur_CreateBinTree(BTNode* bt,char* str) {
//采用先序序列的遍历方法
if (*str != ';')
return;//终止符
if(*str!='*'){//不是虚节点
bt = (BTNode*)malloc(sizeof(BTNode));
bt->data = *str++;//生成节点
Recur_CreateBinTree(bt->lchild, str);
Recur_CreateBinTree(bt->rchild, str);
}
else {//如果是虚节点,置空
bt = NULL;
}
}
2.计算二叉树叶节点个数
首先要有终止条件:
- 如果当前节点为空,说明已经触底,返回0
- 如果当前节点的左右儿子都是空节点,说明自己是叶子节点,返回1
递归:
站在根节点处,那么这棵树叶节点的个数是左子树的叶节点数+右子树的叶节点数
int CountLeaves(BTNode* bt) {//返回叶子节点的个数
if (bt == NULL)
return 0;
if (bt->lchild == NULL && bt->rchild == NULL) {
return 1;
}
else {
return CountLeaves(bt->lchild) + CountLeaves(bt->rchild);
}
}
3.计算二叉树的深度
int BinTreeDepth(BTNode* bt) {
if (bt == NULL) return 0;
else {
int left_depth = BinTreeDepth(bt->lchild);
int right_depth = BinTreeDepth(bt->rchild);
return (left_depth < right_depth) ? right_depth + 1 : left_depth + 1;
}
}
三、非递归遍历算法
要抓住各个节点的前驱和后继。
1.前序遍历
基本思想:利用栈stack
操作步骤:
- 1.将根节点压入stack
- 2.如果stack不为空,就取出栈顶元素(记作p)
- 3.如果p不是NULL,那么访问p的数据。接着将p的右儿子,左儿子(包括空指针)依次压入栈中
- 4.回到第二步,循环
void pre_order(BTNode* bt) {
stack<BTNode*>sta;
sta.push(bt);
while (!sta.empty()) {
BTNode* p = sta.top();
sta.pop();
if (bt != NULL) {
cout << p->data << endl;
sta.push(p->rchild);
sta.push(p->lchild);
}
}
}
2.中序遍历
基本思想:中序遍历总是优先访问左下的那个元素。所以在第一次遇到某个节点时,只要非空就会压入栈中。之后从栈中取出元素访问并会访问右边的元素。——先进左子树再访问,p的前驱是左儿子
操作步骤:
- 1.创建指针p指向根节点
- 2.只要p还不是NULL,就一直往左下走(后继为左儿子),并把路过的节点压入栈中。
- 3.p已经是NULL,将栈顶元素(正是NULL的前驱)取出赋值给p,同时访问p的数据,并且进入p的右子树。
- 4.只要p!=NULL或者stack没有空,就回到步骤2,循环。
void in_order(BTNode* bt) {
stack<BTNode*>sta;
BTNode* p = bt;
while (p || !sta.empty()) {
if (p) {
sta.push(p);
p = p->lchild;
}
else {
p = sta.top();
sta.pop();
cout << p->data << endl;
p = p->rchild;
}
}
}
3.后续遍历
基本思想:与中序遍历不同,后续遍历需要先进入右子树,最后才访问数据。即对于节点p,需要先进入右子树再去栈顶元素访问。p的前驱是右儿子。
操作步骤:
- 1.创建指针p指向根节点
- 2.只要p还不是NULL,就一直往左下走(后继为左儿子),并把路过的节点压入栈中。
- 3.p已经是NULL,将栈顶元素赋值给p(不能pop出来),进行判断,如果p的右子树不存在或未被访问就进入p的右子树,否则访问p的数据。
- 只要p!=NULL或者stack没有空,就回到步骤2,循环。
void post_order(BTNode* bt) {
stack<BTNode*>sta;
BTNode* p = bt, * pre = NULL;
while (p || !sta.empty()) {
if (p) {
sta.push(p);
}
else {
p = sta.top();
if (p->rchild != NULL && p->rchild != pre) {
//p的右儿子非空,且p的右儿子不能是刚刚访问过的
p = p->rchild;
}
else {
sta.pop();
cout << p->data << endl;
pre = p;
p = NULL;//p取空,方便下一轮循环取栈顶元素
}
}
}
}
四、根据访问顺序倒推二叉树
- 只需要(前序,中序)(中序,后序)的访问顺序即可。
- 前序遍历的第一个元素就是树根
- 后续遍历的最后一个元素是树根
- 中序遍历树根在中间