知道了逆序對的概念,那麼在程式中可以很容易地用列舉法暴力求出逆序對,時間複雜度為o(
n2) o(n
2),對於n<104
n
<104
的資料,這種方法是吃得消的,但是當資料變大時,這種方法就很容易超時,因此要進行優化。
這裡講兩種方法,一種是改變統計的演算法,即歸併排序求逆序;另一種是改變儲存的資料結構,使其在統計的時候效率變高,即樹狀陣列逆序,線段樹求逆序。由於樹狀陣列的編碼比線段樹簡單,且兩者的思想差不多,因此這裡只介紹樹狀陣列求逆序。
由於需要編碼,因此借用牛客網的乙個平台來檢驗**的正確性,故這裡給出乙個題目背景:陣列中的逆序對。
用這種方法首先要知道歸併排序的原理與細節,具體請看歸併排序。
統計逆序對的思想就如下圖所示:
在歸併排序中,首先要把序列劃分成兩部分,然後這兩部分分別排序,最後合併兩個有序序列;統計逆序對的過程就在其中,首先把序列分成兩部分,這兩部分內部的逆序對遞迴統計,最後在合併的時候要處理分散在兩部分的逆序對(合併時,兩個子串行內部已經是有序的了),在合併的時候,要關注並列舉右邊子串行的每乙個元素,對於每乙個元素,要在左邊的子串行中找出比當前元素大的元素的個數,把這個個數併入答案。
具體的操作,可以結合**與圖中給出的例子,一步一步分析。
class solution
long
long dfs(vector
&data, int l, int r)
};
由於是基於歸併排序的演算法,因此時間複雜度為o(
nlog
n)o (n
logn
),空間複雜度為o(
nlog
n)o (n
logn
)。樹狀陣列的思路與實現細節請參考樹狀陣列簡單易懂的詳解。
這裡面樹狀陣列並不是重點,它只是一種儲存手段,用了樹狀陣列只不過可以提公升這個演算法的效率而已;
思路其實很簡單,就是陣列中的元素(元素的值做新陣列的下標)按照從左往右的順序一次對映對應到新陣列上,舉個例子,陣列arr = [1, 3, 0, 5]
,對映到新的陣列new_arr = [1, 1, 0, 1, 0, 1]
,也就是說新陣列初始化為零,原陣列中的元素作為下標對應到新陣列的對應位置上的值置1
。注意,這個對映的過程要逐步進行,因為要在這個過程統計逆序對,例如,第乙個元素是arr[0] = 1
,故new_arr[arr[0]] = 1
,這個時候統計new_arr
中1 ~ 4
中有多少個1
,然後第二個元素是arr[1] = 3
,故new_arr[arr[1]] = 1
,這個時候再次統計new_arr
中3 ~ 4
有多少個1
……依次進行下去,最後可以得到答案。
這裡的原理就是利用原陣列中元素出現的順序與對映後元素的順序來確定陣列中的大小關係。
注意,上面的統計這個過程就要用到樹狀陣列了,這也就是我所說的,樹狀陣列在這裡只是為了提高效率而已,如果實際問題中對效率要求不是很嚴格,那麼完全可以不用樹狀陣列。
由於陣列中的元素可正可負,因此要用乙個偏移值offset
把所有的數都變成非負數;又因為樹狀陣列要求下標都是正數,因此要加1
使所有數都變成正數。
class solution
return (int)ret;
}int lowbit(int x)
long
long getsum(long
long c, int i)
void add(long
long c, int i, int n)
};
沒有離散化前的時間複雜度為o(
nlog
n)o (n
logn
),空間複雜度為o(
max−
min)
o (m
ax−m
in)。
首先,離散化是個很基本的操作,我們都應該要熟悉這個操作,把稀疏的數字對映到乙個陣列中,這個陣列的數字是緊湊的,這樣可以節約空間。
假如,上面提到的陣列arr = [1, 3, 0, 5]
,對映到新的陣列new_arr = [1, 1, 0, 1, 0, 1]
,可以看到,中間有兩個0
是沒有用的,離散化的具體表現就是去掉這些0
,使儲存空間更加緊湊;
由於去掉了沒用的元素,因此,下標的意義就發生了變化,new_arr
中的下標表示大小順序,下標對應的值表示元素在排序完後所處的位置,還是舉例子吧,arr = [1, 3, 0, 5]
排完序後對應陣列sorted_arr = [0, 1, 3, 5]
,new_arr = [1, 2, 0, 3]
,也就是說,arr[0] = 1
在sorted_arr
對應在第二位,因此new_arr[0] = 1
;arr[1] = 3
在sorted_arr
對應在第三位,因此new_arr[1] = 2
,……依次類推。這樣就完成了離散化。
離散化部分**如下:
sort(data.begin(), data.end());
for (int i = 0; i < (int)pos.size(); i++)
pos[i] = lower_bound(data.begin(), data.end(), arr[i]) - data.begin();
至於離散化後的事情,就和上面的思想一樣了。
class solution
int lowbit(int x)
long
long getsum(int c, int i)
void add(int c, int i, int n)
};
離散化後的時間複雜度為o(
nlog
n)o (n
logn
),空間複雜度為o(
n)o (n
)。上面說的兩種方法,時間複雜度都為o(
nlog
n)o (n
logn
),對於資料規模較大的情況,效果良好。當然,在使用樹狀陣列的時候同樣可以使用線段樹。
逆序對問題
逆序對問題。給一列數a1 a2,an 求它的逆序對數,即有多少個有序對 i j 使得 i j 但ai aj n 可以高達106 由於 n 的數量級到了106 所以採用o n2 及以上的時間複雜度肯定會超時,所以必須選取o nlog 2n 及以下時間複雜度的演算法。逆序對的求解思路和歸併排序很像,嘗試...
逆序對問題 O nlgn
問題描述 在陣列arr中,i j 如果 arr i arr j 那麼就存在乙個逆序對 目的就是求出逆序對的數目。演算法 暴力求解,o n 2 下面運用了一種很巧妙的方法,通過歸併排序的歸併過程,進行逆序對的統計!具體例子分析 比如 1 5 3 2 4 當 1 3 5 與 2 4 合併的時候,a.1 ...
逆序對問題 O nlgn
問題描述 在陣列arr中,i j 如果 arr i arr j 那麼就存在乙個逆序對 目的就是求出逆序對的數目。演算法 暴力求解,o n 2 下面運用了一種很巧妙的方法,通過歸併排序的歸併過程,進行逆序對的統計!具體例子分析 比如 1 5 3 2 4 當 1 3 5 與 2 4 合併的時候,a.1 ...