Morris二叉树遍历
# 前言
As we all know,二叉树遍历的方法有递归和利用栈实现的迭代版本,他们的时间和空间复杂度都为 O(n) 。
然而,利用 Morris 遍历方法(利用叶子节点的左右空结点),可以将空间复杂度将为 O(1) ,时间复杂度仍为 O(n) 。
# 实现
## 前序遍历
1. 如果当前节点左孩子 cur->left == NULL,输出当前节点的值 cur->value 并将当前结点指向右孩子 cur = cur->right。
2. 如果当前节点左孩子 cur->left != NULL,那么在当前节点的左子树中找出最右叶子结点(即前驱结点 pre )。
① 如果前驱节点的右孩子 pre->right == NULL ,那么将右孩子指向当前节点(pre->right = cur)。
输出当前节点的值 cur->value。当前节点更新为当前节点的左孩子 cur = cur->left。
② 如果前驱节点的右孩子 pre->right != NULL,即 pre->right = cur。
重新将右孩子设为空 pre->right == NULL。当前节点更新为当前节点的右孩子cur = cur->right。
3. 重复 1、2 直到当前节点为空。
代码
vector<int> preorderTraversal(TreeNode* root)
{
TreeNode* cur = root;
TreeNode* pre = NULL;
vector<int> result;
while(cur!=NULL)
{
if (cur->left == NULL)
{
result.push_back(cur->val);
cur = cur->right;
}
else
{
pre = cur->left;
while(pre->right!=NULL && pre->right!=cur)
{
pre = pre->right;
}
if (pre->right==NULL)
{
pre->right=cur;
result.push_back(cur->val);
cur = cur->left;
}
else
{
pre->right = NULL;
cur = cur->right;
}
}
}
return result;
}
## 中序遍历
中序遍历与前序遍历相同,输出时机不同而已。
1. 如果当前节点左孩子 cur->left == NULL,输出当前节点的值 cur->value 并将当前结点指向右孩子 cur = cur->right。
2. 如果当前节点左孩子 cur->left != NULL,那么在当前节点的左子树中找出最右叶子结点(即前驱结点 pre )。
① 如果前驱节点的右孩子 pre->right == NULL ,那么将右孩子指向当前节点(pre->right = cur)。
当前节点更新为当前节点的左孩子 cur = cur->left。
② 如果前驱节点的右孩子 pre->right != NULL,即 pre->right = cur。
重新将右孩子设为空 pre->right == NULL。
输出当前节点的值 cur->value。
当前节点更新为当前节点的右孩子cur = cur->right。
3. 重复 1、2 直到当前节点为空。
代码
vector<int> inorderTraversal(TreeNode* root)
{
TreeNode* cur = root;
TreeNode* pre = NULL;
vector<int> result;
while(cur!=NULL)
{
if (cur->left == NULL)
{
result.push_back(cur->val);
cur = cur->right;
}
else
{
pre = cur->left;
while(pre->right!=NULL && pre->right!=cur)
{
pre = pre->right;
}
if (pre->right == NULL)
{
pre->right=cur;
cur = cur->left;
}
else
{
pre->right = NULL;
result.push_back(cur->val);
cur = cur->right;
}
}
}
return result;
}
## 后序遍历
1. 新增临时结点dump,并且将 root 设为 dump 的左孩子。
2. 如果当前节点左孩子 cur->left == NULL,输出当前节点的值 cur->value 并将当前结点指向右孩子 cur = cur->right。
3. 如果当前节点左孩子 cur->left != NULL,那么在当前节点的左子树中找出最右叶子结点(即前驱结点 pre )。
① 如果前驱节点的右孩子 pre->right == NULL ,那么将右孩子指向当前节点(pre->right = cur)。
当前节点更新为当前节点的左孩子 cur = cur->left。
② 如果前驱节点的右孩子 pre->right != NULL,即 pre->right = cur。
逆序输出当前结点左孩子到前序结点的路径。
重新将右孩子设为空 pre->right == NULL。当前节点更新为当前节点的右孩子cur = cur->right。
4. 重复 2、3 直到当前节点为空。
代码
vector<int> postorderTraversal(TreeNode* root)
{
TreeNode dump(-1);
dump.left = root;
TreeNode* cur = &dump;
TreeNode* pre = NULL;
vector<int> result;
while(cur!=NULL)
{
if (cur->left == NULL)
{
cur = cur->right;
}
else
{
pre = cur->left;
while(pre->right!=NULL && pre->right!=cur)
{
pre = pre->right;
}
if (pre->right == NULL)
{
pre->right=cur;
cur = cur->left;
}
else
{
printReverse(cur->left, pre, result);
pre->right = NULL;
cur = cur->right;
}
}
}
return result;
}
void printReverse(TreeNode* from, TreeNode* to, vector<int>& result) //输出单链表
{
Reverse(from, to);
TreeNode* p = to;
while(true)
{
result.push_back(p->val);
if(p == from)
{
break;
}
p = p->right;
}
Reverse(to, from);
}
void Reverse(TreeNode* from, TreeNode* to) //翻转单链表
{
TreeNode* x = from;
TreeNode* y = from->right;
TreeNode* z;
if (from == to)
{
return;
}
x->right = NULL;
while(x != to)
{
z = y->right;
y->right = x;
x = y;
y = z;
}
}
# 总结
通过三种遍历可以看到,其实总体上的代码逻辑没有发生改变,主要是改变了输出结果的时机和方式。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· 手把手教你更优雅的享受 DeepSeek
· AI工具推荐:领先的开源 AI 代码助手——Continue
· 探秘Transformer系列之(2)---总体架构
· V-Control:一个基于 .NET MAUI 的开箱即用的UI组件库
· 乌龟冬眠箱湿度监控系统和AI辅助建议功能的实现