判斷兩個鏈表是否相交和一個單鏈表是否有環及環的鏈接點(轉)

問題轉自:http://blog.csdn.net/v_JULY_v/article/details/6126406

編程之美3.6 編程判斷兩個鏈表是否相交 第233頁

微軟亞院之編程判斷倆個鏈表是否相交
給出倆個單向鏈表的頭指針,比如h1,h2,判斷這倆個鏈表是否相交。
爲了簡化問題,我們假設倆個鏈表均不帶環。

問題擴展:
1.如果鏈表可能有環列?
2.如果需要求出倆個鏈表相交的第一個節點列?


1.首先假定鏈表不帶環

那麼,我們只要判斷倆個鏈表的尾指針是否相等。
相等,則鏈表相交;否則,鏈表不相交。
2.如果鏈表帶環,
那判斷一鏈表上倆指針相遇的那個節點,在不在另一條鏈表上。
如果在,則相交,如果不在,則不相交。

所以,事實上,這個問題就轉化成了:
1.先判斷帶不帶環
2.如果都不帶環,就判斷尾節點是否相等
3.如果都帶環,判斷一鏈表上倆指針相遇的那個節點,在不在另一條鏈表上。
如果在,則相交,如果不在,則不相交。

//用兩個指針,一個指針步長爲1,一個指針步長爲2,判斷鏈表是否有環
bool check(const node* head)
{
    if(head==NULL)
      return false;
    node *low=head, *fast=head->next;
    while(fast!=NULL && fast->next!=NULL)
    {
        low=low->next;
        fast=fast->next->next;
        if(low==fast) return true;
    }
    return false;
}

//如果鏈表可能有環,則如何判斷兩個鏈表是否相交
//思路:鏈表1 步長爲1,鏈表2步長爲2 ,如果有環且相交則肯定相遇,否則不相交
list1 head: p1
list2 head: p2
while( p1 != p2 && p1 != NULL && p2 != NULL ) 
[b]//但當鏈表有環但不相交時,此處是死循環。![/b]
{
      p1 = p1->next;
      if ( p2->next )
         p2 = p2->next->next;
      else
         p2 = p2->next;
}
if ( p1 == p2 && p1 && p2)
   //相交
else 
  //不相交
所以,判斷帶環的鏈表,相不相交,只能這樣:
如果都帶環,判斷一鏈表上倆指針相遇的那個節點,在不在另一條鏈表上。
如果在,則相交,如果不在,則不相交。(未寫代碼實現,見諒。:)..
------------------

看完上面我對於兩個單鏈表的環和相交不太理解,於是找了相關博客看。下面兩個感覺都不錯。根據對下面博客的理解,我個人得出了一些關於兩個單鏈表的看法,以對上述程序的理解(如有錯誤,歡迎指正):

1. 兩個單鏈表不相交(有無環也就不重要了);

2. 兩個單鏈表相交,但都無環;

3. 兩個單鏈表相交,都有環且共用環。

   對於第三種情況,可以畫圖理解。如下:



如下兩篇相關博客:

轉自http://blog.sina.com.cn/s/blog_725dd1010100tqwp.html

給定一個單鏈表,只給出頭指針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;
    由上式可知:若在頭結點和相遇結點分別設一指針,同步(單步)前進,則最後一定相遇在環入口結點,搞掂!
附圖:

帶環的鏈表

另一篇比較容易懂的,包括證明部分也容易懂:

問題1、判斷鏈表是否存在環。 
  
  
設置兩個指針fast和slow,初始值都指向頭指針,slow每次前進一步,fast每次前進兩步。如果存在環,則fast必先進入環,而slow後進 入環,兩個指針必定相遇(見下面的證明1)。(當然,fast先到達尾部爲NULL,則爲無環鏈表)。程序如下: 
bool isExitsLoop(Node* head) 
Node *slow = head, *fast = head; 
//fast && fast->next保證了fast可以接着向下移動 
while(fast && fast->next) 
    slow = slow->next 
    fast = fast->next->next; 
    if(slow == fast) 
     break; 
return !(fast==NULL || fast->next == NULL) 
  
證明1:兩個指針fast和slow,fast一次遞增兩步,slow一次遞增一步。如果有環的話兩者必然重合,反之亦然。 
因爲fast每次走2步,而slow每次走一步,所以它們之間的差距是一步一步縮小的。當slow進入環入口點後,fast和slow之間的差距將會一步 步縮小,如4,3,2,1,0。到0的時候就重合了。 
根據此方式,可以證明,fast每次走三步以上,並不總能加快檢測速度,反而有可能判別不出環。 
    
2、若鏈表存在環,找到環的入口點 
  
如果fast和slow相遇,那麼在相遇時,slow肯定沒有遍歷完鏈表,而fast已經在環內循環了n圈(n>=1)(見下面的證明2)。假設 slow走了s步,則fast走了2s步(fast的步數還等於s加上在環上多轉的n圈),設環長爲r,則: 
2s=s+nr 
s=nr 
設整個鏈表長L,入口環與相遇點距離爲x,起點到環入口點的距離爲a,則 
a+x=s=nr 
a+x=(n-1)r+r=(n-1)r+L-a 
a=(n-1)r+(L-a-x) 
(L-a-x)爲相遇點到環入口點的距離。由此可知,從鏈表頭到環入口點等於(n-1)循環內環+相遇點到環入口點。於是可以從鏈表頭和相遇點分別設一個 指針,每次各走一步,兩個指針必定相遇,且相遇第一點爲環入口點。 
  
程序描述如下: 
Node* findLoopPort(Node *head) 
Node *slow = head, *fast=head; 
//找到相遇點 
while(fast && fast->next) 
    slow = slow->next 
    fast=fast->next->next 
    if(fast=slow) 
     break 
if(fast==NULL || fast->next==NULL) 
    return NULL 
  
//此時,fast和slow都指向相遇點 
slow=head   //slow指向頭節點 
while(slow != fast) 
    slow=slow->next 
    fast=fast->next 
return slow

發佈了28 篇原創文章 · 獲贊 7 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章