快慢指針
快慢指針中的快慢指的是移動的步長,即每次向前移動速度的快慢。例如可以讓快指針每次沿鏈表向前移動2,慢指針每次向前移動1次。
快慢指針的應用
(1)判斷單鏈表是否存在環
如果鏈表存在環,就好像操場的跑道是一個環形一樣。此時讓快慢指針都從鏈表頭開始遍歷,快指針每次向前移動兩個位置,慢指針每次向前移動一個位置;如果快指針到達NULL,說明鏈表以NULL爲結尾,沒有環。如果快指針追上慢指針,則表示有環。代碼如下:
bool HasCircle(Node *head)
{
if(head == NULL)
return false;
Node *slow = head, *fast = head;
while(fast != NULL && fast->next!=NULL)
{
slow = slow->next; //慢指針每次前進一步
fast = fast->next->next;//快指針每次前進兩步
if(slow == fast) //相遇,存在環
return true;
}
return false;
}
(2)在有序鏈表中尋找中位數
快指針的移動速度是慢指針移動速度的2倍,因此當快指針到達鏈表尾時,慢指針到達中點。
程序還要考慮鏈表結點個數的奇偶數因素,當快指針移動x次後到達表尾(1+2x),說明鏈表有奇數個結點,直接返回慢指針指向的數據即可。
如果快指針是倒數第二個結點,說明鏈表結點個數是偶數,這時可以根據“規則”返回上中位數或下中位數或(上中位數+下中位數)的一半。
while (fast && slow)
{
if (fast->next==NULL)
return slow ->data;
else if (fast->next!= NULL && fast->next->next== NULL)
return (slow ->data + slow ->next->data)/2;
else
{
fast= fast->next;
fast= fast->next;
slow = slow ->next;
}
}
(3) 輸出鏈表中的倒數第K個節點(即正數第K-1個節點)
可以定義兩個指針,第一個指針從鏈表的頭指針開始遍歷向前走k-1步,第二個指針保持不動;從第K步開始,第二個指針也開始從鏈表的頭指針開始遍歷。由於兩個指針的距離保持在k-1,當第一個指針到達鏈表的尾節點時候,第二個指針正好是倒數第K個節點,代碼如下:
// 查找單鏈表中倒數第K個結點
ListNode * RGetKthNode(ListNode * pHead, unsigned int k) // 函數名前面的R代表反向
{
if(k == 0 || pHead == NULL) // 這裏k的計數是從1開始的,若k爲0或鏈表爲空返回NULL
return NULL;
ListNode * pAhead = pHead;
ListNode * pBehind = pHead;
for(int i=0;i<k-1;i++){
pAhead=pAhead->next;
if(pAhead==null) return null;//當鏈表長度小於k時候,返回Null
}
while(pAhead->next != NULL) // 前後兩個指針一起向前走,直到前面的指針指向最後一個結點
{
pBehind = pBehind->next;
pAhead = pAhead->next;
}
return pBehind; // 後面的指針所指結點就是倒數第k個結點
}
(4) 判斷鏈表是否存在環,如果存在,找到環入口
有一個單鏈表,其中可能有一個環,也就是某個節點的next指向的是鏈表中在它之前的節點,這樣在鏈表的尾部形成一環。
如何判斷一個鏈表是否存在環?設定兩個指針slow,fast,均從頭指針開始,每次分別前進1步、2步。如存在環,則兩者相遇;如不存在環,fast遇到NULL退出。
如果鏈表存在環,如果找到環的入口點?當fast若與slow相遇時,slow肯定沒有走遍歷完鏈表或者恰好遍歷一圈。於是我們從鏈表頭與相遇點分別設一個指針,每次各走一步,兩個指針必定相遇,且相遇第一點爲環入口點。
node* findLoopPort(node *head) {
node *fast, *slow;
fast = slow = 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; //讓slow回到鏈表的起點,fast留在相遇點
while (slow != fast) { //當slow和fast再次相遇時,那個點就是環的入口點
slow = slow->next;
fast = fast->next;
}
return slow;
}
(5) 判斷兩個單鏈表是否相交,如果相交,找到他們的第一個公共節點
判斷兩個單鏈表是否相交,如果相交,給出相交的第一個點(假設兩個鏈表都不存在環)。
思路:
首先利用快慢指針判斷鏈表是否存在環。
(1)如果都不存在環,則如果兩個單向鏈表有公共節點,也就是兩個鏈表從某一節點開始,他們的p_next都指向同一個節點,每個節點只有一個p->next。因此從第一個公共節點開始,之後它們所有節點都是重合的。因此,首先兩個鏈表各遍歷一次,求出兩個鏈表的長度L1、L2,然後可以得到它們的長度差L。然後現在長的鏈表上遍歷L個節點,之後再同步遍歷,於是在遍歷中,第一個相同的節點就是第一個公共的節點。此時,若兩個鏈表長度分別爲M,N,則時間複雜度爲O(M+N).
(2)如果一個存在環,另外一個不存在環,則這兩個鏈表是不可能相交的。
(3)如果利用快慢指針發現兩個鏈表都存在環,則判斷任意一個鏈表上快慢指針相遇的那個節點,在不在另外一個鏈表上,如果在,則相交,不在,則不相交。
下面討論兩個沒有環的鏈表如果是相交於某一結點的情況:
相交的鏈表示意圖如下所示:
方法一:
兩個沒有環的鏈表如果是相交於某一結點,如上圖所示,這個結點後面都是共有的。所以如果兩個鏈表相交,那麼兩個鏈表的尾結點的地址也是一樣的。程序實現時分別遍歷兩個單鏈表,直到尾結點。判斷尾結點地址是否相等即可。時間複雜度爲O(L1+L2)。
如何找到第一個相交結點?判斷是否相交的時候,記錄下兩個鏈表的長度,算出長度差len,接着先讓較長的鏈表遍歷len個長度,然後兩個鏈表同時遍歷,判斷是否相等,如果相等,就是第一個相交的結點。
void Is_2List_Intersect(LinkList L1, LinkList L2) {
if (L1 == NULL || L2 == NULL) {
exit(ERROR);
}
LinkList p = L1;
LinkList q = L2;
int L1_length = 0;
int L2_length = 0;
int len = 0;
while (p->next) {
L1_length ++;
p = p->next;
}
while (q->next) {
L2_length ++;
q = q->next;
}
printf("p: = %d\n", p);
printf("q: = %d\n", q);
printf("L1_length: = %d\n", L1_length);
printf("L2_length: = %d\n", L2_length);
if (p == q) {
printf(" 相交\n");
/*p重新指向短的鏈表 q指向長鏈表*/
if (L1_length > L2_length) {
len = L1_length - L2_length;
p = L2;
q = L1;
}
else {
len = L2_length - L1_length;
p = L1;
q = L2;
}
while (len) {
q = q->next;
len--;
}
while (p != q) {
p = p->next;
q = q->next;
}
printf("相交的第一個結點是:%d\n", p->data );
}
else {
printf("不相交 \n");
}
}
方法二:
另外一個方法則是將一個鏈表首尾相接,然後判斷另外一個鏈表是否有環,如果有環,則兩個鏈表相交。那麼求第一個交點則求出有環的的那個鏈表的環結點即是。
int Is_ListLoop(LinkList L) {
LinkList fast, slow;
if (L == NULL || L->next == NULL) {
exit(ERROR);
}
fast = slow = L;
while (fast->next != NULL && fast->next->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if (fast == slow) {
return True;
}
}
return False;
}
int Find_Loop(LinkList L) {
LinkList fast, slow;
if (L == NULL || L->next == NULL) {
exit(ERROR);
}
fast = slow = L;
while (fast->next != NULL && fast->next->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if (fast == slow) {
break;
}
}
slow = L;
while (fast != slow) {
slow = slow->next;
fast = fast->next;
}
printf("%d\n", slow->data );
return TRUE;
}
void Is_2List_Intersect2(LinkList L1, LinkList L2) {
if (L1 == NULL || L2 == NULL) {
exit(ERROR);
}
LinkList p = L1;
LinkList q = L2;
while (p->next) {
p = p->next;
}
p->next = L1->next;
if(Is_ListLoop(L2)){
printf("相交\n");
printf("相交的第一個結點是:");
Find_Loop(L2);
}
else{
printf("不相交\n");
}
}
關於尋找單鏈表循環入口節點的問題具體可以參考博客: