關於快慢指針的若干應用詳解


一.問題來源

  昨晚看微博,發現於梁斌penny,他在說現在的面試制度考不出來真功夫,也就是基本功,面試題千篇一律的算法,看過會,不看就不會。期間提到了快慢指針求中位數。

  查資料時我發現,這其實是計算機系統原理里的知識點。

二.快慢指針概念

  快慢指針中的快慢指的是移動的步長,即每次向前移動速度的快慢。例如可以讓快指針每次沿鏈表向前移動2,慢指針每次向前移動1次。

三.快慢指針的應用

3.1 判斷單鏈表是否為循環鏈表

  對於初學者來說,要解決這個問題,最可能采取的方法就是使用兩個循環。當外層循環步進一個節點時,內層循環就遍歷外層循環節點之后的所有節點,然后比較內外循環的兩個節點。若有節點地址相等,則表明該單鏈表有循環,反之則不存在循環。這種方法無疑效率比較低。

  今天給大家介紹一個經典的方法,通過快慢指針來檢查單鏈表是否存在循環。其思路很簡單,大家可以想一下上體育課長跑的情景。當同學們繞着操場跑步的時候,速度快的同學會遙遙領先,最后甚至會超越其它同學一圈乃至n圈——這是繞圈跑。那么如果不是繞圈跑呢?速度快的同學則會一直領先直到終點,不會再次碰到后面的速度慢同學——不考慮地球是圓的這種情況。

  快慢指針的設計思想也是這樣。快指針每次步進多個節點——這個視情況而定,慢指針每次只步進一個節點。那么如果該鏈表存在循環的話,快指針一定會再次碰到慢指針,反之則不存在循環。

讓快慢指針從鏈表頭開始遍歷,快指針向前移動兩個位置,慢指針向前移動一個位置;如果快指針到達NULL,說明鏈表以NULL為結尾,不是循環鏈表。如果 快指針追上慢指針,則表示出現了循環。

int isExitsLoop(LinkList L) {
    LinkList fast, slow;
    fast = slow = L;
	while (fast && fast->next)
    {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast)
        {
            break;
        }
    }
    return ((fast == NULL) || (fast->next == NULL));
}

  注:不一定直接是一個環,可能說先共同走一段路,在尾部形成環。如果是第一種情況(長度為5,從1開始),看分解如下表。

1 3 5 2 4 1
1 2 3 4 5 1

 

3.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.3 如果鏈表為存在環,如果找到環的入口點?

  有一個單鏈表,其中可能有一個環,也就是某個節點的next指向的是鏈表中在它之前的節點,這樣在鏈表的尾部形成一環。
  那么問題來了,如何判斷一個鏈表是不是這類鏈表?如果鏈表為存在環,如果找到環的入口點?  當fast若與slow相遇時,slow肯定沒有走遍歷完鏈表(不是一整個環,有開頭部分,如上圖)或者恰好遍歷一圈(未做驗證,看我的表格例子,在1處相遇)。於是我們從鏈表頭、與相遇點分別設一個指針,每次各走一步,兩個指針必定相遇,且相遇第一點為環入口點(慢指針走了n步,第一次相遇在c點,對慢指針來說n=s+p,也就是說如果慢指針從c點再走n步,又會到c點,那么順時針的CB距離是n-p=s,但是我們不知道s是幾,那么當快指針此時在A點一步一步走,當快慢指針相遇時,相遇點恰好是圓環七點B(AB=CB=s))。

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;
    while (slow != fast) {
        slow = slow->next;
        fast = fast->next;
    }
    return slow;
}

3.4 擴展問題

  判斷兩個單鏈表是否相交,如果相交,給出相交的第一個點(兩個鏈表都不存在環)。

  比較好的方法有兩個:

  1.將其中一個鏈表首尾相連,檢測另外一個鏈表是否存在環,如果存在,則兩個鏈表相交,而檢測出來的依賴環入口即為相交的第一個點。
  2.如果兩個鏈表相交,那個兩個鏈表從相交點到鏈表結束都是相同的節點,我們可以先遍歷一個鏈表,直到尾部,再遍歷另外一個鏈表,如果也可以走到同樣的結尾點,則兩個鏈表相交。
這時我們記下兩個鏈表length,再遍歷一次,長鏈表節點先出發前進(lengthMax-lengthMin)步,之后兩個鏈表同時前進,每次一步,相遇的第一點即為兩個鏈表相交的第一個點。

四.參考文獻及結束語

4.1 問題1

  相差多少?才能保證相遇?如果從物理學的角度理解(s-t曲線),肯定相遇,那么問題是如何盡快相遇(也就是快慢指針相差幾倍才能使A點盡可能靠近源點,也就是說相遇盡可能早,這樣復雜度就低了)?看下圖,自己研究吧,筆者未做詳細探索。

 

4.2 問題2

  s=t,s=2t和s=3t,s=6t的效果一樣嗎?

4.3 感想

  指針可以相差倍數,那也可以相差固定位數啦?比如求鏈表的倒數第n位.

4.4 參考文獻

  http://anyhu.blog.sohu.com/184515249.html
  http://www.nowamagic.net/librarys/veda/detail/1842


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM