算法训练营【6】Morris遍历及其相关扩展

双指针思想

双指针指的是两个指针,而这俩个指针可以随意步数的前进后退

一般是一次一步往后走

一个数组,请把奇数放到奇数下标,偶数放到偶数下标

双指针

a在0位置,b在1位置,两个指针每次走两步,则一个始终指向奇数位置,一个为偶数位置

从后往前遍历,如果当前数为奇数,与b交换,b往后两步,

如果当前数为偶数则与a 交换,a 往后两步,
当前数再重复判断,直到满足题目要求再往前遍历

二叉树的Morris遍历 (面试用)

一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1)

通过利用原树中大量空闲指针的方式,达到节省空间的目的

一般的递归和迭代方法都是

时间复杂O(n)

空间复杂度 递归栈开的空间为O(h)

流程

1)如果cur没有左孩子,cur向右移动(cur = cur.right)

2)如果cur有左孩子,找到左子树上最右的节点mostRight:

​ a.如果mostRight的右指针指向空,让其指向cur,

​ 然后cur向左移动(cur = cur.left)

​ b.如果mostRight的右指针指向cur,让其指向null,

​ 然后cur向右移动(cur = cur.right)

3)cur为空时遍历停止

实质:

对于没有左子树的节点只到达一次,

对于有左子树的节点会到达两次

morris遍历时间复杂度依然是O(N)

	public static class Node {
    
    
		public int value;
		Node left;
		Node right;
 
		public Node(int data) {
    
    
			this.value = data;
		}
	}
 
public static void morris(Node head) {
    
    
		if (head == null) {
    
    
			return;
		}
		Node cur = head;
		Node mostRight = null;
		while (cur != null) {
    
    
			mostRight = cur.left;
			if (mostRight != null) {
    
    
        // while 为找到最右节点 
        //要么第一次到达最右节点,要么第二次到达时,最右节点指向了当前节点,则此时遍历的节点就是最右节点 
				while (mostRight.right != null && mostRight.right != cur) {
    
    
					mostRight = mostRight.right;
				}
        //第一次来到我自己
				if (mostRight.right == null) {
    
    
					mostRight.right = cur;
					cur = cur.left;
					continue;
				} else {
    
    
          // 此时最右节点指向了cur,需要将指针置为空
					mostRight.right = null;
				}
			}
			cur = cur.right;
		}
	}

先序遍历

​ 1

2 3

4 5 6 7

morris 序列 1 2 4 2 5 3 6 3 7

打印 morris 序列中第一次出现的元素就是先序遍历

(有左孩子即会出现两次)

先序 1 2 4 5 3 6 7

//第一次到达时,打印
	public static void morrisPre(Node head) {
    
    
		if (head == null) {
    
    
			return;
		}
		Node cur = head;
		Node mostRight = null;
		while (cur != null) {
    
    
			mostRight = cur.left;
			if (mostRight != null) {
    
    
				while (mostRight.right != null && mostRight.right != cur) {
    
    
					mostRight = mostRight.right;
				}
				if (mostRight.right == null) {
    
    
          // 有左树,第二次到达时打印
					System.out.print(cur.value + " ");
					mostRight.right = cur;
					cur = cur.left;
					continue;
				} else {
    
    
					mostRight.right = null;
				}
			} else {
    
    
        // 没有左树,第一次就打印
				System.out.print(cur.value + " ");
			}
			cur = cur.right;
		}
		System.out.println();
	}

中序遍历

对于出现两次的元素,第二次打印,只出现一次的元素,第一次出现就打印

morris 序列 1 2 4 2 5 3 6 3 7

中序 4 2 5 1 6 3 7


	public static void morrisIn(Node head) {
    
    
		if (head == null) {
    
    
			return;
		}
		Node cur = head;
		Node mostRight = null;
		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;
				}
			}
      //只能到达一次的第一次打印,可以到达两次的第二次到达才打印
			System.out.print(cur.value + " ");
			cur = cur.right;
		}
		System.out.println();
	}

后序遍历

对于可以到达两次的元素,第二次回到该元素时,逆序打印右边界

整个树遍历后,逆序打印整棵树的右边界

原理为:

一个树可以被右边界分解掉,由左往右,每次逆序打印其右边界,就是后序遍历

中途要求逆序打印,但morris 要求O(1)空间,即排除了使用额外空间的办法

可以使用反转链表来实现逆序打印


	public static void morrisPos(Node head) {
    
    
		if (head == null) {
    
    
			return;
		}
		Node cur = head;
		Node mostRight = null;
		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;
          //第二次回到该节点时,逆序打印此节点
					printEdge(cur.left);
				}
			}
			cur = cur.right;
		}
    // 结束之后,打印整个树的右边界
		printEdge(head);
		System.out.println();
	}
 
	//先链表反转,遍历后再反转回去
	public static void printEdge(Node head) {
    
    
		Node tail = reverseEdge(head);
		Node cur = tail;
		while (cur != null) {
    
    
			System.out.print(cur.value + " ");
			cur = cur.right;
		}
		reverseEdge(tail);
	}
 
	public static Node reverseEdge(Node from) {
    
    
		Node pre = null;
		Node next = null;
		while (from != null) {
    
    
			next = from.right;
			from.right = pre;
			pre = from;
			from = next;
		}
		return pre;
	}

判断是否为二叉查找树

中序遍历为递增序列则为二叉查找树

使用morris 遍历


	public static boolean isBST(Node head) {
    
    
		if (head == null) {
    
    
			return;
		}
		Node cur = head;
		Node mostRight = null;
    Integer pre= null;
		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;
				}
			}
      // 如果上一个值大于等于当前值,则这个树一定不是 bst
         if(pre !=null && pre>=cur.value  ){
    
    
           return false;
         }
      pre =cur.value; 
			cur = cur.right;
		}
 
    return true;
	}

求二叉树最小高度

给定一棵二叉树的头节点head

求以head为头的树中,最小深度是多少?

递归办法

public static class Node {
    
    
		public int val;
		public Node left;
		public Node right;
 
		public Node(int x) {
    
    
			val = x;
		}
	}
 
	public static int minHeight1(Node head) {
    
    
		if (head == null) {
    
    
			return 0;
		}
		return p(head);
	}
 
	// 返回x为头的树,最小深度是多少
    // 使用递归求解
	public static int p(Node x) {
    
    
		if (x.left == null && x.right == null) {
    
    
			return 1;
		}
		// 左右子树起码有一个不为空
		int leftH = Integer.MAX_VALUE;
		if (x.left != null) {
    
    
			leftH = p(x.left);
		}
		int rightH = Integer.MAX_VALUE;
		if (x.right != null) {
    
    
			rightH = p(x.right);
		}
		return 1 + Math.min(leftH, rightH);
	}

morris 办法

需要做到以下两点

​ 每到一个节点,可以知道它的高度

​ 每到一个节点,可以判断出是否为叶子节点

	// 根据morris遍历改写

	public static int minHeight2(Node head) {
    
    
		if (head == null) {
    
    
			return 0;
		}
		Node cur = head;
		Node mostRight = null;
		int curLevel = 0;
		int minHeight = Integer.MAX_VALUE;
		while (cur != null) {
    
    
			mostRight = cur.left;
			if (mostRight != null) {
    
    
          int rightBoardSize = 1;
          while (mostRight.right != null && mostRight.right != cur) {
    
    
            // 由下往上的元素层数
            rightBoardSize++;
            mostRight = mostRight.right;
          }
          if (mostRight.right == null) {
    
     // 第一次到达
            curLevel++;
            mostRight.right = cur;
            cur = cur.left;
            continue;
          } else {
    
     
            // 第二次到达,需要减去rightBoardSize
            if (mostRight.left == null) {
    
    
              //此时到达叶节点,计算最小高度
              minHeight = Math.min(minHeight, curLevel);
            }
            curLevel -= rightBoardSize;
            mostRight.right = null;
          }
			} else {
    
     
        // 只有一次到达
				curLevel++;
			}
			cur = cur.right;
		}
		int finalRight = 1;
		cur = head;
		while (cur.right != null) {
    
    
			finalRight++;
			cur = cur.right;
		}
    // 单独去找一下最右部分的最小高度
		if (cur.left == null && cur.right == null) {
    
    
			minHeight = Math.min(minHeight, finalRight);
		}
		return minHeight;
	}

猜你喜欢

转载自blog.csdn.net/qq_41852212/article/details/121472214