二叉树遍历总结:DFS+BFS【C++】
二叉树遍历总结(C++实现)
二叉树遍历分类
- DFS(depth-first-search) 深度优先搜索
- 前序遍历(根-左-右)
- 中序遍历(左-根-右)
- 后序遍历(左-右-根)
- BFS(breadth-first-search) 广度优先搜索
- 层序遍历(从上到下,从左到右访问)
DFS 深度优先搜索:前序、中序、后序遍历
- 深搜的三种遍历算法,实际上通过的是同一条路径,其不同之处仅仅是对结点执行操作的时机不同
- 相同:在DFS过程中,每个结点都会作为其子树的根结点,被函数经过三次(根-左-根-右-根)
- 不同:前序遍历在第一次经过时对结点执行操作,中序遍历在第二次执行操作,后序遍历在第三次进行操作
如上图所示,左右子树可为空。圈叉表示第一次访问,星标表示第二次,三角形表示第三次。
实现方式
前序、中序和后序均有递归和非递归两种实现方式:
- 递归版本中,三种遍历的实现大同小异
- 非递归版本中,前中序相似,后序较为复杂,相同点在于三种遍历的非递归方式都会用到栈
结点结构:
struct TreeNode {
int val; //结点值
TreeNode *left; //左孩子
TreeNode *right; //右孩子
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
前序遍历的实现
递归版本
void preOrderRecur(TreeNode *root) {
if (!root) return;
// do something e.g. print
cout << root->val << " ";
preOrderRecur(root->left);
preOrderRecur(root->right);
}
非递归版本
最左结点:一直往左,直到碰到无左孩子的结点
边界判断:需要当前结点r为空且栈为空,循环才会结束。
- 栈空但r不为空:当前根结点的左子树处理完毕,右子树没有处理
- r空但栈不空:正在处理某一个子树的过程中,且其根结点无右儿子
void preOrderNocur(TreeNode *root) {
if (!root) return;
// 初始化栈
stack<TreeNode*> s;
TreeNode *r = root;
// 循环开始
while (r || !s.empty()) {
// 一次循环处理一个最小子树
// 从根开始一直往左入栈,直到最左结点入栈结束
while (r) {
s.push(r);
// 这里是第一次经过结点,前序遍历在这里进行操作
cout << r->val << " ";
r = r->left;
}
// 处理栈内结点,访问其右子树
if (!s.empty()) {
r = s.top(); //第二次经过结点,中序遍历在这里进行操作
s.pop(); //这里结点出栈了,所以没有第三次经过结点,无法用这个方法实现后序遍历
r = r->right; //转到右子树
}
}
}
中序遍历的实现
递归版本
void inOrderRecur(TreeNode *root) {
if (!root) return;
inOrderRecur(root->left);
// do something
cout << root->val << " ";
inOrderRecur(root->right);
}
非递归版本(与前序遍历类似)
void inOrderNocur(TreeNode *root) {
if (!root) return;
// 初始化
stack<TreeNode*> s;
TreeNode* r = root;
// 循环开始
while (r || !s.empty()) {
// 一直往左全部入栈
while (r) {
s.push(r);
r = r->left;
}
// 从最左儿子开始由左根右的顺序处理
if (!s.empty()) {
r = s.top();
// 中序遍历在这里do something
cout << r->val << " ";
s.pop();
r = r->right;
}
}
}
后序遍历的实现
递归版本
void postOrderRecur(TreeNode *root) {
if (!root) return;
postOrderRecur(root->left);
postOrderRecur(root->right);
// do something
cout << root->val << " ";
}
非递归版本
思路:
- 前序遍历的顺序是根左右,反过来就是右左根
- 因此想到,将前序遍历变为根右左,反过来就是左右根,即后序遍历
- 实现反向输出的方法就是增加一个栈(p)
void postOrderNocur(TreeNode *root) {
if (!root) return;
stack<TreeNode*> s; // 第一个栈,保证前序遍历(根-右-左)的执行
stack<TreeNode*> p; // 第二个栈,实现反向输出左右根
TreeNode *r = root;
while (r || !s.empty()) {
while (r) {
s.push(r);
p.push(r); // p与s同步push
r = r->right;
}
if (!s.empty()) {
r = s.top();
s.pop(); // p不需要pop;s执行pop的原因是需要将前序遍历执行下去
r = r->left;
}
}
while (!p.empty()) {
cout << p.top()->val << " ";
p.pop();
}
}
BFS 广度优先搜索:层序遍历
层序遍历
思想:使用队列实现
- 根入队;
- 循环开始:队头出队,其左右儿子按序入队
void levelOrder(TreeNode *root) {
if (!root) return;
// 初始化队列
queue<TreeNode*> q;
TreeNode *r = root;
q.push(r);
while (!q.empty()) {
r = q.front();
q.pop();
// do something
cout << r->val << " ";
if (r->left) q.push(r->left);
if (r->right) q.push(r->right);
}
}
将队列改为堆栈,并且将左右儿子入队顺序交换,可以实现另一个非递归版本的前序遍历:
void preOrderStack(TreeNode* root) {
if (!root) return;
// 初始化
stack<TreeNode*> s;
TreeNode* r = root;
s.push(r);
while (!s.empty()) {
r = s.top();
s.pop();
// do something
cout << r->val << " ";
if (r->right) s.push(r->right);
if (r->left) s.push(r->left);
}
}