Morris算法通过建立与收回线索进行遍历,无需栈或队列,仅需几个指针,可以做到空间O(1)
二叉树遍历迭代法请参考
Morris前序
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
TreeNode cur = root;
while(cur != null){
if(cur.left != null){ //cur有左子树
TreeNode pre = cur.left;
while(pre.right!=null && pre.right!=cur) pre = pre.right;
if(pre.right==null){
//第一次来
list.add(cur.val); //输出cur
pre.right = cur; //建立线索
cur = cur.left; //继续处理左子树
}else{
//第二次来
pre.right = null; //收回线索
cur = cur.right; //去右子树
}
}else{
//cur无左子树
list.add(cur.val); //输出cur
cur = cur.right; //去右子树
}
}
return list;
}
}
Morris中序
class Solution {
public List<Integer> inorderTraversal(TreeNode root) { //Morris中序遍历
List<Integer> list = new ArrayList<>();
TreeNode cur = root; //当前结点
TreeNode pre = null; //中序线索前驱
while(cur != null){
if(cur.left == null){ //当前结点无左子树
list.add(cur.val); //直接输出
cur = cur.right; //往右子树遍历
}else{
pre = cur.left; //当前结点有左子树,pre初始指向左孩子
//pre右子树不为空,且右指针不指向cur
while(pre.right != null && pre.right != cur){
pre = pre.right; //pre继续往右下
}
if(pre.right == null){ //右指针为空,第一次来该结点,往下层建立线索的阶段
pre.right = cur; //建立线索,pre结点为cur的中序线索前驱
cur = cur.left; //当前结点往左子树继续建立线索
}else if(pre.right == cur){ //右指针指向cur,第二次来该结点,往上层边输出边收回线索
list.add(cur.val); //输出
pre.right = null; //收回线索
cur = cur.right; //左子树遍历完毕,去右子树
}
}
}
return list;
}
}
Morris后序
leetcode145. 二叉树的后序遍历
Morris后序算法较为复杂,需要两个辅助方法。
熟悉后序输出顺序的话可知:后序输出序列最后是反向输出该树的最右侧一线,而Morris是以中序顺序为基准,并且不使用辅助栈/队列,否则失去Morris节省空间的意义。
所以在输出最右侧一线时:
- 先反转最右侧一线的顺序
- 输出这一线
- 再反转回来
注意此时千万不要想象树的结构,把最右侧一线当成一个链表理解。
因为在反转过程中,树的结构会被打乱。
printRight(TreeNode node):反向输出最右侧一线
reverse(TreeNode root):反转最右侧一线
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
TreeNode cur = root;
while(cur != null){
if(cur.left != null){
//cur有左子树
TreeNode p = cur.left; //用p来找左子树最右侧结点
while(p.right!=null && p.right!=cur) p = p.right;
if(p.right == null){
//第一次来
p.right = cur; //建立索引
cur = cur.left; //继续处理左子树
}else{
//第二次来
p.right = null; //收回索引
printRight(cur.left,list); //输出其左子树的最右侧一线
cur = cur.right; //去处理右子树
}
}else{
//cur没有左子树
cur = cur.right; //去处理右子树
}
}
printRight(root,list); //最后输出整棵树的最右侧一线
return list;
}
private void printRight(TreeNode node , List<Integer> list){ //输出node结点的最右侧一线
TreeNode tail = reverse(node); //反转最右侧一线
TreeNode cur = tail; //cur初始为反转后的根结点
while(cur != null){
list.add(cur.val); //输出
cur = cur.right; //指向其右孩子
}
reverse(tail); //再反转回来
}
private TreeNode reverse(TreeNode root){ //反转node为根结点的最右侧一线
TreeNode cur = root; //当前需处理的结点,初始为root
TreeNode parent = null; //指向cur的父节点,初始为null,最终为翻转后的根结点
TreeNode child = null; //指向cur的右孩子结点,初始为null
while(cur != null){
child = cur.right; //先记录当前结点cur的右孩子
cur.right = parent; //反转,cur的右指针指向双亲
parent = cur; //父指针移动,为下一轮做准备
cur = child; //cur指向其原本的右孩子,为下一轮做准备
}
return parent; //翻转后的根结点
}
}