二叉树的三序遍历
前言
今天复习王道数据结构的时候注意到二叉树的遍历需要掌握迭代的方式,故去力扣刷了一下发现官方解法与民间解法都不太令我满意,不是难以理解就是代码统一度不够高,代码不够清爽。故给出我的示例代码和理解。
方法主要有两种方式
-
递归
-
迭代
要明白遍历的本质,本质并不是数字输出的顺序,遍历的本质而是以什么样的顺序去访问节点。
递归方式实现遍历
首先就是递归,递归其实感觉没啥好讲的,就是直接暴力递归,遵循树遍历的规则就好了,如果不会建议直接立即推。
前序遍历
根左右
void inorder_traversal(TreeNode* root) {
//递归的结束条件
if(root == NULL) return;
//访问根节点
v.push_back(root->val);
//左递归
inorder_traversal(root->left);
//右递归
inorder_traversal(root->right);
}
中序遍历
左根右
void inorder_traversal(TreeNode* root) {
//递归的结束条件
if(root == NULL) return;
//左递归
inorder_traversal(root->left);
//访问根节点
v.push_back(root->val);
//右递归
inorder_traversal(root->right);
}
后序遍历
左右根
void inorder_traversal(TreeNode* root) {
//递归的结束条件
if(root == NULL) return;
//左递归
inorder_traversal(root->left);
//右递归
inorder_traversal(root->right);
//访问根节点
v.push_back(root->val);
}
迭代方式实现遍历
迭代其实和递归方式是一样的只不过是由程序员手动去维护递归所需的方法栈
前序遍历
vector<int> preorderTraversal(TreeNode* root) {
//接收遍历结果的数组
vector<int> v;
//栈
stack<TreeNode*> stk;
//前面的root!=NULL其实就是排除空树的干扰并且把第一个根节点放入栈当中,这样写可以少写两行代码
//后面栈不为空说明遍历未结束
while(root || !stk.empty()) {
//如果是个非空结点就访问他并放入栈当中并左递归
if(root) {
//入栈
stk.push(root);
//访问当前节点
v.push_back(root->val);
//相当于左递归
root = root->left;
}
//如果是个空结点就说明左递归到头了得右递归
else {
//拿到当前结点
root = stk.top();
//出栈
stk.pop();
//相当于右递归
root = root ->right;
}
}
return v;
}
中序遍历
和先序遍历相似只不过将遍历操作放到左递归之后右递归之前
vector<int> inorderTraversal(TreeNode* root) {
//接收遍历结果的数组
vector<int> v;
//栈
stack<TreeNode*> stk;
//前面的root!=NULL其实就是排除空树的干扰并且把第一个根节点放入栈当中,这样写可以少写两行代码
//后面栈不为空说明遍历未结束
while(root || !stk.empty()) {
//如果是个非空结点就访问他并放入栈当中并左递归
if(root) {
//入栈
stk.push(root);
//相当于左递归
root = root->left;
}
//如果是个空结点就说明左递归到头了得右递归
else {
//拿到当前结点
root = stk.top();
//访问当前节点
v.push_back(root->val);
//出栈
stk.pop();
//相当于右递归
root = root ->right;
}
}
return v;
}
后序遍历
后序遍历和先序与中序遍历略有不同,你不能简单的把访问根结点操作放到右递归后面,因为要保证其左右子树均被访问过才能访问该节点其实就是说当我访问这个结点时以这个结点为根结点的子树必须全部访问完了。
这里有个比较讨巧的方法:后序遍历为左右根,那么我可以遍历为根右左然后进行一次反转就是最终答案。
vector<int> postorderTraversal(TreeNode* root) {
//接收遍历结果的数组
vector<int> v;
//栈
stack<TreeNode*> stk;
//前面的root!=NULL其实就是排除空树的干扰并且把第一个根节点放入栈当中,这样写可以少写两行代码
//后面栈不为空说明遍历未结束
while(root || !stk.empty()) {
//如果是个非空结点就访问他并放入栈当中并右递归
if(root) {
//入栈
stk.push(root);
//访问当前节点
v.push_back(root->val);
//相当于右递归
root = root->右;
}
//如果是个空结点就说明右递归到头了得左递归
else {
//拿到当前结点
root = stk.top();
//出栈
stk.pop();
//相当于左递归
root = root ->right;
}
}
reverse(v.begin(),v.end());
return v;
}
上述代码用一个比较巧妙的方式避免了直接实现后序遍历,曲线救国。但我们思考一个问题遍历的本质到底是什么?难道只是简单的输出数据通过案例作对题目吗?显然不是的。遍历的本质并不是数字输出的顺序,而是以什么样的顺序去访问节点。故下面的操作才是正解。
后序遍历的本质其实就是说当我访问这个结点时以这个结点为根结点的子树必须全部访问完了,因此只需在上述代码中添加这个判断即可
vector<int> postorderTraversal(TreeNode* root) {
//接收遍历结果的数组
vector<int> v;
//栈
stack<TreeNode*> stk;
//用来记录上一个访问结点,用来判断是否重复访问
TreeNode* recent = NULL;
//前面的root!=NULL其实就是排除空树的干扰并且把第一个根节点放入栈当中,这样写可以少写两行代码
//后面栈不为空说明遍历未结束
while(root || !stk.empty()) {
//如果是个非空结点就访问他并放入栈当中并左递归
if(root) {
//入栈
stk.push(root);
//相当于左递归
root = root->left;
}
//如果是个空结点就说明左递归到头了得右递归
else {
root = stk.top();
//如果右节点存在且未被访问过
if(root->right && root->right != recent) {
root = root->right;
}
//如果右结点存在且已经被访问过
else {
v.push_back(root->val);
stk.pop();
recent = root;
//每次栈访问完一个结点相当于访问完该结点为子树的全部结点,故需要将其重置为NULL
root = NULL;
}
}
}
return v;
}
1833

被折叠的 条评论
为什么被折叠?



