複製鏈接
請複製以下鏈接發送給好友

逆序對

鎖定
逆序對,數學術語,設 A 為一個有 n 個數字的有序集 (n>1),其中所有數字各不相同。
如果存在正整數 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],則 這個有序對稱為 A 的一個逆序對,也稱作逆序數。
中文名
逆序對
外文名
Count Inversions
問題描述
一個數組,求數組中多少個逆序對
求解方法
利用兩重循環進行枚舉或歸併排序

目錄

逆序對定義

對於一個包含N個非負整數的數組A[1..n],如果有i < j,且A[ i ]>A[ j ],則稱(A[ i] ,A[ j] )為數組A中的一個逆序對。
例如,數組(3,1,4,5,2)的逆序對有(3,1),(3,2),(4,2),(5,2),共4個。

逆序對求解

問題描述
給定一個數組,求該數組中包含多少個逆序對。
求解方法
方法一:最原始的方法,利用兩重循環進行枚舉。該算法的時間複雜度為O(n^2)。
C++代碼如下:
int count_inversion(int a[], int n)
{
    int count = 0;
    for(int i = 0; i < n - 1; ++i)
        for(int j = i + 1; j < n; ++j)
            if(a[i] > a[j]) count += 1;
    return count;
}
Pascal代碼如下:
var
  i,j,k,n:longint;
  a:array[1..1000000] of longint;
begin
  readln(n);
  for i:=1 to n do read(a[i]);
  k:=0;
  for i:=1 to n-1 do
  for j:=i+1 to n do
  if a[i]>a[j] then inc(k);
  writeln(k);
end.
方法二:利用歸併排序的思想求解逆序對的個數,這是解決該問題的一種較為高效的算法。該算法的時間複雜度為O(nlogn)。
C++代碼如下:
#include <bits/stdc++.h>
using namespace std;

// [l, r)
void merge_inversion(vector<int> &nums, int  l, int r, int &cnt)
{
    cout << l << " - " << r << endl;
    // 只有一個元素直接返回
    if(r - l <= 1 ) return;

    // 分治
    int m = l + (r - l) / 2;
    merge_inversion(nums, l, m, cnt);
    merge_inversion(nums, m, r, cnt);

    // 歸併, 按照從大到小的順序。
    int i = l;
    int j = m;
    vector<int> temp;
    for(int k = l; k < r; ++k) {
        if(i < m && j < r)
            if(nums[i] > nums[j]) {
                 cnt += (r - j);
                 temp.push_back(nums[i++]);
            }
            else temp.push_back(nums[j++]);
        else {
            if(i < m) temp.push_back(nums[i++]);
            else if(j < r) temp.push_back(nums[j++]);
        }   
    }   
    for(int k = l; k < r; ++k)
        nums[k] = temp[k - l];
}       

int main()
{
    vector<int> nums = {1, 7, 2, 5, 8, 10, 6};
    int ans = 0;
    merge_inversion(nums, 0, nums.size(), ans);
    cout << ans << endl;
    for(int i = 0; i < nums.size(); ++i)
        cout << nums[i] << " ";
    cout << endl;
    return 0;
}
Pascal代碼如下:
Type arr=array[1..1000000]of longint;
Var i,n:longint;
    k:int64;
    a,b:arr;
Procedure merge(var a:arr;l,x,r:longint);
 var i,j,p:longint;//p:position
 begin
  i:=l;
  j:=x+1;
  p:=l;
  while (p<=r) do begin
   if(i<=x)and((j>r)or(a[i]<=a[j])) then begin
     b[p]:=a[i];
     inc(i);
     end
   else begin
    b[p]:=a[j];
    inc(j);
    inc(k,x-i+1);
    end;
   inc(p);
  end;
  for i:=l to r do a[i]:=b[i];
 end;

Procedure msort(var a:arr;l,r:longint);
 var x:longint;
 begin
  if (l<>r) then begin
  x:=(l+r)div 2;
  msort(a,l,x);
  msort(a,x+1,r);
  merge(a,l,x,r);
  end;
 end;

Begin
 readln(n);
 for i:=1 to n do read(a[i]);
 k:=0;
 msort(a,1,n);
 writeln(k);
End.
方法三:利用樹狀數組求逆序對,開始先將序列離散化以提高算法的時空效率,然後從序列的最左端開始建立樹狀數組,每創建一個就執行一次 i - getsum( x ) (x 為離散化的 值) 累加到ans上。最後的 ans即為所求的答案。注意在進行排序的時候如果兩個數組的值相等, 則需要確保排序後原先的相對順序不發生改變, 即確保使用穩定的排序方式。
該算法的時間複雜度為O(nlogn)。
C++代碼如下:
#include <bits/stdc++.h>
using namespace std;
#define MAXN 1000
int b[MAXN];        /* 離散化後的數組*/ 
int c[MAXN];        /* 存儲樹狀數組的元素*/
int n;                /* 數組的元素的個數*/
 
struct Node
{
    int val, i;
    bool operator < (const Node& rhs) const {
        return (this->val < rhs.val) || (this->val == rhs.val && this->pos < rhs.pos);
    }        
}a[MAXN];            /* 存放原數組元素, 並帶有其在元素組當中的位置*/
int lowbit(int i)
{
    return i & (-i);
}
void update(int i, int val)
{
    while(i <= n) {
        c[i] += val;
        i += lowbit(i);
    }
}
int getSum(int i)
{
    int res = 0;
    while(i > 0) {
        res += c[i];
        i -= lowbit(i);
    }
    return res;
}
int main()
{
    // 輸入原數組 
    cin >> n;            
    for(int i = 1; i <= n; ++i)
        cin >> a[i].val, a[i].i = i;
 
     // 對原數組按照val大小排序, 則此時的 i 的順序就是離散後的數組
    sort(a + 1, a + 1 + n); 
    
    // 計算逆序對
    int ans = 0; 
    for(int i = 1; i <= n; ++i) { 
        cout << a[i].val << " - " << a[i].i << endl;;
        update(a[i].i, 1);
        ans += a[i].i - getSum(a[i].i);
    }
    cout << ans << endl;
    return 0;
}