在刷力扣的 145. 二叉树的后序遍历时发现了一种时间复杂度为 O(n),空间复杂度为 O(1) 的算法:morris 遍历。
morris 遍历通过利用树结点的空闲右指针,来避免额外空间的使用。在 morris 遍历的过程中插入合适的观测点即可实现二叉树的前序、中序和后序遍历。
morris 遍历的规则:
约定:当前结点(cur),cur 的中序前驱结点(pre)(从 cur.left 开始一直往右走,直到遇到空时的最后一个非空结点)。
初始化 cur = root。
如果 cur.left == null,则 cur = cur.right。
如果cur.left != null,则找到当前节点的中序前驱结点(pre)。
如果 pre.right == null,则 pre.right = cur, cur = cur.left。
如果 pre.right != null(一定等于 cur),则 pre.right = null, cur = cur.right。
重复以上过程直到 cur == null。
morris 遍历的代码实现:
在代码注释的地方插入相应的代码即可实现前序、中序和后序遍历。
auto morris(TreeNode * root) {
auto cur = root; // 如果需要后序遍历,这里应该指向一个新结点,新结点的子左结点是 root,子右结点是 nullptr
while (cur != nullptr) {
if (cur->left == nullptr) {
// 前序
// 中序
cur = cur->right;
continue;
}
// 找到中序前驱结点(pre)
auto pre = cur->left;
while (pre->right != nullptr && pre->right != cur) {
pre = pre->right;
}
if (pre->right == nullptr) {
// 前序
pre->right = cur;
cur = cur->left;
} else {
// 中序
pre->right = nullptr;
// 后序
// 与前序和中序不同的地方,在这里从 cur->left 到 pre 的逆序才是后序遍历的部分结果
// 因此严格来说,保存这个逆序的操作的空间复杂度为 O(n)
cur = cur->right;
}
}
}
注意:后序比前序和中序复杂,需要一个临时的根结点,空间复杂度严格来说也不算 O(1) 而是 O(n)
前序、中序、后序的具体版本(代码块中没法加粗,已在差异的地方加上注释):
前序
auto morris(TreeNode * root) {
auto res = std::vector<int>();
auto cur = root;
while (cur != nullptr) {
if (cur->left == nullptr) {
// 前序
res.push_back(cur->val);
cur = cur->right;
continue;
}
auto pre = cur->left;
while (pre->right != nullptr && pre->right != cur) {
pre = pre->right;
}
if (pre->right == nullptr) {
// 前序
res.push_back(cur->val);
pre->right = cur;
cur = cur->left;
} else {
pre->right = nullptr;
cur = cur->right;
}
}
return res;
}
中序
auto morris(TreeNode * root) {
auto res = std::vector<int>();
auto cur = root;
while (cur != nullptr) {
if (cur->left == nullptr) {
// 中序
res.push_back(cur->val);
cur = cur->right;
continue;
}
auto pre = cur->left;
while (pre->right != nullptr && pre->right != cur) {
pre = pre->right;
}
if (pre->right == nullptr) {
pre->right = cur;
cur = cur->left;
} else {
// 中序
res.push_back(cur->val);
pre->right = nullptr;
cur = cur->right;
}
}
return res;
}
后序
auto morris(TreeNode * root) {
auto res = std::vector<int>();
// 后序
auto temp = TreeNode(0, root, nullptr);
auto cur = &temp;
while (cur != nullptr) {
if (cur->left == nullptr) {
cur = cur->right;
continue;
}
auto pre = cur->left;
while (pre->right != nullptr && pre->right != cur) {
pre = pre->right;
}
if (pre->right == nullptr) {
pre->right = cur;
cur = cur->left;
} else {
pre->right = nullptr;
// 后序
auto old_size = res.size();
for (auto i = cur->left; i != nullptr; i = i->right) {
res.push_back(i->val);
}
std::reverse(res.begin() + old_size, res.end());
cur = cur->right;
}
}
return res;
}
可以通过做题来验证自己理解的是否正确:前序、中序、后序。
如果我描述的不够明白,也可以看看这位作者的
god-jiang:神级遍历——morriszhuanlan.zhihu.com