写在前面
笔试不推荐用,容易出错,平常写就好
面试推荐用,增加印象分
其他:Morris方法中间过程会改变原树结点的指向,虽然最后都可以改回来,但是题中如果要求过程中不可以改变树的走向,那么Morris方法就不可以用了
综述
一种遍历二叉树的方式,并且时间复杂度是O(N), 额外空间复杂度是O(1)
通过利用原树中大量空闲指针的方式,达到节省空间的目的。
Morris遍历
Morris遍历细节
假设来到当前节点cur,开始时cur来到头结点位置
-
如果cur没有左孩子,cur向右移动(cur = cur.right)
-
如果cur有左孩子,找到左子树上最右的节点mostRight:
a. 如果mostRight的右指针指向空, 让其指向cur, 然后cur向左移动(cur = cur.left)
b. 如果mostRight的右指针指向cur, 让其指向null, 然后cur向右移动(cur = cur.right)
-
cur为空时遍历停止
Morris遍历图解
/////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
等等
cur依次出现的顺序是:1 2 4 2 5 1 3 6 3 7
相关分析
每一个有左子树的节点都会出现两次, 比如 1 2 3
如果一个结点有左树,怎样判断是第几次回到自己
答:通过自己左子树的最右节点指向谁来确定,如果指向null,就是第一次,如果指向自己就是第二次。
相关代码
//定义结点结构
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static void morris(Node head){
if (head == null){
return;
}
//当前cur先来到head节点
Node cur = head;
//承载是否有左子树和有左子树的情况下左子树最右侧的节点
Node mostRight = null;
while(cur != null){//符合上面morris的流程,直到cur是null才停止
mostRight = cur.left;//mostRight是cur左孩子
if (mostRight != null){//有左子树
//右侧不为空就进行下去,如果右侧不是当前节点进行下去,符合上面morris的流程
while (mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
//如果当前节点左子树最右侧节点的右侧是null
if (mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue;
}else {
//如果当前节点左子树最右侧节点的右侧是cur
mostRight.right = null;
}
}
cur = cur.right;
}
}
复杂度分析
每来到一个节点就会遍历一下它的左孩子的右边界,这个会不会使时间复杂度上升呢?
答:因为每个需要遍历右边界的节点,遍历的节点都是不同的,所以时间复杂度不会变化是O(N)。
- 故空间复杂度是O(1),时间复杂度是O(N)。
Morris改前序遍历
如果一个节点只出现过一次直接打印,如果出现过两次,打印第一次的
相关代码:
//定义结点结构
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
//改先序遍历
public static void morris(Node head){
if (head == null){
return;
}
//当前cur先来到head节点
Node cur = head;
//承载是否有左子树和有左子树的情况下左子树最右侧的节点
Node mostRight = null;
while(cur != null){//符合上面morris的流程,直到cur是null才停止
mostRight = cur.left;//mostRight是cur左孩子
if (mostRight != null){//有左子树
//右侧不为空就进行下去,如果右侧不是当前节点进行下去,符合上面morris的流程
while (mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
//如果当前节点左子树最右侧节点的右侧是null
if (mostRight.right == null){//这是第一次来到cur
//打印第一次出现的
//---------------------先序添加下面------------
System.out.println(cur.value);
//---------------------先序添加上面------------
mostRight.right = cur;
cur = cur.left;
continue;
}else {
//mostRight.right == cur
//这是第二次来到cur
//如果当前节点左子树最右侧节点的右侧是cur
mostRight.right = null;
}
//---------------------先序添加下面------------
}else{
//没有左子树的情况
System.out.println(cur.value);
}
//---------------------先序添加上面------------
cur = cur.right;
}
}
Morris改中序遍历
如果一个节点只出现过一次直接打印,如果出现过两次,打印第二次的
相关代码:
//定义结点结构
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
//改中序遍历
public static void morris(Node head){
if (head == null){
return;
}
//当前cur先来到head节点
Node cur = head;
//承载是否有左子树和有左子树的情况下左子树最右侧的节点
Node mostRight = null;
while(cur != null){//符合上面morris的流程,直到cur是null才停止
mostRight = cur.left;//mostRight是cur左孩子
if (mostRight != null){//有左子树
//右侧不为空就进行下去,如果右侧不是当前节点进行下去,符合上面morris的流程
while (mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
//如果当前节点左子树最右侧节点的右侧是null
if (mostRight.right == null){//这是第一次来到cur
mostRight.right = cur;
cur = cur.left;
continue;
}else {
//mostRight.right == cur
//这是第二次来到cur
//如果当前节点左子树最右侧节点的右侧是cur
mostRight.right = null;
}
}
//---------------------中序添加下面------------
System.out.println(cur.value);
//---------------------中序添加上面------------
cur = cur.right;
}
}
Morris改后序遍历
先只看出现两次的节点,在这个节点出现第二次的时候,逆序打印它的左子树的右边界,等过完了所有出现两次的节点,逆序打印整棵树的右边界
图像分析
相关代码:
//定义结点结构
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
//改后序遍历
public static void morris(Node head){
if (head == null){
return;
}
//当前cur先来到head节点
Node cur = head;
//承载是否有左子树和有左子树的情况下左子树最右侧的节点
Node mostRight = null;
while(cur != null){//符合上面morris的流程,直到cur是null才停止
mostRight = cur.left;//mostRight是cur左孩子
if (mostRight != null){//有左子树
//右侧不为空就进行下去,如果右侧不是当前节点进行下去,符合上面morris的流程
while (mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
//如果当前节点左子树最右侧节点的右侧是null
if (mostRight.right == null){//这是第一次来到cur
mostRight.right = cur;
cur = cur.left;
continue;
}else {
//mostRight.right == cur
//这是第二次来到cur
//如果当前节点左子树最右侧节点的右侧是cur
mostRight.right = null;
//---------------------后序添加下面------------
//逆序打印左树的右边界
printEdge(cur.left);
//---------------------后序添加上面------------
}
}
cur = cur.right;
}
//整个while跑完后,单独打印整个树的右边界
printEdge(head);
System.out.println();
}
//以X为头的树,逆序打印这棵树的右边界
public static void printEdge(Node X) {
Node tail = reverseEdge(X);
Node cur = tail;
while(cur != null){
System.out.println(cur.value + " ");
cur = cur.right;
}
//最后在反转回去,毕竟我们不能改变树的走向(最终),有些题,任何时候都不可以
//那么morris这个方法都不能用了
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 class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
//改二叉搜索树
public static boolean isBST(Node head){
if (head == null){
return true;
}
//当前cur先来到head节点
Node cur = head;
//承载是否有左子树和有左子树的情况下左子树最右侧的节点
Node mostRight = null;
//---------------------二叉搜索树添加下面------------
int preValue = Integer.MIN_VALUE;
//---------------------二叉搜索树添加上面------------
while(cur != null){//符合上面morris的流程,直到cur是null才停止
mostRight = cur.left;//mostRight是cur左孩子
if (mostRight != null){//有左子树
//右侧不为空就进行下去,如果右侧不是当前节点进行下去,符合上面morris的流程
while (mostRight.right != null && mostRight.right != cur){
mostRight = mostRight.right;
}
//如果当前节点左子树最右侧节点的右侧是null
if (mostRight.right == null){//这是第一次来到cur
mostRight.right = cur;
cur = cur.left;
continue;
}else {
//mostRight.right == cur
//这是第二次来到cur
//如果当前节点左子树最右侧节点的右侧是cur
mostRight.right = null;
}
}
//---------------------二叉搜索树添加下面------------
//从中序上改,也是这个位置
if(cur.value <= preValue){
return false;
}
preValue = cur.value;
//---------------------二叉搜索树添加上面------------
cur = cur.right;
}
return true;
}
//以X为头的树,逆序打印这棵树的右边界
public static void printEdge(Node X) {
Node tail = reverseEdge(X);
Node cur = tail;
while(cur != null){
System.out.println(cur.value + " ");
cur = cur.right;
}
//最后在反转回去,毕竟我们不能改变树的走向(最终),有些题,任何时候都不可以
//那么morris这个方法都不能用了
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;
}
相关分析
这样的解法,不用将数据收集到list之后,在比较是不是递增的,解决了最本质的遍历的问题,所以很多问题,Morris都是最优解。
dp套路与Morris
因为Morris方法解决了最本质的遍历的问题,所以很多问题,Morris都是最优解。
那么那些方法是以dp套路是最优解,那些是以Morris是最优解?
答:
- 如果你发现你的方法必须做第三次信息的强整合,就是dp套路最优解。
- 如果不需要,最优解就是Morris遍历