参考点击打开链接
Morris Traversal方法遍历二叉树(非递归,不用栈,O(1)空间)。
通常,在实现二叉树的前序、中序和后序遍历时,有两种常用方法,一种是递归,一种是使用栈+迭代。这两种方法都是O(n)的空间复杂度(递归本身占用栈空间或者用户自定义的栈)。
Morris Traversal可以做到:
1、O(1)空间复杂度,只使用常数空间;O(n)时间复杂度。
2、不破坏二叉树的形状(中间过程允许改变其形状)。
要使用O(1)空间进行遍历,最大的难点在于遍历到子结点的时候如何返回到父节点,Morris Traversal方法不需要为每个节点额外分配指针指向其前驱结点和后继结点,只需要利用叶子结点中的左右空指针指向某种顺序遍历下的前驱结点和后继结点就可以了。
中序遍历:
1、如果当前结点的左孩子为空,则输出当前结点并将其右孩子作为当前结点。
2、如果当前结点的左孩子不为空,在当前结点的左子树中找到当前结点在中序遍历下的前驱结点。
a) 如果前驱结点的右孩子为空,则它的右孩子设置为当前结点。当前结点更新为当前结点的左孩子;
b) 如果前驱结点的右孩子为当前结点,则将它的右孩子重新设为空(恢复树的形状)。输出当前结点,当前结点更新为当前结点的右孩子。
3、重复以上1、2直到当前结点为空。
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
class Solution {
public static void main(String[] args) {
Solution s = new Solution();
TreeNode p1 = new TreeNode(1);
TreeNode p2 = new TreeNode(2);
TreeNode p3 = new TreeNode(3);
TreeNode p4 = new TreeNode(4);
TreeNode p5 = new TreeNode(5);
TreeNode p6 = new TreeNode(6);
TreeNode p7 = new TreeNode(7);
TreeNode p8 = new TreeNode(8);
TreeNode p9 = new TreeNode(9);
p2.left = p1; p2.right = p4;
p4.left = p3; p4.right = p5;
p6.left = p2; p6.right = p7;
p7.right = p9;
p9.left = p8;
s.inorderMorrisTraversal(p6);
}
public void inorderMorrisTraversal (TreeNode root)
{
TreeNode curr = root;
TreeNode pre = null;
while (curr != null)
{
if (curr.left == null) //1.
{
System.out.println(curr.val);
curr = curr.right;
}
else
{
pre = curr.left;
while (pre.right != null && pre.right != curr)
pre = pre.right;
if (pre.right == null) //2. a)
{
pre.right = curr;
curr = curr.left;
}
else //2. b)
{
pre.right = null;
System.out.println(curr.val);
curr = curr.right;
}
}
}
}
}
复杂度分析:
空间复杂度:O(1),只使用了两个结点变量
时间复杂度:O(n),在于寻找中序遍历下二叉树中所有结点的前驱结点的复杂度是多少。
直接上时间复杂度为O(nlogn),找到单个结点的前驱结点和树的高度有关。但事实上,寻找所有结点的前驱结点只需要O(n)时间。n个结点的二叉树中一共有n-1条边,整个过程中每条边最多只走2次,一次是为定位到某个结点,另一次是为了寻找上面某个结点的前驱结点,所以时间复杂度为O(n)。
前序遍历:
1、如果当前结点的左孩子为空,则输出当前结点并将其右孩子作为当前结点。
2、如果当前结点的左孩子不为空,则在当前结点的左子树中找到当前结点在中序遍历下的前驱结点。
a) 如果前驱结点的右孩子为空,将它的右孩子设置为当前结点。输出当前结点。更新当前结点为当前结点的左孩子;
b) 如果前驱结点的右孩子为当前结点,将它的右孩子重新设为空。当前结点更新为当前结点的右孩子。
3、重复以上1、2直到当前结点为空。
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
class Solution {
public static void main(String[] args) {
Solution s = new Solution();
TreeNode p1 = new TreeNode(1);
TreeNode p2 = new TreeNode(2);
TreeNode p3 = new TreeNode(3);
TreeNode p4 = new TreeNode(4);
TreeNode p5 = new TreeNode(5);
TreeNode p6 = new TreeNode(6);
TreeNode p7 = new TreeNode(7);
TreeNode p8 = new TreeNode(8);
TreeNode p9 = new TreeNode(9);
p2.left = p1; p2.right = p4;
p4.left = p3; p4.right = p5;
p6.left = p2; p6.right = p7;
p7.right = p9;
p9.left = p8;
s.preorderMorrisTraversal(p6);
}
public void preorderMorrisTraversal (TreeNode root)
{
TreeNode curr = root;
TreeNode pre = null;
while (curr != null)
{
if (curr.left == null) //1.
{
System.out.println(curr.val);
curr = curr.right;
}
else
{
pre = curr.left;
while (pre.right != null && pre.right != curr)
pre = pre.right;
if (pre.right == null) //2. a)
{
System.out.println(curr.val);
pre.right = curr;
curr = curr.left;
}
else //2. b)
{
pre.right = null;
curr = curr.right;
}
}
}
}
}
复杂度分析:
时间复杂度和空间复杂度都与中序遍历时的情况相同。
后序遍历:
建立一个临时结点dump,令其左孩子是root。并且还需要一个子过程,倒序输出两个结点之间路径上的各个结点。
当前结点设置为临时结点dump。
1、如果当前结点的左孩子为空,则将其右孩子作为当前结点。
2、如果当前结点的左孩子不为空,在当前结点的左子树中找到当前结点在中序遍历下的前驱结点。
a) 如果前驱结点的右孩子为空,将它的后孩子设置为当前结点。当前结点更新为当前结点的左孩子;
b) 如果前驱结点的右孩子为当前结点,将它的右孩子重新设为空。倒序输出当前结点的左孩子到该前驱结点这条路径上的所有结点。当前结点更新为当前结点的右孩子。
3、重复以上1、2直到当前结点为空。
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; }
}
class Solution {
public static void main(String[] args) {
Solution s = new Solution();
TreeNode p1 = new TreeNode(1);
TreeNode p2 = new TreeNode(2);
TreeNode p3 = new TreeNode(3);
TreeNode p4 = new TreeNode(4);
TreeNode p5 = new TreeNode(5);
TreeNode p6 = new TreeNode(6);
TreeNode p7 = new TreeNode(7);
TreeNode p8 = new TreeNode(8);
TreeNode p9 = new TreeNode(9);
p2.left = p1; p2.right = p4;
p4.left = p3; p4.right = p5;
p6.left = p2; p6.right = p7;
p7.right = p9;
p9.left = p8;
s.postorderMorrisTraversal(p6);
}
public void reverse(TreeNode from, TreeNode to)
{
if (from == to)
return;
TreeNode x = from;
TreeNode y = from.right;
TreeNode z;
while (true)
{
z = y.right;
y.right = x;
x = y;
y = z;
if (x == to) break;
}
}
public void printReverse(TreeNode from, TreeNode to)
{
reverse(from, to);
TreeNode p = to;
while (true)
{
System.out.println(p.val);
if (p == from) break;
p = p.right;
}
reverse(to, from);
}
public void postorderMorrisTraversal (TreeNode root)
{
TreeNode dump = new TreeNode(0);
dump.left = root;
TreeNode curr = dump;
TreeNode pre = null;
while (curr != null)
{
if (curr.left == null) //1.
{
curr = curr.right;
}
else
{
pre = curr.left;
while (pre.right != null && pre.right != curr)
pre = pre.right;
if (pre.right == null) //2. a)
{
pre.right = curr;
curr = curr.left;
}
else //2. b)
{
printReverse(curr.left, pre);
pre.right = null;
curr = curr.right;
}
}
}
}
}
复杂度分析:
空间复杂度O(1);
时间复杂度O(n)。倒序输出加大了常数系数。