快慢指針應用總結

原文鏈接:https://blog.csdn.net/qq_21815981/article/details/79833976

快慢指針
    快慢指針中的快慢指的是移動的步長,即每次向前移動速度的快慢。例如可以讓快指針每次沿鏈表向前移動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");
    }
}

關於尋找單鏈表循環入口節點的問題具體可以參考博客:

https://blog.csdn.net/puss0/article/details/78462375

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