天天看點

快慢指針應用總結快慢指針

快慢指針

    快慢指針中的快慢指的是移動的步長,即每次向前移動速度的快慢。例如可以讓快指針每次沿連結清單向前移動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/a1091220321/article/details/47706711

https://www.cnblogs.com/hxsyl/p/4395794.html

https://blog.csdn.net/zwhlxl/article/details/45745825

繼續閱讀