前言
提起二叉树,刚刚结束的大二上学期那可真把我折磨死了,当时连c都没摸清楚的我,至今还记得实验课上老师要求我们写出二叉树的先中后序遍历时我尴尬的表情QAQ,那时真是的太难了,连一个for循环都要反复去理解才看得懂。趁寒假比较闲,笔者又去学了一遍二叉树,顺便写个博客,记录一下自我的提升。那么,我们开始吧~~
结构体定义:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {
}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {
}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {
}
};
创建二叉搜索树(BST)
TreeNode* BST::CreBST(TreeNode* root, vector<int>& vec) {
root = new TreeNode(vec[0]);//为根新建一个节点
int len = vec.size();
for (int i = 1; i < len; i++)//遍历后续节点
{
TreeNode* node = root;//记录住根节点,用node寻找插入的位置
TreeNode* newNode = new TreeNode(vec[i]);//为新节点申请空间并将值赋给它的val
while (node) {
if ((newNode->val) < (node->val)) {
if (node->left == NULL) {
//该节点的左孩子为空则插入
node->left = newNode;//连接起来
break;//本次循环结束
}
node = node->left;
}
else {
if (node->right == NULL) {
//该节点的右孩子为空则插入
node->right = newNode;
break;
}
node = node->right;
}
}
}
return root;
}
层次遍历
使用一个队列来对孩子节点进行存储,因为队列拥有先进先出(FIFO)的特点,在遍历到一个节点时都是将其子节点依次放入队列中,那么等遍历到这些子节点时也是一连串地,形成了我们需要的层次感
void LevelTraversal(TreeNode* root)
{
queue<TreeNode*> que;
if (root)
que.push(root);
while (!que.empty()) {
TreeNode* node = que.front();
que.pop();
if (node) {
cout << node->val << " ";
if (node->left)
que.push(node->left);
if (node->right)
que.push(node->right);
}
}
}
先序递归
class Solution{
public:
void traverse(TreeNode* root, vector<int>& vec) {
if (root == nullptr)
return;
vec.push_back(root->val);
traverse(root->left, vec);
traverse(root->right, vec);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> vec;
traverse(root, vec);
return vec;
}
};
中序递归
class Solution {
public:
void Recur_inorderTraversal(TreeNode* root,vector<int>& vec)
{
if(root==nullptr)
return;
Recur_inorderTraversal(root->left,vec);
vec.push_back(root->val);
Recur_inorderTraversal(root->right,vec);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> vec;
Recur_inorderTraversal(root,vec);
return vec;
}
};
后序递归
class Solution {
public:
void RecurPosTraverse(TreeNode* root, vector<int>& vec)
{
if (root == nullptr)
return;
RecurPosTraverse(root->left, vec);
RecurPosTraverse(root->right, vec);
vec.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> vec;
RecurPosTraverse(root, vec);
return vec;
}
};
递归的处理方式是系统自动将暂时未处理的信息存入内存栈中,最后一步一步完成,因此我们可以借助 栈 自己实现迭代;
需要注意的是栈是 先进后出(FILO) 的规则,所以我们将数据压入栈中的时候需要留心它们的先后顺序,以免处理出错。
先序非递归
vector<int> preorderTraversal(TreeNode* root) {
vector<int> vec;//vec容器装取数据
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
if (node != NULL)
vec.push_back(node->val);
else continue;//结束本次循环,立刻进入下次判断
stk.push(node->right);
stk.push(node->left);
}
return vec;
}
中序非递归
一般求解
中序非递归与先序(前序)的代码风格差异很大,原因在于 先序遍历中根节点一经访问就可以存放于vec容器中,但是中序是LDR,也就是先找左孩子,直到该节点的没有左孩子或者它的左孩子以及被处理掉了,这时才能输出中间节点(根节点);
因此 我们首先需要找到最 “左”的位置,寻找的过程中将这些节点一一压入栈中,直到找到一个没有左孩子的节点,这时候我们需要将该节点的值放于vector 容器中,再去寻找该节点的右孩子;
代码如下:
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> stk;
vector<int> vec;
TreeNode* cur = root;
while (!stk.empty() || cur != NULL) {
if (cur != NULL) {
stk.push(cur);
cur = cur->left;
}
else {
cur = stk.top();//从栈里弹出数据,就是要处理的数据(放入数组vec的数据)
stk.pop();
vec.push_back(cur->val);
cur = cur->right;
}
}
return vec;
}
};
标记法
中序非递归另一种写法:
无法同时解决访问节点(遍历节点)和处理节点(将元素放进结果集)不一致的情况。
那我们就将访问的节点放入栈中,把要处理的节点也放入栈中但是要做标记。
如何标记呢, 就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。 这种方法也可以叫做标记法。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> vec;
stack<TreeNode*> stk;//创建栈
if (root != nullptr)
stk.push(root);
while (!stk.empty()) {
TreeNode* node = stk.top();
if (node != nullptr) {
stk.pop();//弹出该节点
if (node->right) stk.push(node->right);
stk.push(node);
stk.push(NULL);//在根节点上放一NULL标记,表示访问过该节点但未处理过
if (node->left) stk.push(node->left);
}
else {
//遇到空节点,先弹出空节点,再处理根节点
stk.pop();//弹出空节点
node = stk.top();
stk.pop();
vec.push_back(node->val);
}
}
return vec;
}
};
后序非递归
先序逆向思维求解
后序遍历(LRD)也就是先找左节点,再找最右节点,最后才输出中间节点,我们可以将其倒过来变为 DRL,这就与先序遍历非常相似,最终我们只需将数组倒置,得到的内容就是后序遍历;
代码如下:
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> stk;
stk.push(root);
vector<int> vec;
while (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
if (node != nullptr) {
vec.push_back(node->val);
}
else continue;
stk.push(node->right);
stk.push(node->left);
}
reverse(vec.begin(),vec.end());//翻转完之后就是先序遍历
return vec;
}
};
栈求解
在后序遍历中,根节点总是在处理完左右孩子(LRD)才输出,我们额外借助一个栈来构建一个后序输出序列,
栈stk1负责收集子节点,栈stk2负责收集最终输出序列;注意:存放到stk1中的节点顺序为先左孩子,再右孩子,这样在栈stk1中进行输出时的顺序就是 先右孩子,再左孩子,栈stk2是收集从stk1中弹出的所有节点,这样以来栈stk2收集到的节点顺序就是我们需要最终输出的后序序列。
TreeNode* posOrderedTraversal(TreeNode* root)
{
stack<TreeNode*> stk1;
stack<TreeNode*> stk2;
if (root)
stk1.push(root);
while (!stk1.empty()) {
TreeNode* cur = stk1.top();
stk2.push(cur);
stk1.pop();
if (cur->left)
stk1.push(cur->left);
if (cur->right)
stk1.push(cur->right);
}
while (!stk2.empty()) {
cout << stk2.top() << " ";
stk2.pop();
}
return root;
}
完整代码
头文件
#include <iostream>
#include <vector>
#include <stack>
#include<queue>
using namespace std;
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
TreeNode() :val(0), left(nullptr), right(nullptr) {
}
TreeNode(int x) :val(x), left(nullptr), right(nullptr) {
}
TreeNode(int x, TreeNode* left, TreeNode* right) :val(x), left(left), right(right) {
}
};
class BST {
public:
TreeNode* root;
TreeNode* getRoot();//获取根
TreeNode* CreBST(TreeNode* root, vector<int>& vec);//创建BST树
void LevelTraversal(TreeNode* root);//层次遍历
void NonRecur_preOrderTraversal(TreeNode* root);//先序非递归遍历
void NonRecur_inOrderTraversal(TreeNode* root);//中序非递归遍历
vector<int> NonRecur_posOrderTraversal(TreeNode* root);//后序非递归遍历
void Recur_preOrderTraversal(TreeNode* root);//先序递归遍历
void Recur_inOrderTraversal(TreeNode* root);//中序递归遍历
void Recur_posOrderTraversal(TreeNode* root);//后序递归遍历
void printVector(vector<int> & vec);//打印vector<int>
void printVVector(vector<vector<int>>& vec);//打印vector<vector<int>>
};
源文件(函数体实现)
#include"BTree.h"
using namespace std;
TreeNode* BST::getRoot() {
return root;
}
TreeNode* BST::CreBST(TreeNode* root, vector<int>& vec) {
root = new TreeNode(vec[0]);//为根新建一个节点
int len = vec.size();
for (int i = 1; i < len; i++)//遍历后续节点
{
TreeNode* node = root;//记录住根节点,用node寻找插入的位置
TreeNode* newNode = new TreeNode(vec[i]);//为新节点申请空间并将值赋给它的val
while (node) {
if ((newNode->val) < (node->val)) {
if (node->left == NULL) {
//该节点的左孩子为空则插入
node->left = newNode;//连接起来
break;//本次循环结束
}
node = node->left;
}
else {
if (node->right == NULL) {
//该节点的右孩子为空则插入
node->right = newNode;
break;
}
node = node->right;
}
}
}
return root;
}
void BST::NonRecur_preOrderTraversal(TreeNode* root) {
stack<TreeNode*> stk;
if (root)//根不为空才放入栈中
stk.push(root);
while (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
if (node) {
cout << node->val << " ";
if (node->right)
stk.push(node->right);
if (node->left)
stk.push(node->left);
}
}
}
void BST::NonRecur_inOrderTraversal(TreeNode* root) {
stack<TreeNode*> stk;//定义一个存储节点地址的栈
if (root)
stk.push(root);
vector<int> vec;
while (!stk.empty()) {
TreeNode* node = stk.top();
if (node != NULL) {
stk.pop();
if (node->right)
stk.push(node->right);
stk.push(node);
stk.push(NULL);
if (node->left)
stk.push(node->left);
}
else {
//该节点上面为空,说明需要处理这个节点
stk.pop();
TreeNode* node = stk.top();
stk.pop();
cout << node->val << " ";
}
}
}
vector<int> BST::NonRecur_posOrderTraversal(TreeNode* root)
{
stack<TreeNode*> stk;
if (root)
stk.push(root);
vector<int> vec;
while (!stk.empty()) {
TreeNode* node = stk.top();
stk.pop();
if (node) {
vec.push_back(node->val);
//先放入左孩子,再放入右孩子,就能形成 DRL 的输出形式
if (node->left)
stk.push(node->left);
if (node->right)
stk.push(node->right);
}
}
reverse(vec.begin(), vec.end());
return vec;
}
void BST::Recur_preOrderTraversal(TreeNode* root)
{
if (root == nullptr)
return;
cout << root->val << " ";
Recur_preOrderTraversal(root->left);
Recur_preOrderTraversal(root->right);
}
void BST::Recur_inOrderTraversal(TreeNode* root)
{
if (root == nullptr)
return;
Recur_inOrderTraversal(root->left);
cout << root->val << " ";
Recur_inOrderTraversal(root->right);
}
void BST::Recur_posOrderTraversal(TreeNode* root)
{
if (root == nullptr)
return;
Recur_posOrderTraversal(root->left);
Recur_posOrderTraversal(root->right);
cout << root->val << " ";
}
void BST::LevelTraversal(TreeNode* root)
{
queue<TreeNode*> que;
if (root)
que.push(root);
TreeNode* last = root;
TreeNode* nlast = nullptr;
while (!que.empty()) {
TreeNode* node = que.front();
que.pop();
if (node) {
cout << node->val << " ";
if (node->left)
que.push(node->left);
if (node->right)
que.push(node->right);
}
}
}
void BST::printVector(vector<int>& vec) {
for (auto val : vec) {
cout << val << " ";
}
cout << endl;
}
void BST::printVVector(vector<vector<int>>& vec) {
for (int i = 0; i < vec.size(); i++) {
for (int j = 0; j < vec[0].size(); j++) {
cout << vec[i][j] << " ";
}
cout << endl;
}
}
源文件(函数调用)
#include "BTree.h"
using namespace std;
int main(int argc, char* argv[])
{
BST p;
TreeNode* root;
root=p.getRoot();
vector<int> vec = {
2,4,3,1,6,9,8,7,5 };
root =p.CreBST(root, vec);
cout << "层次遍历:" << endl;
p.LevelTraversal(root);
cout << endl << endl<< "先序非递归遍历:" << endl;
p.NonRecur_preOrderTraversal(root);
cout << endl<<"中序非递归遍历:" << endl;
p.NonRecur_inOrderTraversal(root);
cout << endl << "后序非递归遍历:" << endl;
vector<int> res;
res=p.NonRecur_posOrderTraversal(root);
p.printVector(res);
cout << endl << "先序递归遍历:" << endl;
p.Recur_preOrderTraversal(root);
cout << endl << "中序递归遍历:" << endl;
p.Recur_inOrderTraversal(root);
cout << endl << "后序递归遍历:" << endl;
p.Recur_posOrderTraversal(root);
cout << endl;
return 0;
}
运行效果图
总结
此时我们用迭代法写出了二叉树的前后中序遍历,大家可以看出前序和中序是完全两种代码风格,并不想递归写法那样代码稍做调整,就可以实现前后中序。
「这是因为前序遍历中访问节点(遍历节点)和处理节点(将元素放进vec数组中)可以同步处理,但是中序就无法做到同步!」