二叉树的前中后序遍历(递归与非递归)以及层序遍历
前言
关于二叉树的结构:二叉树每个节点一般都必有三个元素,左孩子的指针,右孩子的指针,节点内存储的数据
template <class T>
struct TreeNode
{
TreeNode(const T& x)
:_left(nullptr)
, _right(nullptr)
, _val(x)
{}
TreeNode* _left;
TreeNode* _right;
T _val;
};
所谓二叉树的遍历,一般都是先左后右,也就是先走左子树后走右子树,只不过树中节点存储的数据访问的时机不同产生了三种不同的遍历方式(这里简称访问的数据为val)
- val—>左子树—>右子树——前序遍历
- 左子树—>val—>右子树——中序遍历
- 左子树—>右子树—>val——后序遍历
一、递归实现
1. 前序遍历
void PreOrder(TreeNode* root)
{
//如果root为空就返回
if (root == nullptr)
{
return;
}
//访问_val值
cout << root->_val << " ";
//递归走左子树
PreOrder(root->_left);
//递归走右子树
PreOrder(root->_right);
}
2. 中序遍历
void InOrder(TreeNode* root)
{
//如果root为空就返回
if (root == nullptr)
{
return;
}
//递归走左子树
PreOrder(root->_left);
//访问_val值
cout << root->_val << " ";
//递归走右子树
PreOrder(root->_right);
}
3.后序遍历
void PostOrder(TreeNode* root)
{
//如果root为空就返回
if (root == nullptr)
{
return;
}
//递归走左子树
PreOrder(root->_left);
//递归走右子树
PreOrder(root->_right);
//访问_val值
cout << root->_val << " ";
}
二、非递归实现
相比递归实现,非递归会稍微复杂些,基本原理就是利用栈先进后出的性质保存节点来模拟递归的过程,以达到遍历二叉树的目的
注:以下代码默认_val是int类型的,并且将遍历访问的数据先存入vector中不直接打印
1. 前序遍历
void PreOrder(TreeNode* root)
{
TreeNode* cur = root;
vector<int> v;
stack<TreeNode*> st;
while (cur || !st.empty())
{
while (cur)
{
//访问该节点数据
v.push_back(cur->_val);
//将节点入栈保存
st.push(cur);
//继续访问左子树
cur = cur->_left;
}
//走到此处,栈内节点的左子树都已访问
//如果栈内不为空,则取栈顶元素,并访问其右子树
//实际上此处栈不可能为空,此处应是多余判断,可以自己想下是为什么
if (!st.empty())
{
//取栈顶元素
cur = st.top();
//删除栈顶元素
st.pop();
//转化为子问题,将cur->_right当作一颗新树看待 重复以上操作
cur = cur->_right;
}
}
//依次打印前序遍历的数据
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
2. 中序遍历
void InOrder(TreeNode* root)
{
TreeNode* cur = root;
vector<int> v;
stack<TreeNode*> st;
while (cur || !st.empty())
{
while (cur)
{
//将节点入栈保存
st.push(cur);
//继续访问左子树
cur = cur->_left;
}
//走到此处,栈内节点的左子树都已访问
//如果栈内不为空,取栈顶元素,访问该节点数据,并访问其右子树
//实际上此处栈不可能为空,此处应是多余判断,可以自己想下是为什么
if (!st.empty())
{
//取栈顶元素
cur = st.top();
//删除栈顶元素
st.pop();
//访问该节点数据
v.push_back(cur->_val);
//转化为子问题,将cur->_right当作一颗新树看待 重复以上操作
cur = cur->_right;
}
}
//依次打印中序遍历的数据
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
3. 后序遍历
后序遍历比前序和中序稍微复杂一些,因为访问顺序的不同,需要额外判断当前节点是否是从右子树返回的节点
通俗来讲就是只有当该节点的左子树和右子树都走完之后,才能访问该节点存储的_val,也就是需要判断是不是第二次回到该节点,因为该节点走左子树回来会访问一次该节点,此时是第一次,根据后序遍历规则,需要先走右子树才能访问该节点的_val,所以只有当第二次回到该节点时才能访问
void PostOrder(TreeNode* root)
{
TreeNode* cur = root;
TreeNode* front = nullptr;
vector<int> v;
stack<TreeNode*> st;
while (cur || !st.empty())
{
while (cur)
{
//将节点入栈保存
st.push(cur);
//继续访问左子树
cur = cur->_left;
}
//走到此处,栈内节点的左子树都已访问
//取栈顶元素,访问该节点数据,并访问其右子树
//取栈顶元素
cur = st.top();
//此处只有两种情况能访问节点数据,并在栈内删除该节点
//1. 如果该节点的右为空则直接访问数据
//2. 上一个访问的节点是该节点的右子树,说明此处回到了该节点了两次,是走过右子树回来的
if (cur->_right == nullptr || front == cur->_right)
{
//删除栈顶元素
st.pop();
//访问该节点数据
v.push_back(cur->_val);
//保存该节点成为front
front = cur;
//将cur置空,外层循环判断栈是否为空,不为空则循环操作
cur = nullptr;
}
//当该节点右不为空,且只访问了一次时
else
{
//转化为子问题,将cur->_right当作一颗新树看待 重复以上操作
cur = cur->_right;
}
}
//依次打印后序遍历的数据
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
三、层序遍历
层序遍历顾名思义就是一层一层的访问二叉树,具体思路也很简单,一层一层的访问节点,边访问数据边用队列存下一层的节点
注:以下代码默认_val是int类型的,并且将遍历访问的数据先存入二维数组中不直接打印,二维数组的每一层就代表树中对应的一层存储的数据
void levelOrder(TreeNode* root)
{
queue<TreeNode*> q;
//定义一个二维数组来存整个树的数据
vector<vector<int>> vv;
//定义一个数组来存单层的数据,方便插入二位数组
vector<int> v;
//记录每一层的数据个数
size_t levelnum;
if (root)
{
q.push(root);
levelnum = 1;
}
//队列不为空就继续
while (!q.empty())
{
//借助levelnum一层层完成
while (levelnum--)
{
TreeNode* top = q.front();
q.pop();
v.push_back(top->_val);
if (top->_left)
{
q.push(top->_left);
}
if (top->_right)
{
q.push(top->_right);
}
}
//将这一层数据插入二维数组中
//清除v中的数据
//更新levelnum,新的levelnum的值就是队列内节点的个数
vv.push_back(v);
v.clear();
levelnum = q.size();
}
}