神奇的Morris遍历
Morris 遍历
概述
- 一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1)。通过利用原树中大量空闲右指针的方式,达到节省空间的目的。
- Morris的本质是线索二叉树(Threaded Bianry Tree)。本质是利用二叉树中n+1个指向NULL的指针。
- Morris遍历的关键
- 利用一棵树上大量的右指针空闲空间
- Morris遍历细节
- 假如现在有个当前节点cur,开始时cur再root的位置
- 1.如果cur没有左孩子,cur向右移动
- 2.如果cur有左孩子,找到左子树上最右的节点 mostRight
- a.如果mostRight.right为null,就让其指向cur,也就是mostRight.right=cur,然后cur向左移动cur=cur.left。
- b.如果mostRight.right=cur,则让其指向null,然后cur向右移动cur=cur.right.
- 3.cur为空时遍历停止。
- morris序
- 任何结点只要有左树,都会来两次,而且是在遍历完左树后,第二次回到这个结点;如果某个结点没有左树,只会到一次。
应用
Morris 先序遍历
- 思路:对于能回到自己两次的结点,在第一次到的时候就处理;对于只会到达一次的结点,就直接处理
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> stoge = new ArrayList<Integer>();
TreeNode mostRight=null;
TreeNode cur=root;
if(root==null){
return stoge;
}
while(cur!=null){
mostRight=cur.left;
if(mostRight!=null){
while(mostRight.right!=null&&mostRight.right!=cur){
mostRight=mostRight.right;
}
if(mostRight.right==null){
stoge.add(cur.val);
mostRight.right=cur;
cur=cur.left;
continue;
}
else{
mostRight.right=null;
}
}
else{
stoge.add(cur.val);
}
cur=cur.right;
}
return stoge;
}
Morris 中序遍历
- 思路:对于能回到自己两次的结点,在第二次到的时候就处理;对于只会到达一次的结点就直接处理
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> storage = new ArrayList<Integer>();
Stack<TreeNode> visit = new Stack<TreeNode>();
TreeNode cur = root;
while(cur!=null||!visit.isEmpty()){
if(cur!=null){
visit.push(cur);
cur=cur.left;
}
else{
cur=visit.pop();
storage.add(cur.val);
cur=cur.right;
}
}
return storage;
}
Morris 后序遍历
- 思路:把处理的时机放在能回到自己两次的结点,并且是第二次回到自己的时候,但是不打印自己,而是逆序打印自己左子树的右边界。最后,Morris遍历完后,逆序打印整棵树的右边界。
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> stoge = new ArrayList<Integer>();
Stack<Integer> visit = new Stack<Integer>();
TreeNode cur = root,mostRight =null;
if(cur==null){
return stoge;
}
while(cur!=null){
mostRight=cur.left;
if(mostRight!=null){
while(mostRight.right!=null&&mostRight.right!=cur){
mostRight=mostRight.right;
}
if(mostRight.right==null){
mostRight.right=cur;
cur=cur.left;
continue;
}else{
mostRight.right=null;
addPath(stoge,cur.left);
}
}
cur=cur.right;
}
addPath(stoge,root);
return stoge;
}
public void addPath(List<Integer>stoge,TreeNode node){
int count = 0;
while(node!=null){
++count;
stoge.add(node.val);
node=node.right;
}
int left = stoge.size()-count,right = stoge.size()-1;
while(left<right){
int temp = stoge.get(left);
stoge.set(left,stoge.get(right));
stoge.set(right,temp);
left++;
right--;
}
}
问题:
问题1:逆序打印一棵树的右边界,是不是一定得用到栈呢???
- 答:其实还有一种方法,那就是链表反转。
问题2:Morris遍历它的每到一个结点,都会遍历该结点左子树的右边界两次,那么它的时间复杂度还会使O(n)吗?
- 答:所有的左子树的右边界都是不重复的,也就是说,所有结过它左子树的有边界的时间复杂度也就是整棵树的规模而已。
本部落格所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 Kalyan的小书房!
評論