【算法】归并排序

归并排序:
思路:每一次尽量把元素分成尽量相等的两半[x, m), [m, y)。两半的元素分别排序(递归求解),再把两个有序表合并成一个。(x 为排列元素起始的下标, y 为排列元素终止的下标 + 1,m 为 x + (y - x )/ 2 )

递归出口: 当且序列元素的长度为1时,递归结束。

合并的过程: 每次把两个序列的最小元素加以比较,把最小元素加入新表。只要一个序列非空就要继续合并 ( while( p < m || q < y ) )。因为其中一个序列为空时,因此不能直接比较A[p] 或 A[q].

  • 如果第二个序列为空(q >= y),第一个序列肯定非空,复制A[p](第一个序列)
  • 否则当且第二个序列非空,第一个序列也非空且A[p] <= A[q]时,复制A[p].

时间复杂度: O(nlogn)
这里写图片描述
补充Java版

    /**
     * @param array 待排序数组
     * @param left 待排列数组起始的下标
     * @param right 待排序数组终止的下标+1
     * @param temp 辅助数组
     */
    public static void mergeSort(int[] array, int left, int right, int[] temp) {
        if (right - left > 1) {
            int mid = left + (right - left) / 2;
            mergeSort(array, left, mid, temp); //递归求解左半数组
            mergeSort(array, mid, right, temp);//递归求解右半序列

            int index = left;
            int leftBegin = left;
            int rightBegin = mid;

            //只要一个序列非空就要继续合并
            while (leftBegin < mid || rightBegin < right) {
                if (rightBegin >= right || leftBegin < mid && array[leftBegin] <= array[rightBegin]) {
                    temp[index++] = array[leftBegin++];
                } else {
                    temp[index++] = array[rightBegin++];
                }
            }
            for (int i = left; i < right; i++) {
                array[i] = temp[i];
            }
        }
    }
void merge_sort(int* A, int x, int y, int* T)
{/*A是原数组,x 为排列元素起始的下标,y为排列元素终止的下标+1
T是辅助数组*/
    if(y - x > 1) //递归出口
    {
        int m = x + (y - x)/2;
        int p = x, q = m, i = x;
        merge_sort(A, x, m, T);//递归求解左半序列
        merge_sort(A, m, y, T);//递归求解右半序列
        while(p < m || q < y)
        {//只要一个序列非空就要继续合并
            if(q >= y || p < m && A[p] <= A[q]) 
	            T[i++] = A[p++];
            else 
	            T[i++] = A[q++];
        }
        for(i = x; i < y; i++)A[i] = T[i];//从辅助空间复制回数组A
    }
}

应用求逆序对数等等。
由于归并操作是从小到大进行的,当右边的A[q]复制到T中, 左边没来得及复制到临时数组T中的就是左边所有比A[q]大的数。值得注意的是对于划分的数列,左边划分的数列的排列顺序相对于右边的数列所产生的逆序对无关。如(20,12) 和(13,19)与 (12,20)和(13,19)左边相对于右边的逆序对都为2.((20,13),(20,19)),所以归并过程中的逆序对和分块差不多。在代码上唯一修改else T[i++] = A[q++];改成else {T[i++] = A[q++]; cnt += m - p;}//左边所剩的元素在[p,m)中元素个数为m - p
题目:HDU 1394 (传送门)Minimum Inversion Number
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 65536/32768 K (Java/Others)

Problem Description
The inversion number of a given number sequence a1, a2, …, an is the number of pairs (ai, aj) that satisfy i < j and ai > aj.

For a given sequence of numbers a1, a2, …, an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following:

a1, a2, …, an-1, an (where m = 0 - the initial seqence)
a2, a3, …, an, a1 (where m = 1)
a3, a4, …, an, a1, a2 (where m = 2)

an, a1, a2, …, an-1 (where m = n-1)

You are asked to write a program to find the minimum inversion number out of the above sequences.

Input
The input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1.

Output
For each case, output the minimum inversion number on a single line.

Sample Input
10
1 3 6 9 0 8 5 7 4 2

Sample Output
16

思路:对于这道题目,先用归并排序对原序列进行求逆序对。然后在每次产生新数列的过程中,减去因为这个新数列而减少的逆序对数,加上因为这个新数列而增加的逆序对数( 换句话说就是减去比序列首元素小的数的数目, 加上比比序列首元素大的数的数目。如(2,3,0,1)的逆序对数为4.那么(3,0,1,2)逆序对为4 - 2 + (4 - 2 - 1) = 3.)

#include<iostream>
#include<cstdio>
#define maxn 5005
using namespace std;

int cnt;
void merge_sort(int* A, int x, int y, int* T)
{
    if(y - x > 1)
    {
        int m = x + (y - x)/2;
        int p = x, q = m, i = x;
        merge_sort(A, x, m, T);
        merge_sort(A, m, y, T);
        while(p < m || q < y)
        {
            if(q >= y || p < m && A[p] <= A[q])
                T[i++] = A[p++];
            else{
                T[i++] = A[q++];
                cnt += m - p;
            }
        }
        for(int i = x; i < y; i++)
            A[i] = T[i];
    }
}
int T[maxn];
int A[maxn];
int B[maxn];
int main()
{
    int n;
    //freopen("input.txt","r",stdin);
    while(~scanf("%d",&n))
    {
        for(int i = 0; i < n; i++)
        {
            scanf("%d",&A[i]);
            B[i] = A[i];
        }
        cnt = 0;
        merge_sort(B,0,n,T);
        int ans = cnt;
        for(int i = 0; i < n - 1; i++)
        {
            cnt -= A[i];
            cnt += (n - A[i] - 1);
            if(ans > cnt)ans  = cnt;
        }
        printf("%d\n",ans);
    }
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值