本文預計閱讀時間4分鐘,在讀的過程當中你須要帶着如下問題:
-
把輸入劃分紅更小的子問題。
-
遞歸的治理子問題。
-
把子問題的解決方案組合到一塊兒,造成原始問題的解決方案。
輸入包含不一樣整數的數組A, 輸出A中逆序對的數量,逆序是指: 若是 i < j 而 A[i] > A[j],那麼 (i, j) 就是一組逆序對。
其中i = 2對應A[2] =3 ,j=4對應A[4]=2,2<4, 可是A[2] > A[4],因此這是一組逆序對。
一個緣由是想要計算一種數值類似度,該數值的類似度用於對兩個已排序列表之間的類似度進行量化。好比兩我的都看過10部電影,按照從最喜歡到最不喜歡的順序進行排列,那麼怎麼衡量兩我的的選擇是類似仍是不類似的呢?解決這個問題的一種量化方法就是經過包含10個元素的數組A,A1表示讀者的朋友從電影列表中選擇最喜歡的電影,a2表示他喜歡第二的電影,以此類推,a10表示他最不喜歡的電影,這樣若是讀者最喜歡的電影是星球大戰,而這部電影在讀者的列表中只顯示的第5位,那麼a1就等於5,若是兩我的的排序是相同的,這個數組就已經排序了,不存在逆序對,若是這個數組包含的逆序對越多,讀者和朋友之間對電影評價的分歧就越多,對電影的偏好就不一樣了。
對已排序列表進行類似性測量的另外一個緣由就是協同篩選,這是一種任意生成推薦方案的方法,網站就怎麼推出關於產品電影歌曲內容的建議呢?在協同篩選中,其思路就是尋找其餘與它類似偏好的用戶,而後推薦他們所喜歡的內容。所以協同篩選須要用戶「類似性」的定義,而計算逆序對就能夠捕捉問題的本質。
咱們首先想到的就是暴力窮舉搜索法,輸入一個數組A,裏面包含不一樣的整數,輸出的是它的逆序對個數,以上就是暴力解法的僞代碼。外層循環i表示從左到右的遍歷數組A中的元素,內層循環j是沒有與i對比過的元素,逆序了就累加。它的缺點是時間複雜度很高,O(n^2)。
若是咱們用分治算法來算這個問題的話,第一個步驟就是把數組A劃分紅更小的子問題,咱們把A平均的劃分紅兩個部分,左邊和右邊,這樣數組規模就變小了,這樣劃分下就有三種狀況:
-
第1種就是逆序對 i 和 j 都位於數組的左半部分,就是下標 i 和 j 是小於等於n/2的
-
第2種狀況是逆序對 i 和 j 位於數組的右半部分
-
第3種狀況是逆序對 i 位於左半部分 j 位於右半部分,以上是僞代碼。
接下來咱們須要解決子問題,對於狀況1,2其實就是調用自身的遞歸,因此咱們只用實現第3鍾狀況CountSplitInv。
在CountInv的僞代碼中,須要實現CountSplitInv函數,咱們以前講的MergeSort排序算法自然的能夠計算逆序對數目,而它實現的思路又是兩個已排序的數組合併成一個新數組,上面的CountInv的狀況3實際就是i在左邊數組中,j在右邊數組中,而左右兩邊的數組沒有排序,因此咱們對他兩排一下序就能引用MergeSort算法。咱們稍微的修改一下上面的僞代碼,使得遞歸後除了返回逆序對數目,還要返回排序後的數組,下面是修改後的僞代碼。
那麼以上在處理逆序對 i, j 一個在左邊一個在右邊這種狀況的時候,就能夠用上以前的MergeSort算法,如今咱們來回顧一下。
以上是MergSort的僞代碼,它是輸入已排序的C和D,輸出是排序好的B。i,j分別控制C,D的元素,哪一個元素小就把它加入到B中。那麼,這裏的C就是原問題中的左半部,D就是原問題的右半部分,當C[i] > D[j] 的時候,說明產生了逆序對,而C又是排序後的,因此i以後的數字都是大於D[j]的,因此對於D[j]所帶來的逆序對數目就是C數組i到最後的元素個數,因此,咱們能夠在排序的基礎上計算出逆序對個數。把這一段話翻譯成僞代碼就是以下。
這樣就完成了分治算法對於逆序對的計算。時間複雜度是O(nlogn),比暴力搜索快不少。文章開頭的問題你想通了嗎?