分享

有环链表问题及其证明

 孤步 2012-07-31

有环链表问题及其证明 (2011-06-16 10:50:27)转载▼

标签: 杂谈      分类: 算法学习

三种解决方法:

 

第一种方法,将所有的遍历过的节点用某个结构存储起来,然后每遍历一个节点,都在这个结构中查找是否遍历过,如果找到有重复,则说明该链表存在循环;如果直到遍历结束,则说明链表不存在循环。

这个结构我们可以使用hash来做,hash中存储的值为节点的内存地址,这样查找的操作所需时间为O(1),遍历操作需要O(n)hash表的存储空间需要额外的O(n)。所以整个算法的时间复杂度为O(n),空间复杂度为O(n)

 

第二种方法,比较的特别,是使用反转指针的方法,每过一个节点就把该节点的指针反向。

当有环的时候,最后指针会定位到链表的头部,如果到最后,都没有再到头部,那说明链表不存在循环。

这个方法会破坏掉链表,所以如果要求是不能破坏链表的话,我们最后就还需要反转一下,再将链表恢复。

这个方法使用的空间复杂度为O(1),其实是使用了3个指针,用于进行反转。同时,时间复杂度为O(n)

 

第三种方法,可能大家已经知道了,同时也是面试官大多想要得到的答案,就是快慢指针。

快指针pf(f就是fast的缩写)每次移动2个节点,慢指针ps(sslow的缩写)每次移动1个节点,如果快指针能够追上慢指针,那就说明其中有一个环,否则不存在环。

这个方法的时间复杂度为O(n),空间复杂度为O(1),实际使用两个指针。

 

快慢指针判别是否有环(分别为指针lowfast)证明过程:

   假设该链表在环出现之前有L个结点,环中有C个结点。

   再假设慢指针初始化时指向的位置为a、快指针初始化时指向的位置为b

   如果二者在t 次移动后相遇,也就是说:(a+t-L) mod C == (b+2*t-L) mod C,

   所以无论ab的起始位置如何,二者总是会相遇的。

   推导:(a+t-L) mod C == (b+2*t-L) mod C

a+t-L-m*C == b+2*t-L -n*C

t == (a-b) - (m-n)*C

   a==b ==> t == (n-m)*C;在保证mn为正整数的情况下上式t有正整数解。

   若两个指针的步长分别为:xy(即每走一步步长分别为:x*ty*t

   (x-y)*t == (a-b) - m-n)*C

   要是程序不陷入死循环必须满足上面这个等式有正整数解。

   比方说:假设链表是由4个结点首尾相接构成的一个圆圈(编号为0~3

   慢指针初始位置在0,每次前进1步;

   快指针初始位置在3,每次前进3步;

   有:(3-1*t == (3-0) - m-n*4

   此等式没有正整数解,所以虽然这两个指针也是一快一慢一前一后,但它们永远也不会相遇!

 

 

  寻找环的入口。

   x=1y=2a=b=0的情况下,让fast回到链表的头部,重新走,每次步长走1

   那么当lowfast再次相遇的时候,就是环路的入口了。

   t = (n-m)*C

   假设再走t1步:(t+t1-L) mod C == 0 ==> t1 == L

 

测试代码:

 

#include <iostream>

using namespace std;

 

typedef struct Node

{

    int num;

    Node* next;

}SNode;

 

//带头节点

void creatList(SNode** s, int len, int c)

{

    int pos = 0;

    int l = len;

    SNode *p, *q;

    *s = new SNode;

    (*s)->num = -1;

    (*s)->next = NULL;

    q = *s;

    while(l--)

    {

        p = new SNode;

        p->num = pos++;

        p->next = NULL;

 

        q->next = p;

        q = q->next;

    }

 

    if( c >= 0 && c < len )

    {

        q = *s;

        while( c >= 0 )

        {

            --c;

            q = q->next;

        }

        p->next = q;

    }

}

 

//不带头节点

void creatList1(SNode** s, int len, int c)

{

    int pos = 0;

    int l = len;

    SNode *p, *q;

    *s = new SNode;

    (*s)->num = pos++;

    (*s)->next = NULL;

    --l;

    q = *s;

    while(l--)

    {

        p = new SNode;

        p->num = pos++;

        p->next = NULL;

 

        q->next = p;

        q = q->next;

    }

    if( c >= 0 || c < len )

    {

        q = *s;

        while( c-- )

        {

            q = q->next;

        }

        p->next = q;

    }

}

 

void printList(SNode *s, int len)

{

    SNode *p = s;

    while(p && len--)

    {

        cout << p->num << " ";

        p = p->next;

    }

    cout << endl;

}

 

SNode* checkList(SNode *s)

{

    bool flag = false;

    SNode *low, *fast;

    low = s;

    fast = s;

    while( low != NULL && fast != NULL )

    {

        low = low->next;

        fast = fast->next->next;

        if( low == fast )

        {

            flag = true;

            break;

        }

    }

 

    if( flag )

    {

        fast = s;

        while( low != fast )

        {

            low = low->next;

            fast = fast->next;

        }

        return low;

    }

    return NULL;

}

 

int main()

{

    SNode *s;

    SNode *p;

    creatList1(&s, 20, 13);

    printList(s, 25);

    p = checkList(s);

    cout << p->num << endl;

    return 0;

}

 

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多