知道了树的遍历的类型以及概念,接下来我们就要设计算法将其用代码优雅的表述出来。(还不清楚概念的可以看另一篇博客:树的遍历基本概念)
开发语言:C++
编辑器:CLion
项目源码:树的遍历
我们以这棵树为例:
在写代码之前首先定义树的结构体,并且初始化创建一棵树:
//定义树节点的结构体
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) {}
};
//初始化树
TreeNode* generateTree()
{
int a;
cin>>a;
auto *root = new TreeNode();
if(a == -1) //输入-1代表空节点,返回空值
return nullptr;
else{
root->val = a;
root->left = generateTree(); //构建左子树
root->right = generateTree(); //构建右子树
}
return root;
}
//前序遍历 --递归版
void pre(TreeNode* root)
{
if(root == nullptr) //如果节点不存在则返回
return;
else
{
cout<<root->val; //输出当前根节点的值
pre(root->left); //递归遍历左子树
pre(root->right); //递归遍历右子树
}
}
2. 中序遍历:对于中序遍历,我们在遍历完所有的左节点后再遍历根节点,打印根节点信息,然后右节点。
//中序遍历 --递归版
void medium(TreeNode* root)
{
if(root == nullptr) //如果为空值就返回
return;
else
{
medium(root->left); //递归遍历左子树
cout<<root->val; //打印当前节点值
medium(root->right); //递归遍历右子树
}
}
3. 后序遍历:后序遍历先递归遍历左子树,再递归遍历右子树,然后打印根节点信息。
//后序遍历 --递归版
void last(TreeNode* root)
{
if(root == nullptr) //如果为空值就返回
return;
else
{
last(root->left); //遍历左子树
last(root->right); //遍历右子树
cout<<root->val; //打印当前节点值
}
}
//前序遍历 --非递归
void pre1(TreeNode* root)
{
stack<TreeNode*>ans; //保存需要遍历的节点信息
while(root != nullptr || !ans.empty())
{
while(root != nullptr)
{
cout<<root->val; //打印根节点信息
ans.push(root);
root = root->left; //左节点入栈
}
if(!ans.empty())
{
root = ans.top();
ans.pop(); //弹出栈顶
root = root->right; //遍历右节点
}
}
}
2. 中序遍历
//中序遍历 --非递归
/*
* 主要思想:从根节点开始入栈,一直向左遍历,直到左节点为空,此时弹出最左边的节点,同时将当前节点指向最左边节点
* 的右节点,重新刚刚的操作,这样就能保证 中序。例如这个例子:
* 1、 0入栈,1入栈,3入栈,此时当前节点左节点为空,栈内元素为【310】;
* 2、 节点指向3,打印3,弹出3,节点指向3的右节点,因为右节点为空,所以继续弹出元素1;
* 3、 节点指向1的右节点4,对于4.重复1操作。
* 4、 重复以上操作直到当前节点为空并且栈也为空,结束循环。
*/
void medium1(TreeNode* root)
{
stack<TreeNode*>ans; //用栈来保存所遍历过的节点信息
while(root!= nullptr || !ans.empty()) //根节点和栈不为空就进入循环
{
while (root != nullptr)
{
ans.push(root);
root = root->left; //如果左节点不为空就一直向左遍历,直到为空
}
if(!ans.empty())
{
root = ans.top(); //当前节点设置为栈顶
cout<<root->val; //打印
ans.pop(); //弹出栈顶
root = root->right; //当前节点指向右节点
}
}
}
3. 后序遍历
//后序遍历非递归版
/*
* 后序遍历一定要保证根节点在左右节点之后遍历,所以遍历根节点时,我们要判断它的左右节点是否为空,或者左右节点是否被遍历过。同时我们压栈时为了保证
* 先遍历的是左节点,我们要先将右节点入栈,然后将左节点入栈。
*/
void last1(TreeNode* root)
{
stack<TreeNode*>ans; //用栈来保存所遍历过的节点信息
ans.push(root);
TreeNode* pre = nullptr; //保存前一个遍历的节点的值
while(!ans.empty())
{
TreeNode* temp = ans.top();
if((temp->right == nullptr && temp->left == nullptr) || (pre != nullptr && (pre == temp->left || pre == temp->right)))
{
ans.pop();
cout<<temp->val;
pre = temp;
}
else
{
if(temp->right != nullptr) //先将右节点压栈,再将左节点压栈
ans.push(temp->right);
if(temp->left != nullptr)
ans.push(temp->left);
}
}
}
*以上所有的方法由于使用了额外的栈开销(递归方法也隐式的调用了,因为要保存你处理过但是还没有处理完的函数的信息),同时需要遍历每一个元素,所以时间复杂度和空间复杂度都是O(N)
。还有另一种使用常数空间遍历的方法–Morris遍历,由于比较复杂,我会用另一篇文章来详细写。
//广度优先遍历
void bfs(TreeNode* root)
{
queue<TreeNode*>ans; //队列保存每一层的遍历信息
ans.push(root); //初始状态将根节点入队列
while (!ans.empty())
{
TreeNode* front = ans.front(); //依次遍历每一层的队列
cout<<front->val;
ans.pop();
if(front->left != nullptr) //依次入栈每一层对应的下一层的队列,先左后右
ans.push(front->left);
if(front->right != nullptr)
ans.push(front->right);
}
}
//深度优先遍历
void dfs(TreeNode* root)
{
stack<TreeNode*>ans;
ans.push(root); //根节点入队列
while (!ans.empty())
{
TreeNode* temp = ans.top();
cout<<temp->val; //打印根节点信息
ans.pop(); //弹出根节点
if(temp->right != nullptr) //由于栈后进先出的特点,我们需要先保存右节点,再保存左节点
ans.push(temp->right);
if(temp->left != nullptr)
ans.push(temp->left);
}
}
/*
* 树的遍历操作
* 2020/10/27
* CSU-XZY
* */
#include
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) {}
};
//初始化树
TreeNode* generateTree()
{
int a;
cin>>a;
auto *root = new TreeNode();
if(a == -1) //输入-1代表空节点,返回空值
return nullptr;
else{
root->val = a;
root->left = generateTree(); //构建左子树
root->right = generateTree(); //构建右子树
}
return root;
}
//前序遍历 --递归版
void pre(TreeNode* root)
{
if(root == nullptr) //如果节点不存在则返回
return;
else
{
cout<<root->val; //输出当前根节点的值
pre(root->left); //递归遍历左子树
pre(root->right); //递归遍历右子树
}
}
//中序遍历 --递归版
void medium(TreeNode* root)
{
if(root == nullptr) //如果为空值就返回
return;
else
{
medium(root->left); //递归遍历左子树
cout<<root->val; //打印当前节点值
medium(root->right); //递归遍历右子树
}
}
//后序遍历 --递归版
void last(TreeNode* root)
{
if(root == nullptr) //如果为空值就返回
return;
else
{
last(root->left); //遍历左子树
last(root->right); //遍历右子树
cout<<root->val; //打印当前节点值
}
}
//前序遍历 --非递归
void pre1(TreeNode* root)
{
stack<TreeNode*>ans; //保存需要遍历的节点信息
while(root != nullptr || !ans.empty())
{
while(root != nullptr)
{
cout<<root->val; //打印根节点信息
ans.push(root);
root = root->left; //左节点入栈
}
if(!ans.empty())
{
root = ans.top();
ans.pop(); //弹出栈顶
root = root->right; //遍历右节点
}
}
}
//中序遍历 --非递归
/*
* 主要思想:从根节点开始入栈,一直向左遍历,直到左节点为空,此时弹出最左边的节点,同时将当前节点指向最左边节点
* 的右节点,重新刚刚的操作,这样就能保证 中序。例如这个例子:
* 1、 0入栈,1入栈,3入栈,此时当前节点左节点为空,栈内元素为【310】;
* 2、 节点指向3,打印3,弹出3,节点指向3的右节点,因为右节点为空,所以继续弹出元素1;
* 3、 节点指向1的右节点4,对于4.重复1操作。
* 4、 重复以上操作直到当前节点为空并且栈也为空,结束循环。
*/
void medium1(TreeNode* root)
{
stack<TreeNode*>ans; //用栈来保存所遍历过的节点信息
while(root!= nullptr || !ans.empty()) //根节点和栈不为空就进入循环
{
while (root != nullptr)
{
ans.push(root);
root = root->left; //如果左节点不为空就一直向左遍历,直到为空
}
if(!ans.empty())
{
root = ans.top(); //当前节点设置为栈顶
cout<<root->val; //打印
ans.pop(); //弹出栈顶
root = root->right; //当前节点指向右节点
}
}
}
//后序遍历非递归版
/*
* 后序遍历一定要保证根节点在左右节点之后遍历,所以遍历根节点时,我们要判断它的左右节点是否为空,或者左右节点是否被遍历过。同时我们压栈时为了保证
* 先遍历的是左节点,我们要先将右节点入栈,然后将左节点入栈。
*/
void last1(TreeNode* root)
{
stack<TreeNode*>ans; //用栈来保存所遍历过的节点信息
ans.push(root);
TreeNode* pre = nullptr; //保存前一个遍历的节点的值
while(!ans.empty())
{
TreeNode* temp = ans.top();
if((temp->right == nullptr && temp->left == nullptr) || (pre != nullptr && (pre == temp->left || pre == temp->right)))
{
ans.pop();
cout<<temp->val;
pre = temp;
}
else
{
if(temp->right != nullptr) //先将右节点压栈,再将左节点压栈
ans.push(temp->right);
if(temp->left != nullptr)
ans.push(temp->left);
}
}
}
//广度优先遍历
void bfs(TreeNode* root)
{
queue<TreeNode*>ans; //队列保存每一层的遍历信息
ans.push(root); //初始状态将根节点入栈
while (!ans.empty())
{
TreeNode* front = ans.front(); //依次遍历每一层的队列
cout<<front->val;
ans.pop();
if(front->left != nullptr) //依次入栈每一层对应的下一层的队列,先左后右
ans.push(front->left);
if(front->right != nullptr)
ans.push(front->right);
}
}
//深度优先遍历
void dfs(TreeNode* root)
{
stack<TreeNode*>ans;
ans.push(root); //根节点入栈
while (!ans.empty())
{
TreeNode* temp = ans.top();
cout<<temp->val; //打印根节点信息
ans.pop(); //弹出根节点
if(temp->right != nullptr) //由于栈后进先出的特点,我们需要先保存右节点,再保存左节点
ans.push(temp->right);
if(temp->left != nullptr)
ans.push(temp->left);
}
}
int main()
{
TreeNode* root = generateTree(); //初始化树
cout<<"-----------------递归版本------------------"<<endl;
cout<<"前序遍历为: ";
pre(root);
cout<<endl<<"中序遍历为: ";
medium(root);
cout<<endl<<"后序遍历为: ";
last(root);
cout<<endl<<"----------------非递归版本------------------"<<endl;
cout<<"前序遍历为: ";
pre1(root);
cout<<endl<<"中序遍历为: ";
medium1(root);
cout<<endl<<"后序遍历为: ";
last1(root);
cout<<endl<<"----------------BFS和DFS------------------"<<endl;
cout<<"BFS为: ";
bfs(root);
cout<<endl<<"DFS为: ";
dfs(root);
return 0;
}