歸併排序-陣列中的逆序對

許佳佳233發表於2016-12-18

參考《劍指Offer》

題目:

在陣列中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個陣列,求出這個陣列中的逆序對的總數。

輸入:

每個測試案例包括兩行:
第一行包含一個整數n,表示陣列中的元素個數。其中1 <= n <= 10^5。
第二行包含n個整數,每個陣列均為int型別。

輸出:

對應每個測試案例,輸出一個整數,表示陣列中的逆序對的總數。

樣例輸入:

4
7 5 6 4

樣例輸出:

5

解析

1、我們一開始想到的一般就是順序掃描整個陣列,每掃描一個數字,就諸葛比較該數字與它後面的數字的大小,但是這個複雜度是O(n*n),應當存在更快的演算法。然後發現歸併演算法在此就可以使用。
2、歸併演算法的思路就是分治,先兩個兩個排序,然後四個四個排序,一直到排完為止。
3、在這道題使用歸併的思想就是在排序的同時進行逆序對的判斷。
兩個兩個排序的時候,我們判斷前一個數是否大於後一個數,如果是,那麼就存在一個逆序對。
於是當四個四個排序的時候,我們只需要判斷前兩個數中是否有數大於後兩個數,如果存在一個那麼就是存在一個逆序對,如果存在2*2個,那麼就存在2*2個逆序對。而前兩個本身就不需要進行比較了,因為已經排好序了。

程式碼

#include <stdio.h>

int n, arr[5000], tmp[5000];

//歸併排序,過程中 統計逆序數
int merge(int start, int mid, int end)
{
    int cnt = 0;
    int i = start;
    int j = mid+1;
    int k = start;

    while( i<=mid && j<= end)
    {
        if(arr[i] > arr[j])
        {
            tmp[k++] = arr[i++];

            //由於是從大到小排序,並且arr[i] > arr[j]
            //所以arr[i]與arr[j]右邊的每一個陣列成的都是逆序對
            cnt += (end-j+1);
        }
        else
        {
            tmp[k++] = arr[j++];
        }
    }
    while(i<=mid)
        tmp[k++] = arr[i++];
    while(j<=end)
        tmp[k++] = arr[j++];

    for(int i=start; i<=end; i++)
        arr[i] = tmp[i];
    return cnt;
}

int inversePairs(int start, int end)
{
    int cnt = 0;
    if(start < end)//僅僅需要判斷1個以上的數
    {
        int mid = (start + end)/2;
        cnt += inversePairs(start, mid); //左半部分 逆序對數量
        cnt += inversePairs(mid+1, end); //右半部分
        cnt += merge(start, mid, end); //合併兩部分,並計算數量
    }
    return cnt;
}
int main()
{
    while( scanf("%d", &n) != EOF)
    {
        for(int i=0; i<n; i++)
            scanf("%d", &arr[i]);

        printf("%d\n",inversePairs(0, n-1));
    }
    return 0;
}

相關文章