《算法之美》の链表问题の判断链表循环与否

问题:

一个链表要么以NULL结尾(非循环的),要么以循环结尾(循环的),请编写一个函数,接受链表的头指针作为参数,确定该链表是循环的还是非循环的。如果链表是循环的,函数返回true,如果是非循环的,函数返回false。注意,不能以任何方式修改链表。

 

解答:

这两种链表的区别在与它们的末尾。在非循环链表中,末尾节点是以NULL结束的,因此只要遍历链表,直到找到一个以NULL结尾的节点就行;但在非循环链表中,仅仅遍历链表,就会陷入死循环中。

 

所以我们先研究一下末尾节点。对于循环链表中末尾节点指向的节点,还有另一个指针(头指针)指向它。这意味着有两个指针指向了同一个节点,这个节点是唯一一个有两个元素指向的节点。根据这一特征,我们可以遍历该链表,检查每个节点,确定是否有两个节点指向它,如果有,则该链表肯定是循环链表,否则,该链表是非循环的,则最终会遇到一个NULL指针。不幸的是,我们很难检查指向每一个元素的节点数。

 

除了检查是否有两个指针指向同一节点之外,我们还可以检查是否曾经遇到过某个节点。如果发现一个曾经遇到过的节点,就表明这是一个循环的链表;如果遇到NULL指针,就表明这是一个非循环链表。现在关键是怎样判断是否遇到过某个节点呢,最简单的就是遍历每个元素时作标记,但题目不允许我们修改该链表。

 

那么我们可以利用链表已有的东西,因为我们知道链表的当前节点和链表的头指针,因此可以将当前节点的next指针与前面所有的节点直接进行比较。如果对于第i个节点,比较它的next指针,看是否指向了1i-1号节点之中的某一个。如果出现相等的情况,说明这是一个循环链表。但这个算法的时间复杂度是On2)。

 

我们有一种使用两个指针的更好的算法,交替移动两个指针,且两个指针的移动速度不一样。在非循环链表中,较快的指针会先到达末尾;在循环链表中,两个指针会陷入无限循环,较快的指针最终会赶上并超过较慢的指针。如果较快的指针最终超过了较慢的指针,说明这是一个循环链表。如果它遇到一个NULL指针,说明这是一个非循环链表。

 

算法的实现代码如下:

#include <iostream>

 

struct ListNode

{

    int m_nKey;

    ListNode* m_pNext;      

};

 

void InitList(ListNode** pList)

{

    *pList = (ListNode*)malloc(sizeof(ListNode));

    (*pList)->m_pNext = NULL;

}

 

void InsertList(ListNode* pList, int data)

{

     ListNode* pNewNode = (ListNode*)malloc(sizeof(ListNode));

     pNewNode->m_nKey = data;

     pNewNode->m_pNext = pList->m_pNext;

     pList->m_pNext = pNewNode;

}

 

bool determinTermination(ListNode *head)

{

     if(head == NULL)

         return false;

     ListNode *fast, *slow;

     slow = fast = head;

    

     while(true)//排除fast->m_pNext->m_pNext不存在的情况

     {

         if(fast == NULL || fast->m_pNext == NULL)

             return false;  //非循环的

          

         slow = slow->m_pNext;

         fast = fast->m_pNext->m_pNext;

        

         if(fast == slow || fast->m_pNext == slow)

             return true;     //循环的

     }

}

 

int main()

{

    ListNode* pListHead = NULL;

    InitList(&pListHead);

    for(int i=9; i>=0; i--)

    {

        InsertList(pListHead, i);           

    }

   

    if(determinTermination(pListHead) == true)

        std::cout<<"1)单向链表是循环的"<<std::endl;

    else

        std::cout<<"1)单向链表是非循环的"<<std::endl;

       

    ListNode* pTmp = pListHead;

    while(pTmp->m_pNext != NULL)

    {

        pTmp = pTmp->m_pNext;                   

    }

    pTmp->m_pNext = pListHead->m_pNext; //连接成循环链表

   

    if(determinTermination(pListHead) == true)

        std::cout<<"2)单向链表是循环的"<<std::endl;

    else

        std::cout<<"2)单向链表是非循环的"<<std::endl;

   

    system("pause");

    return 0;

}

 

 

复杂度分析:

如果链表是非循环的,较快的指针会在检查了n个节点之后到达链表的末尾,而较慢的指针只遍历了1/2的节点。因此总共遍历了3/2的节点,时间复杂度是On)。

 

如果链表是循环的,较慢的指针永远也不会遍历超过一次。当较慢的指针检查了n个节点时,较快的指针已经检查了2n个节点,并超过了较慢的指针。最坏情况下,需要检查3n个节点,时间复杂度是On)。

 

因此,不论链表是循环的还是非循环的,这种两个指针的方法都比一个指针的方法好很多。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值