中序遍历 前序遍历 后序遍历 编程题_morris 遍历/线索二叉树

在刷力扣的 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:神级遍历——morris​zhuanlan.zhihu.com
9e785476fa33220c5059bf5df42fe8a0.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值