逆序對 歸併排序求法 分治思想

逆序對介紹

如果存在正整數 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],則<A[i], A[j]> 這個有序對稱爲 A 的一個逆序對,也稱作逆序數。

求解一個數組中逆序對個數

其實就是求下標小的大數,那麼二話不說先來暴力,暴力顯然是O(n2),8太行

int unseq(vector<int> &nums)
{
	int cnt = 0;
	for(int i=0; i<nums.size(); i++)
		for(int j=i+1; j<nums.size(); j++)
			if(nums[i]>nums[j]) cnt++;
	return cnt;
}

分治思想:藉助歸併排序

對於數組中的逆序對個數,將數組分爲[l, mid][mid+1, r]區間,問題可以分解爲子問題:

  • 子數組[l, mid]的逆序對個數 lcnt
  • 子數組[mid+1, r]的逆序對個數 rcnt
  • A[i]位於左邊,A[j]位於右邊 且 A[i] > A[j] 的 <i, j> 組成的對

問題 1,2可以遞歸地求解,那麼關鍵是如何求問題3,即橫跨左右的對

注意這裏求解問題12的時候,不單單只是求解,還對數組排序了,也就是左邊/右邊都是有序的狀態,那麼我們可以用O(n)的時間,完成求解

  • 在右側子數組中,從後往前枚舉 j 下標
  • 在左側子數組中,設置指針 i ,一開始 i 指向mid
  • i 向左滑動,找到第一個 i 使得 A[i]<=A[j]
  • 說明前面 i+1 ~ mid 下標,A[i] 都大於 A[j]
  • 那麼對於 A[j] 爲右邊的逆序對,找到了 mid-i

因爲兩邊都有序,那麼對於 j 下標,因爲A[j]是遞減的,那麼A[j+1]配對的個數,同樣可以運用到A[j]上,此外還要額外判斷A[j]是否能滿足更多逆序對

這裏利用兩邊數組的有序性,變相利用前面的結果,達到節省時間

看似兩重循環,其實 i,j 回退不超過 n/2 次,所以複雜度O(n)

用O(n)時間找打橫跨的逆序對個數,我們還要將數組歸併,方便上一層遞歸繼續查找

這裏使用【inplace_merge函數】進行歸併操作

這裏你可能會有很多問號:

排序之後對不是順序被打亂了嗎?

打亂順序(排序)之前,我們已經求得了兩邊子數組的解的個數
而且求解這一趟的橫跨的逆序對的時候,左右都排序了,但是他們相對位置不變,即左元素一定在左邊子數組,右邊同理

int unseq(vector<int> &nums, int l, int r)
{
	if(l>=r || l<0 || r>=nums.size()) return 0;
	int mid=(l+r)/2, cnt=0, i=mid;
	int lcnt = unseq(nums, l, mid);
	int rcnt = unseq(nums, mid+1, r); 
	for(int j=r; j>mid; j--)
	{
		while(i>=l && nums[j]<nums[i]) i--;
		cnt += mid-i;
	}
	inplace_merge(nums.begin()+l, nums.begin()+mid+1, nums.begin()+r+1);
	return lcnt+rcnt+cnt;
}

完整代碼

#include <bits/stdc++.h>

using namespace std;

int unseq(vector<int> &nums, int l, int r)
{
	if(l>=r || l<0 || r>=nums.size()) return 0;
	int mid=(l+r)/2, cnt=0, i=mid;
	int lcnt = unseq(nums, l, mid);
	int rcnt = unseq(nums, mid+1, r); 
	for(int j=r; j>mid; j--)
	{
		while(i>=l && nums[j]<nums[i]) i--;
		cnt += mid-i;
	}
	inplace_merge(nums.begin()+l, nums.begin()+mid+1, nums.begin()+r+1);
	return lcnt+rcnt+cnt;
}

int unseq(vector<int> &nums)
{
	int cnt = 0;
	for(int i=0; i<nums.size(); i++)
		for(int j=i+1; j<nums.size(); j++)
			if(nums[i]>nums[j]) cnt++;
	return cnt;
}

int main()
{	
	vector<int> nums{2,7,4,3,1,8,6,5};
	cout<<unseq(nums)<<endl;
	cout<<unseq(nums, 0, nums.size()-1)<<endl;
	for(int i=0; i<nums.size(); i++) cout<<nums[i]<<" ";
	cout<<endl;
	
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章