题目:
设计一个算法实现二叉树的三种遍历(前序遍历 中序遍历 后序遍历)。
要求时间复杂度为O(n) 空间复杂度为O(1)。
思路:
空间复杂度O(1)的要求很严格。常规的递归实现是显然不能满足要求的[其空间复杂度是树的深度O(h) ]。本篇文章介绍著名的Morris遍历,该方法利用了二叉树结点中大量指向null的指针。
常规的栈结构遍历方式,遍历到某个节点之后并不能回到上层的结点,这是由二叉树本身的结构所限制的,每个结点并没有指向父节点的指针,因此需要使用栈来完成回到上层结点的步骤。
Morris遍历避免了使用栈结构,让下层有指向上层的指针,但并不是所有的下层结点都有指向上层的指针([这些指针也称为空闲指针])。
空闲指针的分配规则如下:
1. 当前子树的头结点为head,空闲指针由head的左子树中最右结点的右指针指向head结点。对head的左子树重复该步骤1,直到遍历至某个结点没有左子树,将该结点记 为node。进入步骤2。
2. 从node结点开始通过每个结点的右指针进行移动,并打印结点的值。
假设遍历到的当前结点为curNode,做如下判断:
curNode结点的左子树中最右结点(记为lastRNode)是否指向curNode。
A. 是 让lastRNode结点的右指针指向null,打印curNode的值。接着通过curNode的右指针遍历下一个结点,重复步骤2。
B. 否 将curNode为头结点的子树重复步骤1。
下面举例说明上述步骤(先以中序遍历为例),二叉树结构如下图所示:
遍历至结点1 发现其没有左子树 记为Node。
curNode : 1 打印1
curNode : 2 满足A 空闲指针由1的右指针指向2 将该空闲指针取消掉 打印2。通过2的右指针遍历到3。
curNode : 3 满足B 进行步骤1 最终打印3。
通过空闲指针遍历至4。
curNode : 4 满足A 空闲指针由3的右指针指向4 将该空闲指针取消掉 打印4。通过4的右指针遍历到6。
至此 左子树和根结点遍历完毕。
curNode : 6 满足B 进行步骤1 之后二叉树变为右图。
遍历至结点5 其没有左子树 记为Node。
curNode : 5 满足B 进行步骤1 最终打印5。
通过空闲指针遍历至6。
curNode : 6 满足A 空闲指针由5的右指针指向6 将该空闲指针取消掉 打印6。
通过6的右指针遍历到7。
curNode : 7 满足B 进行步骤1 最终打印7。
3. 步骤2最终移动到null结点 整个过程结束。
总结:
打印某个结点时,一定是在步骤2开始移动的过程中。
步骤2最开始从子树最左结点开始,在通过右指针移动过程中,只有以下两种移动方式:
①移动到某个结点的右子树【此时 左子树和根结点必定已经打印结束】
②移动到某个上层结点(即通过空闲指针移动)【此时 该上层结点的左子树整体打印完毕 开始处理根结点】
Morris先序遍历只需要将打印顺序稍微调整一下(调整至步骤1中打印)。
Morris后序遍历同样是需要将打印顺序稍微调整一下,即:逆序打印(不能使用额外的数据结构)所有结点的左子树右边界,在满足步骤2中情况A时打印。
注:
二叉树结点定义如下:
typedef int dataType;
struct Node
{
dataType val;
struct Node *left;
struct Node *right;
Node(dataType _val):
val(_val), left(NULL), right(NULL){}
};
常规的二叉树遍历方式采用栈实现,比较容易实现,下面直接给出代码。
/*************************Morris遍历二叉树*************************/
- #include <iostream>
- using namespace std;
- typedef int dataType;
- struct Node
- {
- dataType val;
- struct Node *left;
- struct Node *right;
- Node(dataType _val):
- val(_val), left(NULL), right(NULL){}
- };
- // Morris中序遍历 (左 -> 根 -> 右)
- void MorrisInOrderTraverse(Node *head)
- {
- if (head == NULL)
- {
- return;
- }
- Node *p1 = head;
- Node *p2 = NULL;
- while (p1 != NULL)
- {
- p2 = p1->left;
- if (p2 != NULL)
- {
- while(p2->right != NULL && p2->right != p1)
- {
- p2 = p2->right;
- }
- if (p2->right == NULL)
- {
- p2->right = p1; // 空闲指针
- p1 = p1->left;
- continue;
- }
- else
- {
- p2->right = NULL;
- }
- }
- cout<<p1->val<<" ";
- p1 = p1->right;
- }
- }
- // Morris前序遍历 (根 -> 左 -> 右)
- void MorrisPreOrderTraverse(Node *head)
- {
- if (head == NULL)
- {
- return;
- }
- Node *p1 = head;
- Node *p2 = NULL;
- while (p1 != NULL)
- {
- p2 = p1->left;
- if (p2 != NULL)
- {
- while(p2->right != NULL && p2->right != p1)
- {
- p2 = p2->right;
- }
- if (p2->right == NULL)
- {
- p2->right = p1; // 空闲指针
- cout<<p1->val<<" "; // 打印结点值的顺序稍微调整
- p1 = p1->left;
- continue;
- }
- else
- {
- p2->right = NULL;
- }
- }
- else
- {
- cout<<p1->val<<" ";
- }
- p1 = p1->right;
- }
- }
- // 逆序右边界
- Node* reverseEdge(Node *head)
- {
- Node *pre = NULL;
- Node *next = NULL;
- while(head != NULL)
- {
- next = head->right;
- head->right = pre;
- pre = head;
- head = next;
- }
- return pre;
- }
- // 逆序打印左子树右边界
- void printEdge(Node *head)
- {
- Node *lastNode = reverseEdge(head);
- Node *cur = lastNode;
- while (cur != NULL)
- {
- cout<<cur->val<<" ";
- cur = cur->right;
- }
- reverseEdge(lastNode);
- }
- // Morris后序遍历 (左 -> 右 -> 根)
- void MorrisPostOrderTraverse(Node *head)
- {
- if (head == NULL)
- {
- return;
- }
- Node *p1 = head;
- Node *p2 = NULL;
- while (p1 != NULL)
- {
- p2 = p1->left;
- if (p2 != NULL)
- {
- while(p2->right != NULL && p2->right != p1)
- {
- p2 = p2->right;
- }
- if (p2->right == NULL)
- {
- p2->right = p1; // 空闲指针
- p1 = p1->left;
- continue;
- }
- else
- {
- p2->right = NULL;
- printEdge(p1->left);
- }
- }
- p1 = p1->right;
- }
- printEdge(head);
- }
- void buildBinTree(Node **head)
- {
- dataType _val;
- cin>>_val;
- if (_val == -1)
- {
- *head = NULL;
- }
- else
- {
- *head = (Node*)malloc(sizeof(Node));
- (*head)->val = _val;
- buildBinTree(&(*head)->left);
- buildBinTree(&(*head)->right);
- }
- }
- int main(void)
- {
- Node *head;
- buildBinTree(&head);
- cout<<"前序遍历序列为:";
- MorrisPreOrderTraverse(head);
- cout<<endl;
- cout<<"中序遍历序列为:";
- MorrisInOrderTraverse(head);
- cout<<endl;
- cout<<"后序遍历序列为:";
- MorrisPostOrderTraverse(head);
- cout<<endl;
- return 0;
- }
/*************************Morris遍历二叉树 END*************************/
输入:
1 2 3 -1 -1 4 -1 -1 5 6 8 -1 -1 -1 7 -1 -1
输出:
致谢:
本篇文章参考自左神新书《程序员代码面试指南:IT名企算法与数据结构题目最优解》,在此表示感谢。