歸併排序求逆序對

逆序對的定義:在一個數組a中如果有兩個元素滿足i < j && a[i] > a[j],那麼(i, j)就稱爲數組中的一個逆序對

現在,我們要求的是,給定一個長度爲N的數組a,求出該數組的逆序對的個數

比如 int a[4] = {7, 5, 6, 4} 逆序對爲(7, 6), (7, 5), (7, 4), (6, 4), (5, 4)


1.常規的做法是依次枚舉所有的元素對(a[i], a[j]),顯然,這樣的時間複雜爲平方級別的O(n^2)



2.還有一種做法是基於冒泡排序,從最後一個元素開始,逐次往上面冒,每當交換髮生的時候,逆序對的個數就加1,簡易證明如下

a[1]a[2]a[3]...a[i]a[i+1]..a[n-1],假定之前逆序對的個數爲l,現在在冒泡的過程中發現a[i+1] > a[i], 顯然要交換這兩個元素的位置,並且一個逆序對已經產生,即(a[i], a[i+1]),交換之後的數組爲a[1]a[2]a[3]...a[i+1]a[i]..a[n-1],這個時候,任意兩個元素l,m != i, j(a[l],a[m])如果在交換之前滿足逆序,那麼在交換之後同樣滿足逆序,因爲他們的位置並沒有發生變化,因此,此次交換對逆序對的總數沒有影響,再由於冒泡是從後往前冒,所以能夠保證每一次交換產生的逆序對在原來的數組中都存在,證畢!

由於冒泡排序可以通過各種優化來降低時間複雜度,但是最壞情況下冒泡的時間複雜度依然是O(n^2)



3.基於分治的思想,我們可以將數組分爲兩半,假定左邊的逆序對已經求畢,右邊的逆序對也已經求畢,比如還是數組int a[4] ={7, 5, 6, 4},左半邊{7, 5}逆序對爲1, 右半邊{6, 4}逆序對爲1,剩下的逆序對肯定是一個元素在左半邊,另一個元素在右半邊,那麼,在O((n^2)/4)的時間內可以枚舉所有的這種類型的逆序對,但是根據主定理

T(n) = 2*T(n/2) + O(n^2)最後計算的時間複雜度T(n)依然是平方級別的,有沒有什麼更好的辦法呢?

我們繼續思考,如果我們假定左半邊和右半邊的數組均已經排好序了,比如左半邊的數組爲{5, 7}右半邊的數組爲{4, 6},我們首先比較7和6,發現7>6由於7在數組左半邊,6在數組右半邊,顯然7在原數組中的序號小於6在原數組中的序號,(7, 6)滿足逆序對,由於右半邊的數組是排好序的,也就是說6之前的所有元素都比6小,當然也比7小,所以6之前的所有元素和7也滿足逆序對的條件,這樣就免去了6之前所有元素的掃描。

這樣,每當前半部分的當前元素p大於後半部分的當前元素q,逆序對就增加右半部分區間中q之前的元素個數,並且將p向左邊移動

如果p <= q顯然p,q不滿足逆序對,那麼直接將q往前移一步繼續比較,直到兩邊的元素至少有一邊比較完畢


以上的步驟都是基於左半邊和右半邊都已經排好序的情況下,這個假定貌似有點呵呵了,但是,如果聰明的你熟悉歸併排序的話,這個假定對你來說就是小菜一碟了,歸併排序的思想就是每次將左半邊和右半邊的元素分別排序,然後將排好序的元素歸併, 這個歸併過程我們剛好可以“順便”計算一下逆序對,只要將歸併的過程從兩半的數組尾部開始進行即可,C++實現代碼如下,你也可點擊這裏(IE瀏覽器除外)直接運行下面的代碼

#include <iostream>
#include <string>
#define MAX 110
using namespace std;

int merge_sort_main(int a[], int copy[], int begin, int end)
{
  if (begin == end)
    return 0;
  int mid = begin + (end - begin) / 2;
  int left = merge_sort_main(copy, a, begin, mid);
  int right = merge_sort_main(copy, a, mid + 1, end);
  int i = mid, j = end, k = end + 1;
  int ans = left + right;
  while (i >= begin && j >= mid+1)
  {
    if (copy[i] > copy[j])
    {
      a[--k] = copy[i--];
      ans += j - mid;
    }
    else
      a[--k] = copy[j--];
  }
  while (i >= begin)
      a[--k] = copy[i--];
  while (j >= mid + 1)
      a[--k] = copy[j--];
  return ans;
}
int merge_sort(int a[], int n)
{
  int copy[MAX];
  for (int i = 0; i < n; ++i)
    copy[i] = a[i];
  return merge_sort_main(a, copy, 0, n-1);
}

int main() {
  int a[10] = {7, 5, 6, 4};
  cout << merge_sort(a, 4) << endl;
  return 0;
}


發佈了140 篇原創文章 · 獲贊 70 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章