Morris神级遍历二叉树,时间复杂度为O(1)

来源:互联网 发布:mac迅雷下载 编辑:程序博客网 时间:2024/05/21 01:31

Morris算法介绍

Morris算法在遍历的时候避免使用了栈结构,而是让下层到上层有指针,具体是通过底层节点指向NULL的空闲指针返回上层的某个节点,从而完成下层到上层的移动。我们知道二叉树有很多空闲的指针,比如某个人节点没有右孩子,我们称这种情况为空闲状态,Morris算法的遍历就是利用了这些 空闲的指针!

Morris算法算法的规则:
当我拿到一个节点(node)的时候看它有没有左子树,没有的话向右指针方向移动
有的话找到左子树的最右节点,如果这个最右节点的右指针为空则让它指向node,然后node向左指针移动
,如果这个最右节点已经指向node,则让它指向空则让node向右指针移动

我们就拿一个普通的二叉树来说吧:
这里写图片描述

描述:我们从根节点开始,node节点就为1,1有左子树,找到1左子树的最右节点为5,5的右指针为空,让5的右指针指向node即1,然后node向左指针移动,变为2,2也有左子树,左子树的最右节点为4,4的右指针为空则指向2,node向左移动变为4,node为4没有左子树,所以node向右指针方向移动回到2(这是第二次来到2),2有左子树,并且它左子树的最右节点已经指向了node(即2),则让这个最右节点指向NULL,node向右指针移动来到5,5没有左子树,向右指针移动来到1,1有左子树,切左子树的最右节点指向了node(即自己),让最右节点指向NULL,node向右指针移动来到3,3有子树,左子树的最右节点右指针为空所以让他指向node(即3),node向左指针移动来到7,7没有左子树,向右指针移动回到3(第二次来到3),3有左子树切左子树的最右节点指向自己,则让左子树的最右节点指向NULL,node往右指针走,来到8,此树遍历完毕。

我们可以看到节点2,1,3在node走的过程中会两次来到它,而其他节点node只会来到一次!
归纳为:如果一个节点有左子树node就会访问它两次,如果没有左子树,node只会访问它一次。

这样我们就可以开始做前、中序遍历了:
前序遍历:
一个节点没有左子树直接打印当前节点
如果有的话(第一次来到此节点的时候就去打印),即再往左子树节点走之前打印
code

struct Node{    int value;    Node* left;    Node* right;    Node(int data)    {        value = data;    }};//Morris前序遍历void MorrisPre(Node* root){    if(root == NULL)        return;    Node* cur1 = root;    Node* cur2 = NULLwhile(cur1 != NULL)    {        cur2 = cur1->left;        if(cur2!=NULL)        {            while(cur2->right!=NULL && cur2->right!=cur1)            {                cur = cur->right;            }            if(cur2->right == NULL)            {                cur2->right = cur1;                cout<<cur1->value<<" ";//在node走向左子树之前打印                cur1 = cur1->left;                continue;            }else{                cur2->right = NULL;//如果cur2->right指向node,则置空            }        }else{//cur2为空,直接打印当前节点 cur1往右指针走            cout<<cur1->value<<" ";        }        cur1 = cur->right;    }    cout<<endl;}

中序遍历
在没有左子树的时候直接往右子树走之前打印
有左子树先去遍历左子树然后回来当前节点打印,之后再去右子树(都是往右指针走之前打印他)
code

//morris中序遍历void  MorrisIn(Node* root){    if(root == NULL)     return;    Node* cur1 = root;    Node* cur2 = NULL;    while(cur1 !=NULL)    {        cur2 = cur1->left;        if(cur2!=NULL)        {            while(cur2->right!=NULL && cur2->right!=cur1)            {                cur2 = cur2->right;            }            if(cur2->right == NULL)            {                cur2->right = cur1;                cur1 = cur1->left;                continue;            }else{                cur2->right = NULL;            }        }        cout<<cur1->value<<" ";        cur1 = cur1->right;    }    cout<<endl;}

后序遍历
后序遍历就有点麻烦了,我们仔细观察前面的前中序,node最多可以访问一个节点两次,前序是第一次访问就打印当前节点,中序是第二次访问打印当前节点,那么后序需要第三次访问的时候打印当前节点,但是一个节点最多被访问两次,那怎么办呢?
下来我们引入一个方法:
这里写图片描述
图中红线画出来的称为这棵二叉树的所有右边界,所有的右边界节点加起来就是二叉树的节点个数N,每个节点最多可能被遍历2次,遍历整体二叉树的代价就是2N

后序的方法就是:
第二次来到到此节点,逆序打印它的右边界就是后序,整个走完之后(遍历到二叉树最右节点)后单独逆序打印整棵树的右边界。
那怎么逆序打印呢?类似于单链表的逆置,更改右节点的指针指向即可,在打印完在更改回去,(如1->right = 3,3->right = 8,可以设置为 8->prev = 3,3->pre = 1)
code

/morris后序遍历void PrintEdge(Node* from);void  MorrisPos(Node* root){    if(root == NULL)    {        return;    }    Node* cur1 = root;    NOde* cur2 = NULL;    while(cur1 != NULL)    {        cur2 = cur1->left;        if(cur2 != NULL)        {            while(cur2->right != NULL && cur2->right != cur1)            {                cur2 = cur2->right;            }            if(cur2->right == NULL)            {                cur2->right = cur1;                cur1 = cur1->left;                continue;            }else{                cur2->right = NULL;                PrintEdge(cur->left);            }        }        cur1 = cur1->right;    }    PrintEdge(root);    cout<<endl;}void ReverseEdge(Node* from);void PrintEdge(Node* root){    Node* tail = ReverseEdge(root);    Node* cur = tail;    while(cur != NULL)    {        cout<<cur->value<<" ";        cur = cur->right;    }    ReverseEdge(tail);}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;}
阅读全文
0 0
原创粉丝点击