問題轉自: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;
}
}
判斷是否存在環的程序:
- {
- slist *slow = head, *fast = head;
- while ( fast && fast->next )
- {
- slow = slow->next;
- fast = fast->next->next;
- if ( slow == fast ) break;
- }
- return !(fast == NULL || fast->next == NULL);
- }
尋找環連接點(入口點)的程序:
- {
- slist *slow = head, *fast = head;
- while ( fast && fast->next )
- {
- slow = slow->next;
- fast = fast->next->next;
- if ( slow == fast ) break;
- }
- if (fast == NULL || fast->next == NULL)
- return NULL;
- slow = head;
- while (slow != fast)
- {
- slow = slow->next;
- fast = fast->next;
- }
- return slow;
- }
亦可以用類似與hash表的方法,即設立一個數組,將鏈表結點中的值做數組下標,當賦值衝突時就是環的接入點
-
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() -
//創建一個有環的鏈表
{
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;
由上式可知:若在頭結點和相遇結點分別設一指針,同步(單步)前進,則最後一定相遇在環入口結點,搞掂!
附圖:
設置兩個指針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