大家好,我是怒码少年。
今天讲用迭代法实现二叉树的深度查询(前中后序遍历),想想大一下的时候我还完全看不懂呢哈哈。
前序遍历
也就是中左右,访问本结点之后再访问本结点的左子树,然后再访问右子树。所以在代码中我们也是这样处理。
使用一个指针node
负责遍历结点的话,如果此时它已经遍历完所有的左子树了,它要怎么开始遍历右子树呢?
答:如果能找到左子树的父结点,我们就能找到右子树了不是吗😎
所以我们在遍历左子树的时候,需要用一个数据结构保存上一个遍历的结点(也就是当前结点的父结点),要访问右子树的时候,就从中取出父结点,然后通过node=node->right
找到右子树。
因为存入顺序与取出顺序相反,所以我们使用栈来实现。
这里我们用vector
保存已经遍历完成的结点。
vector<int> preOrderTraversal(TreeNode* root) {
vector<int> res; // 存储结果的 vector
// 如果根结点为空,直接返回空的 vector
if (root == nullptr) {
return res;
}
stack<TreeNode*> stk; // 用于存储暂时还未遍历右子树的结点
TreeNode* node = root; // 从根结点开始遍历
// 循环条件:栈不为空或者当前结点不为空
while (!stk.empty() || node != nullptr) {
// 先处理左子树
while (node != nullptr) {
res.push_back(node->val); // 将当前结点的值加入结果 vector
stk.push(node); // 将当前结点保存到栈中
node = node->left; // 前往下一个左子结点
}
// 左子树遍历完毕,开始遍历右子树
node = stk.top(); // 取出栈顶元素,即最近访问过的结点
stk.pop(); // 弹出栈顶元素
node = node->right; // 前往右子结点
}
return res; // 返回结果 vector
}
中序遍历
左中右。思路大致和前序遍历的是一样的,建议看到这里想停下来自己想想。
和前序遍历不同的是,中序遍历是先遍历左子树再遍历该左子树的父结点,然后再遍历右子树。所以我们先用一个遍历指针node
找到最左结点,这个过程我们需要用一个数据结构保存该结点的父结点(便于访问父节点和右结点)。
vector<int> OrderTraversal(TreeNode* root) {
vector<int> res;
if (root == nullptr) {
return res;
}
stack<TreeNode*> stk;
TreeNode* node = root;
while (node != nullptr || !stk.empty()) {
while (node != nullptr) {
//保存
stk.push(node);
//找到最左的那个结点
node = node->left;
}
node = stk.top();
stk.pop();
res.push_back(node->val);
node = node->right;
}
return res;
}
后序遍历
左右中。后序遍历与前面两个有一个很大的不同。例如我们看下面这个二叉树:
它的后序遍历是[8、13、9、15、7、20、3]。也就是先访问左子树再访问右子树,然后再访问父结点。
如果用前序遍历的思路,访问左子树之后,就要通过栈中保存的父结点访问右子树,然后再访问父结点,这时你就会发现父结点需要被访问两次。这显然是一个栈做不到的。
后序遍历的常见方法有:反转法,访问标记法(栈中结点被访问了两次才能出栈),Morris算法。这里详细讲讲反转法。
反转法
将上图所示二叉树的后序遍历顺序反转一下,得到[3、20、7、15、9、13、8]。观察发现,这个遍历顺序是中右左,本质上是和前序遍历是一样的,只不过是先访问右子树再访问左子树,再把顺序反转一下不就得到后序遍历的顺序了吗😁。
这个方法可以巧妙避开直接后序遍历的难处,代码实现如下:
vector<int> postOrderTraversal(TreeNode* root) {
vector<int> res;
if (root == nullptr) {
return res;
}
stack<TreeNode*> stk;
TreeNode* node = root;
while (!stk.empty() || node != nullptr) {
//先处理右子树
while (node != nullptr) {
res.push_back(node->val);
stk.push(node);
node = node->right;
}
//再处理左子树
node = stk.top();
stk.pop();
node = node->left;
}
//反转数组得到后序遍历顺序
reverse(res.begin(), res.end());
return res;
}
END
记得数据结构课上有一道实验报告就是实现这个算法,当时好像使用指针标记父结点,各种指针指来指去,我是真的看不下去,现在总算补上了。