Morris遍历的原理
- 我们知道遍历二叉树是以一定的规则(前序、中序或后序)将二叉树中的结点排列成一个线性序列。因为二叉树是非线性的结构嘛,这实质上就是将非线性变为线性,有点高数上“化曲为直”的味道。
- 另一方面,Morris遍历用到了线索二叉树的概念。在有 n 个结点的二叉链表中必定存在 n+1 个空链域,利用这些空链域来存放结点的唯一 前驱 和 后继 结点。正是由于这一点,没有使用额外的空间,使空间复杂度为 O(n) 降到了 O(1) 级别,但是,由于每个根节点都要遍历两次,所以这是一种以时间换空间的策略。
Morris实现思想
假设当前节点为 cur ,在开始时初始化为根结点 root
while( cur != NULL)
{
如果 cur 有左结点,则从左子树找到最右侧结点 p
再判断 p 的右结点是否为空
{
1)如果为空,则将右结点指向 cur (创建返回路径)
2)否则 如果 p 的右结点为 cur ,则将其指向为空
(删除返回路径->还原树结构)
}
否则( cur 没有左结点),cur 进入右结点
} // cur 为空时,遍历结束,所有非叶结点都经过了两次
- 演示图示(注意返回路径的创建时机)
Morris遍历的前序实现(DLR)和 中序实现(LDR)
-
实现参考自LeetCode 94.二叉树的中序遍历
#define MaxSize 1000
/*
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
int* preorderTraversal(struct TreeNode* root, int* returnSize){
if (root == NULL)
{//传参检查
*returnSize = 0;
return NULL;
}
struct TreeNode *cur = root;
int *res = (int*)malloc(MaxSize * sizeof(int));
int size = 0;
while (cur != NULL)
{//遍历所有结点
if ( cur->left != NULL)
{//有左结点
struct TreeNode *p = cur->left;//当前结点的左子树
while (p->right != NULL && p->right != cur)
{// p 移动至左子树最右侧的结点
p = p->right;
}
if (p->right == NULL)
{//第一次访问该结点
//前序遍历 —> 记录根节点
//res[size++] = cur->val;
p->right = cur;//使 p 的后继为 cur
cur = cur->left;//继续访问左结点
}
else
{//已经访问过该结点
//中序遍历 —> 记录根节点
//res[size++] = cur->val;
p->right = NULL;//还原树结构
cur = cur->right;//访问右结点
}
}
else
{//无左结点
//前序/中序 —> 记录左、右结点
//res[size++] = cur->val;
cur = cur->right;//访问右结点 or 回退
}
}
*returnSize = size;
return res;
}
Morris遍历的后序实现(LRD)
这个有点复杂,我觉得很麻烦,感觉时间会花费很多。
1)首先明确每个含有左结点的根节点都会遍历两遍
2)然后当第二次经过该根节点的时候,表示它的左子树的右结点全部遍历过了,意味着这些右结点都是可以输出的了
3)按照后序的顺序,应该先访问右结点,再访问其根结点
4)故将这些右结点形成的单链表反转后,访问输出,再反转恢复原样
-
图示
-
代码实现
#define MaxSize 1000
/*
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
struct TreeNode* ReverseList(struct TreeNode* head){
//相当于反转单链表
if (head->right == NULL)
{//只有一个结点
return head;
}
struct TreeNode *ptr = NULL;
struct TreeNode *cur = head;
struct TreeNode *next = NULL;
while (cur != NULL)
{
next = cur->right;
cur->right = ptr;//反转
ptr = cur;//移动
cur = next;
}
return ptr;
}
int Reverse_visit(struct TreeNode* head,int* arr,int size){
//恢复反转后的树结构,并访问结点
if (head->right == NULL)
{//只有一个结点
arr[size++] = head->val;
return size;
}
struct TreeNode *ptr = NULL;
struct TreeNode *cur = head;
struct TreeNode *next = NULL;
while (cur != NULL)
{
arr[size++] = cur->val;//逆序遍历
next = cur->right;
cur->right = ptr;//反转
ptr = cur;//移动
cur = next;
}
return size;//返回数组大小
}
int* postorderTraversal(struct TreeNode* root, int* returnSize){
if (root == NULL)
{//传参检查
*returnSize = 0;
return NULL;
}
struct TreeNode *cur = root;
struct TreeNode *rightmost = NULL;
int *res = (int*)malloc(MaxSize * sizeof(int));
int size = 0;
while (cur != NULL)
{//遍历所有结点
if ( cur->left != NULL)
{//有左结点
struct TreeNode *p = cur->left;//当前结点的左子树
while (p->right != NULL && p->right != cur)
{// p 移动至左子树最右侧的结点
p = p->right;
}
if (p->right == NULL)
{//第一次访问该结点
p->right = cur;//使 p 的后继为 cur
cur = cur->left;//继续访问左结点
}
else
{//已经访问过该结点
p->right = NULL;//还原树结构
rightmost = ReverseList(cur->left);
//将 cur 的左子树的右结点反转以达到逆序
size = Reverse_visit(rightmost,res,size);
//恢复树结构,并访问结点
cur = cur->right;//访问右结点
}
}
else
{//无左结点
cur = cur->right;//访问右结点 or 回退
}
}
//反转输出根结点(root)的右子树
rightmost = ReverseList(root);
size = Reverse_visit(rightmost,res,size);
*returnSize = size;
return res;
}