归并排序

https://www.cnblogs.com/eniac12/p/5329396.html

       归并排序是创建在归并操作上的一种有效的排序算法,效率为O(nlogn),1945年由冯·诺伊曼首次提出。

  归并排序的实现分为递归实现非递归(迭代)实现。递归实现的归并排序是算法设计中分治策略的典型应用,我们将一个大问题分割成小问题分别解决,然后用所有小问题的答案来解决整个大问题。非递归(迭代)实现的归并排序首先进行是两两归并,然后四四归并,然后是八八归并,一直下去直到归并了整个数组。

  归并排序算法主要依赖归并(Merge)操作。归并操作指的是将两个已经排序的序列合并成一个序列的操作,归并操作步骤如下:

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针到达序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾
 
void Merge(int A[], int left, int mid, int right)// 合并两个已排好序的数组A[left...mid]和A[mid+1...right]
{
    int len = right - left + 1;
    int *temp = new int[len];       // 辅助空间O(n)
    int index = 0;
    int i = left;                   // 前一数组的起始元素
    int j = mid + 1;                // 后一数组的起始元素
    while (i <= mid && j <= right)
    {
        temp[index++] = A[i] <= A[j] ? A[i++] : A[j++];  // 带等号保证归并排序的稳定性
    }
    while (i <= mid)            //左边多余元素
    {
        temp[index++] = A[i++];
    }
    while (j <= right)          //右边多余元素
    {
        temp[index++] = A[j++];
    }
    for (int k = 0; k < len; k++)
    {
        A[left++] = temp[k];
    }
}
void MergeSortRecursion(int A[], int left, int right)    // 递归实现的归并排序(自顶向下)
{
    if (left == right)    // 当待排序的序列长度为1时,递归开始回溯,进行merge操作
        return;
    int mid = (left + right) / 2;
    MergeSortRecursion(A, left, mid);     //将左半边排序
    MergeSortRecursion(A, mid + 1, right);//将右半边排序
    Merge(A, left, mid, right);
}
void MergeSortIteration(int A[], int len)    // 非递归(迭代)实现的归并排序(自底向上)
{
    int left, mid, right;// 子数组索引,前一个为A[left...mid],后一个子数组为A[mid+1...right]
    for (int i = 1; i < len; i *= 2)        // 子数组的大小i初始为1,每轮翻倍
    {
        left = 0;
        while (left + i < len)              // 后一个子数组存在(需要归并)
        {
            mid = left + i - 1;
            right = mid + i < len ? mid + i : len - 1;// 后一个子数组大小可能不够,所以要进行判断
            Merge(A, left, mid, right);
            left = right + 1;               // 前一个子数组索引向后移动
        }
    }
}


剑指offer 面试题36 统计逆序对

分治思想,采用归并排序的思路来处理,如下图,先分后治:

 

我们用两个指针分别指向子数组的末尾,并比较两个指针指向的数字。如果第一个子数组中的数字大于第二个子数组中的数字,则构成逆序对,并且逆序对的数目等于第二个子数组中剩余数字的个数。

先把数组分解成两个长度为2的子数组,再把这两个子数组分解成两个长度为1的子数组。接下来一边合并相邻的子数组,一边统计逆序对的数目。在第一对长度为1的子数组{7}、{5}中7>5,因此(7,5)组成一个逆序对。同样在第二对长度为1的子数组{6},{4}中也有逆序对(6,4),由于已经统计了这两对子数组内部的逆序对,因此需要把这两对子数组进行排序,避免在之后的统计过程中重复统计。

逆序对的总数=左边数组中的逆序对的数量+右边数组中逆序对的数量+左右结合成新的顺序数组时中出现的逆序对的数量;

总结统计数组逆序对的过程:先把数组分隔成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序,其实这个排序过程就是归并排序的思路。

这里与归并排序的差别在于它是从后往前进行比较的,同时增加一个临时变量用于统计逆序对的个数,其它基本一致。

#include<stdio.h>  
#include<stdlib.h>  
  
/* 
统计两个子数组之间的逆序对 
*/  
long long MergePairsBetweenArray(int *arr,int *brr,int start,int mid,int end)  
{  
    int i = mid;  
    int j = end;  
    int k = end;  //辅助数组的最后一位  
    long long count = 0;  
  
    //设置两个指针i,j分别从右往左依次比较,  
    //将较大的依次放入辅助数组的右边  
    while(i>=start && j>=mid+1)  
    {  
        if(arr[i] > arr[j])  
        {  
            count += j-mid;  
            brr[k--] = arr[i--];  
        }  
        else  
            brr[k--] = arr[j--];  
    }  
  
    //将其中一个数组中还剩下的元素拷贝到辅助数组中,  
    //两个循环只会执行其中的一个  
    while(i>=start)  
        brr[k--] = arr[i--];  
    while(j>=mid+1)  
        brr[k--] = arr[j--];  
  
    //从辅助数组中将元素拷贝到原数组中,使其有序排列  
    for(i=end;i>k;i--)  
        arr[i] = brr[i];  
  
    return count;  
}  
  
/* 
统计数组中的所有的逆序对 
*/  
long long CountMergePairs(int *arr,int *brr,int start,int end)  
{  
    long long PairsNum = 0;  
    if(start<end)  
    {  
        int mid = (start+end)>>1;  
        PairsNum += CountMergePairs(arr,brr,start,mid); //统计左边子数组的逆序对  
        PairsNum += CountMergePairs(arr,brr,mid+1,end); //统计右边子数组的逆序对  
        PairsNum += MergePairsBetweenArray(arr,brr,start,mid,end); //统计左右子数组间的逆序对  
    }  
    return PairsNum;  
}  
  
/* 
将函数封装起来 
*/  
long long CountTotalPairs(int *arr,int len)  
{  
    if(arr==NULL || len<2)  
        return 0;  
  
    int *brr = (int *)malloc(len*sizeof(int));  
    if(brr == NULL)  
        exit(EXIT_FAILURE);  
  
    long long sum = CountMergePairs(arr,brr,0,len-1);  
    free(brr);  
    brr = NULL;  
  
    return sum;  
}  
  
int main()  
{  
    int n;  
    while(scanf("%d",&n) != EOF)  
    {  
        int *arr = (int *)malloc(n*sizeof(int));  
        if(arr == NULL)  
            exit(EXIT_FAILURE);  
  
        int i;  
        for(i=0;i<n;i++)  
            scanf("%d",arr+i);  
  
        printf("%lld\n",CountTotalPairs(arr,n));  
  
        free(arr);  
        arr = NULL;  
    }  
    return 0;  
}  

参考链接:https://blog.csdn.net/ns_code/article/details/27520535

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值