今天又一次做了這個參見的題目,不過是在不想寫東西了,隨手轉載一篇
給定一個單鏈表,只給出頭指針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;
由上式可知:若在頭結點和相遇結點分別設一指針,同步(單步)前進,則最後一定相遇在環入口結點,搞掂!
附圖: