归并排序 及拓展—逆序对

归并排序

时间复杂度

归并排序时间复杂度为O(NlogN)
似乎和快速排序差不多,但在有些特定的场合下,归并排序却能起到快速排序达不到的效果(如一年的联赛题,瑞士轮)

思路及实现

归并排序分为两个步骤,分、合;
分 的过程我们用二分的思路实现;
合 的过程时间复杂度可达到O(n);

分:

进行分治:
假设当前处理的区间为l~r;
实现:
过程定义:void merge_sort(int l,int r)

merge_sort(l,l+r>>1);
merge_sort(l+r>>1+1,r);
合:

过程定义:void merge_group(int l,int r)

void merge_group(int l,int r)
{
    int i=l,mid=l+r>>1,j=mid+1;
    for(int k=l;k<=r;k++)
        if(j>r||i<=mid&&a[i]<a[j])
            b[k]=a[i++];
        else
            b[k]=a[j++];
}
理解的话就不用记了

但对于一小部分人,这是不是很难记

这就到了我们stl发挥功效的时候了

介绍inplace_merge函数(头文件#include<algorithm>)

举个例子,数组a在连续的l~mid上是有序的,在mid+1~r上是有序的,要把合并的话
表达如下

inplace_merge(a+l,a+mid+1,a+r+1);

最终代码:

#include<iostream>
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;

inline int read()
{
    char chr=getchar();
    int f=1,ans=0;
    while(!isdigit(chr)) {if(chr=='-') f=-1;chr=getchar();}
    while(isdigit(chr))  {ans=ans*10;ans+=chr-'0';chr=getchar();}
    return ans*f;

}
int a[100],b[100],n;

void merge_group(int l,int r)//手打合并
{
    int i=l,mid=l+r>>1,j=mid+1;
    for(int k=l;k<=r;k++)
        if(j>r||i<=mid&&a[i]<a[j])
            b[k]=a[i++];
        else
            b[k]=a[j++];
    for(int k=l;k<=r;k++)
        a[k]=b[k];
}

void merge_sort(int l,int r)
{
    if(l<r)
    {
        int mid=l+r>>1;
        merge_sort(l,mid);
        merge_sort(mid+1,r);
        inplace_merge(a+l,a+mid+1,a+r+1);
//      merge_group(l,r);//手打合并
    }
    return;
}

int main()
{
    n=read();
    for(int i=1;i<=n;i++)
        cin>>a[i];
    merge_sort(1,n);
    for(int i=1;i<=n;i++)
        cout<<a[i]<<endl;
    return 0;
}

hmmmm然后是拓展——求逆序对个数

什么是逆序对呢?


看下面一组例子
>5 4 2 6 3 1
>其中逆序对有
>5 4
>5 2
>5 3
>5 1
>4 2
>4 3
>4 1
>2 1
>6 3
>6 1
>3 1
>共11组

朴素算法:O(N^2) //显然数据过大便无法接受

for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
if(a[i]>a[j]) ans++;


算法升级:O(n logn)
思路:归并排序

>在归并排序的合并步骤中,假设将两个有序数组A[] 和有序数组B[] 和并为一个有序数组C[]。计算逆序对问题转换为计算逆序对(a,b)的问题,其中a来自A[], b来自B[]。当a < b的时候,不计数,当a>b的时候(a,b)就是逆序对,由于A[]是有序的,那么A[]中位于a之后的元素对于B[]中的元素b也形成了逆序对,于是对于逆序对(a,b),(假设A[]的起始下标为sa,结束下标为ea,a的下标为pos)实际上合并成C[]后会会产生ea-pos+1个逆序对。

(我觉得,这一块我自己可能不能讲得很清楚,所以...............以上内容摘自流动的城市的博客https://blog.csdn.net/Sugar_Z_/article/details/48213537)

####好了,这时便不得不手打合并过程了

####但在合并原程下加一丢丢改变就OK了

修改合并的过程,其他不变

void merge_group(int l,int r)
{
int i=l,mid=l+r>>1,j=mid+1;
for(int k=l;k<=r;k++)
if(j>r||i<=mid&&a[i]<a[j])
b[k]=a[i++];
else
b[k]=a[j++],ans+=mid-i+1;//改动
for(int k=l;k<=r;k++)
a[k]=b[k];
}
```
附上练习题目Cow Photographs(Usaco2010Nov)

原文地址:https://www.cnblogs.com/zhenglw/p/9507920.html

时间: 2024-07-29 19:43:16

归并排序 及拓展—逆序对的相关文章

[PHP] 算法-数组归并排序并计算逆序对的个数的PHP实现

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数P.并将P对1000000007取模的结果输出. 即输出P%1000000007 1.数组归并排序 2.归并排序比较左右两个堆数组中的元素大小时,进行计数,倒着比较,因为左堆倒第一如果比右堆倒第一大,那么就比右堆的所有都大 mergeSort if left<right mid=[(p+r)/2] mergeSort(arr,left,mid,temp) mergeSort(

P1136 归并排序,求逆序对个数

这道题从看到它开始到做出来,已经过了快两周[因为第一次思路完全跑偏写的是暴力模拟想水过]: 题意是这样的:  jzabc除了对多米诺骨牌感兴趣外,对赛车也很感兴趣.上个周末他观看了一场赛车比赛.他总是能想出许多稀奇的问题.某一时刻,他看到有n辆车(总是匀速行驶)在同一直线上,并且处在一个无限长度的直道上,而且n辆车有严格的先后之分.他通过特殊的器材测出了每一辆车的速度.那么问题出现了,如果有两辆车A车和B车,A车在B车的后面,并且A车的速度比B车的快,那么经过一段时间后,A车一定会超过B车.我们

树状数组求逆序对

给定n个数,要求这些数构成的逆序对的个数.除了用归并排序来求逆序对个数,还可以使用树状数组来求解.树状数组求解的思路:开一个能大小为这些数的最大值的树状数组,并全部置0.从头到尾读入这些数,每读入一个数就更新树状数组,查看它前面比它小的已出现过的有多少个数sum,然后用当前位置减去该sum,就可以得到当前数导致的逆序对数了.把所有的加起来就是总的逆序对数.题目中的数都是独一无二的,这些数最大值不超过999999999,但n最大只是500000.如果采用上面的思想,必然会导致空间的巨大浪费,而且由

分治法 求 逆序对数 的个数 时间复杂度为O(n*logn)

思路: 分治法 归并排序的过程中,有一步是从左右两个数组中,每次都取出小的那个元素放到tmp[]数组中 右边的数组其实就是原数组中位于右侧的元素.当不取左侧的元素而取右侧的元素时,说明左侧剩下的元素均比右侧的第一个元素大,即均能构成一个逆序对.假设现在左侧剩余n个元素,则逆序对数+n. 另外,如果当所有右侧的元素都取完,但是左侧仍然有元素剩余时,左侧剩余的元素已经在之前的运算中加到了逆序对中,不需要再添加一次 下面给出 归并排序 和 求逆序对数 两份代码: code1: 归并排序 #includ

HDU 4911 Inversion(归并求逆序对)

HDU 4911 Inversion 题目链接 题意:给定一个数组,可以相邻交换最多k次,问交换后,逆序对为多少 思路:先利用归并排序求出逆序对,然后再减去k就是答案 代码: #include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const int N = 100005; int n, num[N], save[N], sn; void init() { for

第二章 算法基础 思考题2-4(逆序对)

1 package chap02; 2 3 import static org.junit.Assert.*; 4 5 import java.util.Arrays; 6 7 import org.junit.Test; 8 9 public class ques2_4 { 10 /** 11 * 逆序对,将一个序列中的所有逆序对打印输出 12 * 13 * @author xiaojintao 14 * 15 */ 16 static void printReverseOrder(int[]

ACM:归并排序,以及利用归并排序思想求解逆序对数!

(一)归并排序 分析: (1)划分问题:把序列分成元素个数尽量相等的两半. (2)递归求解:把两半元素分别排序. (3)合并问题:把两个有序表合并成一个.(每次只需要把两个序列的最小元素加以比较,删除其中的较小元素并加入合并后的新表) #include <iostream> using namespace std; const int MAXN = 1000; int A[MAXN], T[MAXN]; void merge_sort(int *A, int x, int y, int *T)

求逆序对 36

? ? 引言 ? ? 一开始接触到这题还觉得挺有意思的,但后来发现其深层次的含义就是一个归并排序,只是在归并排序的过程中做了一点小动作而已,这也再次证明了很多东西都是万变不离其宗的 ? ? 本文首先讲了一下归并排序的过程,用了自己比较喜欢的简洁的方式,然后对比归并排序与求逆序对之间的关系,发现需要稍微修改一下合并两个已排序数组的方法,要从两个数组的最后的数开始 ? ? 最后考虑到如果求逆序对的字符是固定数目的话,比如4个,详情可以参考后面的,那么时间复杂度不用归并排序的nlogn了,如果我们可以

codeforces 414C C. Mashmokh and Reverse Operation(归并排序求逆序对)

题目链接: C. Mashmokh and Reverse Operation time limit per test 4 seconds memory limit per test 512 megabytes input standard input output standard output Mashmokh's boss, Bimokh, didn't like Mashmokh. So he fired him. Mashmokh decided to go to university