lintcode 判斷一個單鏈表是否有環及環的鏈接點

今天又一次做了這個參見的題目,不過是在不想寫東西了,隨手轉載一篇



給定一個單鏈表,只給出頭指針h:

1、如何判斷是否存在環?

2、如何知道環的長度?

3、如何找出環的連接點在哪裏?

4、帶環鏈表的長度是多少?

 

解法:

1、對於問題1,使用追趕的方法,設定兩個指針slow、fast,從頭指針開始,每次分別前進1步、2步。如存在環,則兩者相遇;如不存在環,fast遇到NULL退出。

2、對於問題2,記錄下問題1的碰撞點p,slow、fast從該點開始,再次碰撞所走過的操作數就是環的長度s。

3、問題3:有定理:碰撞點p到連接點的距離=頭指針到連接點的距離,因此,分別從碰撞點、頭指針開始走,相遇的那個點就是連接點。(證明在後面附註)

4、問題3中已經求出連接點距離頭指針的長度,加上問題2中求出的環的長度,二者之和就是帶環單鏈表的長度

void Isloop(Llink head)
{
 if(!head||!head->next)
  return;
 Llink p,q;
 bool loop=false;
 p=q=head->next;
 while(q&&q->next)//判斷是否有環
 {
  p=p->next;
  q=q->next->next;
  if(p==q)
  {
   loop=true;
   break;
  }
 }
 if(!loop)
  cout<<"This link has not loop\n";
 else
 {
  cout<<"This link has a loop\n";
  Llink r=p;
  q=head->next;
  int nonloop=1,loopcount=1;

  //nonloop計算非環結點數,loopcount計算環上結點數
  do//計算環上的結點數
  {
   p=p->next;
   ++loopcount;
  }while(p!=r);
  --loopcount;
  while(p!=q)//得到環的入口結點,同時計算得到非環的結點數
  {
   p=p->next;
   q=q->next;
   ++nonloop;
  }
  --nonloop;
  cout<<"\nStart of loop: "<<p->data<<endl;  
  cout<<"\nCount of nonloop: "<<nonloop
      <<"\nCount of loop: "<<loopcount
      <<"\nCount of Linknode: "<<nonloop+loopcount<<endl;
 }
}

  

判斷是否存在環的程序:

bool IsExitsLoop(slist *head)  
  1. {  
  2.     slist *slow = head, *fast = head;  
  3.     while ( fast && fast->next )   
  4.     {  
  5.         slow = slow->next;  
  6.         fast = fast->next->next;  
  7.         if ( slow == fast ) break;  
  8.     }    
  9.     return !(fast == NULL || fast->next == NULL);  
  10. }  

 

尋找環連接點(入口點)的程序:

slist* FindLoopPort(slist *head)  
  1. {  
  2.     slist *slow = head, *fast = head;    
  3.     while ( fast && fast->next )   
  4.     {  
  5.         slow = slow->next;  
  6.         fast = fast->next->next;  
  7.         if ( slow == fast ) break;  
  8.     }    
  9.     if (fast == NULL || fast->next == NULL)  
  10.         return NULL;  
  11.     slow = head;  
  12.     while (slow != fast)  
  13.     {  
  14.          slow = slow->next;  
  15.          fast = fast->next;  
  16.     }  
  17.     return slow;  

亦可以用類似與hash表的方法,即設立一個數組,將鏈表結點中的值做數組下標,當賦值衝突時就是環的接入點

  1.  bool isloop(Llink p)
    {
     if(!p||!p->next)
      return true;
     int a[MAXSIZE],n=0;
     memset(a,0,sizeof(int)*MAXSIZE);
     p=p->next;
     while(p)
     {
      if(a[p->data]==-1)//存在環時,會發生衝突
      {
       cout<<"\nLoop node: "<<p->data<<endl
        <<"\nLen of node: "<<n<<endl;
       return true;
      }
      a[p->data]=-1;
      ++n;
      p=p->next;
     }
     return false;
    }
    Llink CreatlinkLoop()
  2. //創建一個有環的鏈表
    {
     Llink head=new Lnode;
     //head->data=0;
     head->next=NULL;
     Lelemtype e;
     Llink q=head;
     int N=0;
     cout<<"input elems:";
     while(cin>>e)
     {
      Llink p=new Lnode;
      ++N;
      p->data=e;
      p->next=q->next;
      q->next=p;
      q=p;
     }
     cin.clear();
     cin.sync();
     srand(time(0));
     q->next=Findnode(head,rand()%N);//隨機產生環的接入點
     return head;
    }
    Llink Findnode(Llink head,int n)//找出鏈表中的第n個結點
    {
     if(n<=0)
      return head;
     Llink p=head->next;
     for(int i=1;p&&i<n;++i)
      p=p->next;
     return p;
    }

////////////////////////////////////////////////////////

附註

問題2的證明如下:

鏈表形狀類似數字 6 。
假設甩尾(在環外)長度爲 a(結點個數),環內長度爲 b 。
則總長度(也是總結點數)爲 a+b 。
從頭開始,0 base 編號。
將第 i 步訪問的結點用 S(i) 表示。i = 0, 1 ...
當 i<a 時,S(i)=i ;
當 i≥a 時,S(i)=a+(i-a)%b 。

分析追趕過程:
兩個指針分別前進,假定經過 x 步後,碰撞。則有:S(x)=S(2x)
由環的週期性有:2x=tb+x 。得到 x=tb 。
另,碰撞時,必須在環內,不可能在甩尾段,有 x>=a 。

連接點爲從起點走 a 步,即 S(a)。
S(a) = S(tb+a) = S(x+a)。
得到結論:從碰撞點 x 前進 a 步即爲連接點。

根據假設易知 S(a-1) 在甩尾段,S(a) 在環上,而 S(x+a) 必然在環上。所以可以發生碰撞。
而,同爲前進 a 步,同爲連接點,所以必然發生碰撞。

綜上,從 x 點和從起點同步前進,第一個碰撞點就是連接點。

/////////////////////////////////////////////////////////////

假設單鏈表的總長度爲L,頭結點到環入口的距離爲a,環入口到快慢指針相遇的結點距離爲x,環的長度爲r,慢指針總共走了s步,則快指針走了2s步。另外,快指針要追上慢指針的話快指針至少要在環裏面轉了一圈多(假設轉了n圈加x的距離),得到以下關係:
    s = a + x;
    2s = a + nr + x;
    =>a + x = nr;
    =>a = nr - x;
    由上式可知:若在頭結點和相遇結點分別設一指針,同步(單步)前進,則最後一定相遇在環入口結點,搞掂!
附圖:

帶環的鏈表
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章