目录
1,三种遍历都是先把二叉树的最左结点循环入栈(DFS迭代),以帮助找到返回处理的节点。
每个子树也是先把该右子树中的最左节点循环入栈,以帮助找到返回处理的节点。
先序遍历和中序遍历都是通过一个左子树(全绿的)的最右下叶节点的right(灰NULL)带出接下来的处理结点(左子树的父结点(紫)),表明该处理结点的左子树已经遍历处理完毕。因为不管先序遍历还是中序遍历,一颗子树的结束点是在该子树的最右下叶节点。
2,先序和中序代码结构一致,是处理完父和左或左和父之后再处理右,天然可以通过父节点带出右子树。但是后序遍历是先处理左和右再处理父,其中某次父节点出栈,只用一个栈的话没法确定左右是否处理完毕,因此需要增加一个栈记录状态。
3,后序遍历需要用两个栈,一个栈用于记录返回处理的结点,一个用于记录状态,0表示处理了左子结点,1表示处理了右子结点。
注:
1,二叉树遍历非递归经典解法DFS(栈)、BFS(队列),注意入栈的条件都是当节点非空node != nullptr时入栈,否则从栈中取出节点指针,取值会非法。
2,DFS栈解法比递归的好处:以先序为例,在把当前p的right、left入栈后,在下一轮从栈pop出left前的这段时间,可以额外进行各种处理(如二叉树转为链表时需对p的left和right重置值leetcode114栈解法),因为在额外处理前已经用栈记录了先序遍历的节点顺序。
先序遍历
DFS递归:
注:(递归:从上到下)先父节点,后子节点,是尾递归。
public void preorderTraversal(TreeNode node)
{
if (node == null) {
return;
}
System.out.println(node.value); // 访问当前结点
preorderTraversal(node.left); // 前序遍历其左子树
preorderTraversal(node.right); // 前序遍历其右子树
}
DFS栈解法1:DFS栈通用模板(只适用于先序遍历,因为只有先序遍历是先父后子):CSDN
注:1,因为二叉树状态转换虽然是DAG(有向无环图)但不同路径间没有重复(从同一点A出发,只有一条路径到达另一点B),所以左右孩子入栈前不需要(空间换时间的visit)判重。
2,入栈栈顺序是先right孩子,后left孩子,这样出栈是先左后右,满足先序遍历顺序。
class Solution {
public:
vector<int> preorderTraversal(TreeNode *root) {
vector<int> result;
stack<const TreeNode *> s;
if (root != nullptr)
s.push(root);
while (!s.empty()) {
const TreeNode *p = s.top();
s.pop();
result.push_back(p->val);
if (p->right != nullptr) {
s.push(p->right);
}
if (p->left != nullptr) {
s.push(p->left);
}
}
return result;
}
};
DFS栈解法2:用栈stack记住来时的路(来时的路即父节点)
注:本解法的意图强调:用栈stack记住来时的路(即stack中是存的所有的父节点)。
//先序遍历(栈)
void pre_visit(BTree* root)
{
std::stack<BTree*> stack_tree;
BTree* cur_node = root;
while(cur_node != NULL || !stack_tree.empty())
{
if(cur_node != NULL) { // 当前节点非空:记录到结果集,并入栈作为子节点的灯塔
std::cout << cur_node->value.c_str() << "\t"; // 记录到结果集
stack_tree.push(cur_node); // 当前节点入栈
cur_node = cur_node->leftChild; // 处理左
} else {
cur_node = stack_tree.top(); // 父节点出栈
stack_tree.pop(); //栈顶元素出栈
cur_node = cur_node->rightChild; // 处理右
}
}
}
中序遍历
DFS递归
注:先孩子,又父亲,再孩子。所以是非尾递归。
public void inorderTraversal(TreeNode node)
{
if (node == null) {
return;
}
inorderTraversal(node.left); // 中序遍历结点的左子树
System.out.println(node); // 访问当前结点
inorderTraversal(node.right); // 中序遍历结点的右子树
}
DFS栈:记住来时的路
//中序遍历(非递归)
void mid_visit(BTree* root)
{
std::stack<BTree*> stack_tree;
BTree* cur_node = root;
while(cur_node != NULL || !stack_tree.empty())
{
if(cur_node != NULL) {
stack_tree.push(cur_node); //当前节点入栈
cur_node = cur_node->leftChild; //指向左子节点,进行相同处理
} else {
cur_node = stack_tree.top(); //指向栈顶节点
stack_tree.pop(); //栈顶节点出栈
std::cout << cur_node->value.c_str() << "\t"; //处理当前节点的值
cur_node = cur_node->rightChild; //指向右子节点,进行相同处理
}
}
}
后序遍历
DFS递归(非尾递归)
public void postorderTraversal(TreeNode node) {
if (node == null) {
return;
}
postorderTraversal(node.left); // 后序遍历结点的左子树
postorderTraversal(node.right); // 后序遍历结点的右子树
System.out.println(node); // 访问当前结点
}
DFS栈:
思路1:借用先序遍历的"根左右"入结果集。改为"根右左"入一个可以支撑倒序的容器,并反向输出得到"左右根"
思路1-解法1:以"根右左"顺序