假如有一組電影集合,包括n部電影。某個人對這n部電影的喜歡程度各有高低,根據其喜歡程度對這n部電影進行排名,按照從1到n的方式進行標記,這就形成了乙個關於電影的排名表。假設你和乙個陌生人各有自己對於這n部電影的排名表。現在想要比較你跟這個陌生人的「品味」差別,看看你們倆是否有「類似」的愛好。乙個很自然的辦法就是對比兩個人各自的排名表,看看兩個排名表的排名狀況是否相似。如果兩張排名表上的電影順序非常接近,就表示兩個人的」品味」非常接近,反之則兩個人的」品味」差距較大。
給定乙個序列包含n個資料,我們假設所有的數都是不相同的,我們想定義乙個度量,它將告訴我們這個序列跟處於上公升順序的序列相差多遠。如果a1 < a2 < … < an,那麼這個度量的值應該為0,表示與上公升順序基本一致。如果數變得更加雜亂時這個度量也就相應增大,表示與上公升順序相差較大。把這個概念量化的一種自然方式是計算序列中的逆序對的個數。逆序對的判斷標準如下,當i < j且ai > aj時,這兩個元素構成乙個逆序對。舉個例子,序列中有3個逆序對(2,1),(4,1),(4,3),所以該序列與上公升順序的序列之間的不相似度為3。
繼續考慮背景介紹中提出的問題,我們可以把任意兩個人的排名表,其中乙個作為上公升順序表來參考,然後計算另乙個排名表中的逆序對個數。逆序對越多,則兩個排名表的差別越大,說明兩個人的愛好品味差別越大。逆序對如果很接近0,說明兩個人的愛好品味都很接近。這種計算可以用於許多方面,比如書本、電影、餐廳等等,對使用者的嗜好進行匹配,進而知道哪些使用者跟哪些使用者的興趣愛好更為接近,這些計算結果可以用於許多應用軟體的推薦服務。
顯然,存在乙個時間複雜度為o(n^2)的暴力演算法,遍歷所有可能的資料對(ai, aj),計算出其中的逆序對的個數。這種演算法比較簡單便不多做闡述,這裡介紹的是一種更高效的演算法,它的時間複雜度只有o(nlogn)。
這個演算法的思想跟歸併排序很是類似,也是乙個分治演算法。它的基本思想如下:把要統計逆序對個數的序列c沿著中間位置切分成兩個子串行a和b,遞迴地計算子串行a和子串行b中逆序對的個數並排序,然後合併兩個子串行,合併的同時計算所有(ai,aj)的數對中逆序對的個數(ai在子串行a,aj在子串行b)。這個演算法的關鍵過程是合併計數這個環節,假設我們已經遞迴地排序好了這個序列的兩個子串行並計算好了子串行的逆序對個數,我們要如何計算總的逆序對個數呢?由於子串行a和b是已經排好序的,在把a和b合併到c時,按照歸併排序的合併過程,每次都挑選兩個子串行中最小的元素加入到c中。每次a中的元素ai被加到c中,不會遇到新的逆序,因為ai小於子串行b中剩下的每個元素,並且ai出現在b中元素的前面(子串行a為原序列的前半部分)。但每次b中的元素bj被加到c中,說明它比子串行a中剩下的元素都小,由於b中所有元素本來都排在a後面,所以bj就與a中剩下的所有元素都構成逆序對,此時a中剩下的元素個數就是與bj構成的逆序對的個數。理解這個過程之後,我們就可以很容易地在合併過程中計算逆序對個數了,合併計數過程的偽**如下:
mergeandcount(a, b)
初始化count= 0,c = 空
while
a和b都不為空
令ai和bj分別為a和b中的首元素
if ai <= bj
把ai加入到輸出表c中
a = a -
else
把bj加入到輸出表c中
b = b -
count += a中剩下的元素
endif
endwhile
ifa 為空
把b中剩下元素加入到c
else
把a中剩下元素加入到c
endif
return 合併結果c和逆序對個數count
整個合併計數過程如以上所示,理解了合併計數過程之後再來理解整個計算逆序對個數的演算法就簡單多了,整個演算法流程的偽**如下:
sortandcount
(c)if l只有1個元素
沒有逆序,c1 = c2 = c3 = 0
else
把這個表c均分成兩半,a和b
(c1, a) = sortandcount
(a)(c2, b) = sortandcount
(b)(c3, c) = mergeandcount
(a, b)
endif
return
(c1 + c2 + c3, c)
#include
using
namespace
std;
class array
~array()
} int
operator(int i) const
int& operator(int i)
int count()
private:
int sortandcount(int* arr, int* tmp, int beg, int end, bool inarr) else
return c1 + c2 + c3;
} else
} int mergeandcount(int* arr1, int* arr2, int beg, int mid, int end) else
}while (i != mid+1) arr1[k++] = arr2[i++];
while (j != end+1) arr1[k++] = arr2[j++];
return c;
} int* entry;
int size;
};int main()
cout
<< "逆序對個數: "
<< arr.count() << endl;
return
0;}
分治 求逆序對個數並列印逆序對
如果用最hick的方法去求那麼就是o n 2 的複雜度,如果想優化的話,用歸併排序的方法分治處理。主要思想 總逆序 左邊逆序 右邊逆序 左邊右邊分別排序後的逆序 include includeusing namespace std 用歸併排序的思想來求,歸併排序為o nlogn 的時間複雜度,比暴力...
分治法求逆序對
逆序對是這樣定義的 對於給定的一段正整數序列,逆序對就是序列中ai aj且i對於這個問題,很容易能想到n 2的演算法,但是顯然很多時候這個複雜度太高了,我們今天談談nlogn的演算法。對於求逆序對,有很多種nlogn的演算法,其中一種就是分治法。其實,分治法求逆序對的演算法就是歸併排序的思想 假設我...
動態逆序對 CDQ分治
對於序列a,它的逆序對數定義為滿足iaj的數對 i,j 的個數。給1到n的乙個排列,按照某種順序依次刪除m個元素,你的任務是在每次刪除乙個元素之前統計整個序列的逆序對數。輸入格式 輸入第一行包含兩個整數n和m,即初始元素的個數和刪除的元素個數。以下n行每行包含乙個1到n之間的正整數,即初始排列。以下...