首先來看看原題
微軟2010年筆試題
在一個排列中,如果一對數的前后位置與大小順序相反,即前面的數大于后面的數,那么它們就稱為一個逆序數對。一個排列中逆序的總數就稱為這個排列的逆序數。如{2,4,3,1}中,2和1,4和3,4和1,3和1是逆序數對,因此整個數組的逆序數對個數為4,現在給定一數組,要求統計出該數組的逆序數對個數。
計算數列的逆序數對個數最簡單的方便就最從前向后依次統計每個數字與它后面的數字是否能組成逆序數對。代碼如下:
#include <stdio.h>
int main()
{
const int MAXN = 8;
int a[MAXN] = {1, 7, 2, 9, 6, 4, 5, 3};
int nCount = 0;
int i, j;
for (i = 0; i < MAXN; i++)
for (j = i + 1; j < MAXN; j++)
if (a[i] > a[j])
nCount++;
printf("逆序數對為: %d\n", nCount);
}
運行結果如下:
這種方法用到了雙循環,時間復雜度為O(N^2),是一個不太優雅的方法。因此我們嘗試用其它方法來解決。
在《經典算法系列之五歸并排序的實現》中觀察歸并排序——合并數列(1,3,5)與(2,4)的時候:
1.先取出前面數列中的1。
2.然后取出后面數列中的2,明顯!這個2和前面的3,5都可以組成逆序數對即3和2,5和2都是逆序數對。
3.然后取出前面數列中的3。
4.然后取出后面數列中的4,同理,可知這個4和前面數列中的5可以組成一個逆序數對。
這樣就完成了逆序數對的統計,歸并排序的時間復雜度是O(N * LogN),因此這種從歸并排序到數列的逆序數對的解法的時間復雜度同樣是O(N * LogN),下面給出代碼:
//從歸并排序到數列的逆序數對
#include <stdio.h>
int g_nCount;
void mergearray(int a[], int first, int mid, int last, int temp[])
{
int i = first, j = mid + 1;
int m = mid, n = last;
int k = 0;
while (i <= m && j <= n) //a[i] 前面的數 a[j] 后面的數
{
if (a[i] < a[j])
temp[k++] = a[i++];
else
{
temp[k++] = a[j++];
//a[j]和前面每一個數都能組成逆序數對
g_nCount += m - i + 1;
}
}
while (i <= m)
temp[k++] = a[i++];
while (j <= n)
temp[k++] = a[j++];
for (i = 0; i < k; i++)
a[first + i] = temp[i];
}
void mergesort(int a[], int first, int last, int temp[])
{
if (first < last)
{
int mid = (first + last) / 2;
mergesort(a, first, mid, temp); //左邊有序
mergesort(a, mid + 1, last, temp); //右邊有序
mergearray(a, first, mid, last, temp); //再將二個有序數列合并
}
}
bool MergeSort(int a[], int n)
{
int *p = new int[n];
if (p == NULL)
return false;
mergesort(a, 0, n - 1, p);
return true;
}
int main()
{
printf(" 從歸并排序到數列的逆序數對 \n");
const int MAXN = 8;
int a[MAXN] = {1, 7, 2, 9, 6, 4, 5, 3};
g_nCount = 0;
MergeSort(a, MAXN);
printf("逆序數對為: %d\n", g_nCount);
return 0;
}
運行結果:
推薦閱讀:
經典算法應用之一----歸并排序(微軟筆試題)
經典算法應用之二----基數排序(google筆試題)
經典算法應用之三----應用二中題目的升華
經典算法應用之四(上)---基本位操作之算法篇
經典算法應用之四(中)---基本位操作之算法篇
經典算法應用之四(下)---百度面試題
經典算法應用之五---隨機生成和為S的N個正整數
經典算法應用之六---過橋問題和過河問題
經典算法應用之七----10億數據中取最大的100個數據