說一說程式設計師“舉一反三”的能力

cricode發表於2014-08-18

  獲得思想的能力,我們稱之為智力。解決一個問題,如果你能做到舉一反三,那麼你就真正獲得瞭解決這一類問題的思想了。

  好吧,我們一起來體會一下自己“舉一反三”的能力究竟怎麼樣。

題圖:“私想者”

 一、從快速排序說起

  快速排序可以說是應用最廣的一種排序演算法。其思想是基於分治模式,將問題不斷縮小,對小的問題進行排序,合併,後最終完成排序。

  其中,快速排序最為核心的一個步驟是劃分(partition)待排序陣列:從陣列中選擇一個元素(主元),然後以這個主元為基準,將陣列劃分成兩部分,使得前邊部分元素都小於主元,後邊部分元素都大於主元。(這裡假設為從小到大排序),更詳細的快速排序介紹見此文:快速排序

  如下圖所示為一個劃分陣列的例項,這裡選取陣列的最後一個元素作為主元。

  其虛擬碼如下:

//核心函式,對陣列A[p,r]進行就地重排,將小於A[r]的數移到陣列前半部分,將大於A[r]的數移到陣列後半部分。
PARTITION(A,p,r)
    pivot <—— A[r]
    i <—— p-1
    for j <—— p to r-1
        do if A[j] < pivot
            i <—— i+1
            exchange A[i]<——>A[j]
    exchange A[i+1]<——>A[r]
return i+1

 二、如果你理解了快速排序演算法思想

  假設你知道並理解了快速排序演算法,我們接下來解決如下一個問題。

  問題描述:

  輸入一個整數陣列,調整陣列中數字的順序,使得所有奇數位於陣列的前半部分,所有偶數位於陣列的後半部分。要求時間複雜度為O(n)。

  分析與解答:

  最容易想到的辦法是從頭掃描這個陣列,每碰到一個偶數,拿出這個數字,並把位於這個數字後面的所有數字往前挪動一位。挪完之後在陣列的末尾有一個空位,然 後把該偶數放入這個空位。由於每碰到一個偶數,需要移動O(n)個數字,所以這種方法總的時間複雜度是O(n^2),不符合題目要求。

  事實上,若把奇數看做是小的數,偶數看做是大的數,那麼按照題目所要求的奇數放在前面偶數放在後面,就相當於小數放在前面大數放在後面,聯想到快速排序中 的partition過程,不就是通過一個主元把整個陣列分成大小兩個部分麼,小於主元的小數放在前面,大於主元的大數放在後面。

  而partition過程有以下兩種實現:

  • 一頭一尾兩個指標往中間掃描,如果頭指標遇到的數比主元大且尾指標遇到的數比主元小,則交換頭尾指標所分別指向的數字;
  • 一前一後兩個指標同時從左往右掃,如果前指標遇到的數比主元小,則後指標右移一位,然後交換各自所指向的數字。

  類似這個partition過程,奇偶排序問題也可以分別借鑑partition的兩種實現解決。

  為何?比如partition的實現一中,如果最終是為了讓整個序列元素從小到大排序,那麼頭指標理應指向的就是小數,而尾指標理應指向的就是大數,故當頭指標指的是大數且尾指標指的是小數的時候就不正常,此時就當交換。

  因此,我們的做法是維護兩個指標i和j,一個指標指向陣列的第一個數的前一個位置,我們稱之為後指標i,向右移動;一個指標指向陣列第一個數,稱之為前指標j,也向右移動,且前指標j先向右移動。如果前指標j指向的數字是奇數,則令i指標向右移動一位,然後交換i和j指標所各自指向的數字。

  如果你能很快想到類似快排partition的解決方案,說明你理解了快速排序。

 三、舉一反三——是金子,你就應該發光

  奇偶調序問題我們順利用partition思想在O(n)的複雜度下解決了戰鬥,恩,我們獲取思想的能力還是不錯的。接下來,我們再來一瓶:荷蘭國旗問題。

  拿破崙席捲歐洲大陸之後,代表自由,平等,博愛的豎色三色旗也風靡一時。荷蘭國旗就是一面三色旗(只不過是橫向的),自上而下為紅白藍三色。

  該問題本身是關於三色球排序和分類的,由荷蘭科學家Dijkstra提出。由於問題中的三色小球有序排列後正好分為三類,Dijkstra就想象成他母國的國旗,於是問題也就被命名為荷蘭旗問題(Dutch National Flag Problem)。

  問題的正規描述:

  有n個紅白藍三種不同顏色的小球,亂序排列在一起,請通過兩兩交換任意兩個球,使得從左至右,依次是一些紅球、一些白球、一些藍球。

  分析與解法:

  首先,你看到在問題,可能會無從下手。但如果給你提示,讓你儘量往快排思想上靠攏呢?

  我們知道,快速排序依託於一個partition分治過程,在每一趟排序的過程中,選取的主元都會把整個陣列排列成一大一小的部分,那我們是否可以借鑑partition過程設定三個指標完成重新排列,使得所有球排列成三個不同顏色的球呢?

  由此,我們得到此問題的一個解決方法:

  通過前面的分析得知,這個問題類似快排中partition過程,只是需要用到三個指標:一個前指標begin,一箇中指標current,一個後指標end,current指標遍歷整個陣列序列,當

  1. current指標所指元素為0時,與begin指標所指的元素交換,而後current++,begin++ ;
  2. current指標所指元素為1時,不做任何交換(即球不動),而後current++ ;
  3. current指標所指元素為2時,與end指標所指的元素交換,而後,current指標不動,end– 。

  為什麼上述第3點中,current指標所指元素為2時,與end指標所指元素交換之後,current指標不能動呢?因為第三步中current指標所 指元素與end指標所指元素交換之前,如果end指標之前指的元素是0,那麼與current指標所指元素交換之後,current指標此刻所指的元素是 0,此時,current指標能動麼?不能動,因為如上述第1點所述,如果current指標所指的元素是0,還得與begin指標所指的元素交換。

  說這麼多,你可能不甚明瞭,直接引用下gnuhpc的圖,就一目瞭然了:

  通過以上幾個問題,你現在可以給自己“舉一反三”的能力打個分了。

  如果你覺得你懂了快速排序演算法,在給出提示的情況下,卻沒能想到荷蘭國旗問題的相關解法,甚至奇偶調序問題都想不到相關的解法,你應該問問自己:你真的懂了嗎?

  不管你懂不懂,我反正不懂!

  全文大部分內容整理自《程式設計師程式設計藝術》一書部分章節。

相關文章