二叉树这种数据结构想必大家都已经非常熟悉了,他的独特的三种遍历方式也广为人知,接下来我们就来解析如何用代码实现对二叉树的遍历
首先我们录入一下二叉树结构体和后面四种遍历实现所需要的头文件
#include <iostream>
#include <vector>
#include <stack>
#include <unordered_set>
#include <queue>
using namespace std;
//这里用类的方式构造二叉树节点
class TreeNode {
public:
TreeNode(int num):data(num), left(nullptr), right(nullptr)
{}
int data;
TreeNode* left;
TreeNode* right;
};
层序遍历
层序遍历应该是二叉树遍历中最容易理解的一种 遍历方式,对于该方式的实现需要借助一个队列来实现
void tierOrder(TreeNode* root)
{
//创建一个存储二叉树节点的队列
queue<TreeNode*> NodeQueue;
//首先将根节点放入队列
NodeQueue.push(root);
TreeNode* cur;
//利用队列先进先出的特性用二叉树上层节点引出下层节点并放入队列
while (!NodeQueue.empty()) {
//取队列首节点
cur = NodeQueue.front();
//取出使用后删除
NodeQueue.pop();
cout << cur->data << endl;
//依次引入非空的左节点和右节点
if (cur->left)
NodeQueue.push(cur->left);
if (cur->right)
NodeQueue.push(cur->right);
}
}
前序遍历
递归版本
递归版本就很简单了,利用其遍历特性先输出当前节点再输出左节点最后右节点
void preOrder_1(TreeNode* tmp)
{
//先输出自己的data
cout << tmp->data << endl;
//左右节点不为空时仍执行该函数
if (tmp->left != nullptr) {
preOrder_1(tmp->left);
}
if (tmp->right != nullptr) {
preOrder_1(tmp->right);
}
}
非递归版本
这里的非递归版本在逻辑上可能于递归版本难于理解,但在执行效率方面大大占优
通过一个栈来存储节点,入栈节点的值是输出过的,然后先左孩子后右孩子实现前序遍历
void preOrder_2(TreeNode* root)
{
//建立一个存储二叉树节点的栈
stack<TreeNode*> NodeStack;
TreeNode* tmp = root;
//当前节点不为空或者栈中还存在节点代表着未遍历结束
while (tmp || !NodeStack.empty()) {
//当前节点不为空输出其data然后向左孩子一直执行该语句
while (tmp) {
cout << tmp->data << endl;
NodeStack.push(tmp);
tmp = tmp->left;
}
//此时当前节点已经的到该二叉树部分的最左下方
if (!NodeStack.empty()) {
//取出栈顶节点的左孩子以及自身都已经输出过data
tmp = NodeStack.top();
//向右孩子方向做处理
tmp = tmp->right;
NodeStack.pop();
}
}
}
中序遍历
递归版本
void inOrder_1(TreeNode* tmp)
{
if (tmp->left != nullptr)
inOrder_1(tmp->left);
cout << tmp->data << endl;
if (tmp->right != nullptr)
inOrder_1(tmp->right);
}
非递归版本
仍然使用栈来记录左孩子和自身都已经遍历过的节点,和前序遍历很像
void inOrder_2(TreeNode* root)
{
stack<TreeNode*> NodeStack;
TreeNode *tmp = root;
while (tmp || !NodeStack.empty()) {
while (tmp) {
NodeStack.push(tmp);
tmp = tmp->left;
}
if (!NodeStack.empty()) {
//和前序遍历唯一的区别在于这里是第二次遍历到节点时才输出data
tmp = NodeStack.top();
cout << tmp->data << endl;
tmp = tmp->right;
NodeStack.pop();
}
}
}
后序遍历
递归版本
void postOrder_1(TreeNode* tmp)
{
if (tmp->left != nullptr)
postOrder_1(tmp->left);
if (tmp->right != nullptr)
postOrder_1(tmp->right);
cout << tmp->data << endl;
}
非递归版本
这几张遍历最不好理解的就是后序遍历的非递归版本,这里主要要考虑后序是最后才输出根节点,先输出左节点和右节点,在这个过程中我们的遍历要两次回到跟节点,第一次是从左孩子访问完要去右孩子访问,第二次是右孩子也访问完了,我们需要在第二次访问结束的时候输出该根节点的data
这里提供两个版本,第一个版本使用一个依赖于哈希的unordered_set来存储访问过一次的二叉树节点(不懂set的可以看第二个版本)
void postOrder_2(TreeNode* root)
{
stack<TreeNode*> NodeStack;
TreeNode *tmp = root;
unordered_set<TreeNode*> display;
while (tmp || !NodeStack.empty()) {
while (tmp) {
NodeStack.push(tmp);
tmp = tmp->left;
}
//此时该二叉树部分的左孩子遍历结束(已到最左下方),全部存入站当中
if (!NodeStack.empty()) {
tmp = NodeStack.top();
NodeStack.pop();
//当栈顶元素是第一次被回溯访问
if (display.find(tmp) == display.end()) {
//将他加入到哈希表中
display.insert(tmp);
//重新把该节点送入栈当中
NodeStack.push(tmp);
//开始向右孩子遍历
tmp = tmp->right;
}
//这里代表哈希表中已经存在该节点,意味着该节点是第二次被回溯访问
else {
//输出该节点,tmp置空取下一个栈顶元素
cout << tmp->data << endl;
tmp = nullptr;
}
}
}
}
第二个版本是用一个节点记录上一个被访问的节点,以此达成和哈希表一样的效果
void postOrder_3(TreeNode* root)
{
stack<TreeNode*> NodeStack;
TreeNode* pre = nullptr;
TreeNode* cur;
NodeStack.push(root);
while (!NodeStack.empty()) {
cur = NodeStack.top();
//当该节点的左孩子和右孩子都为空或是上一个访问的节点是左孩子或者右孩子
//说明他已经是第二次回溯访问了,所以输出该节点的数据
if ((cur->right == nullptr && cur->left == nullptr) || \
(pre != nullptr && (pre == cur->left || pre == cur->right)))
{
cout << cur->data << endl;
NodeStack.pop();
pre = cur;
}
else {
if (cur->right)
NodeStack.push(cur->right);
if (cur->left)
NodeStack.push(cur->left);
}
}
}
测试用例
这里我构建一个三次的满二叉树用来测试,从上到下层序比例依次是1-7
int main()
{
vector<TreeNode*> array;
array.push_back(nullptr);
for (int i = 0; i < 7; i++)
{
TreeNode *tmp = new TreeNode(i + 1);
array.push_back(tmp);
}
TreeNode *root = array[1];
for (int i = 1; i < array.size() / 2; i++)
{
array[i]->left = array[2 * i];
array[i]->right = array[2 * i + 1];
}
tierOrder(root);
system("pause");
}