zoukankan      html  css  js  c++  java
  • 归并排序 及拓展—逆序对

    归并排序

    时间复杂度

    归并排序时间复杂度为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在连续的lmid上是有序的,在mid+1r上是有序的,要把合并的话
    表达如下

    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然后是拓展——求逆序对个数

    什么是逆序对呢?

    给定一组数列若其中存在i<j而a[i]>a[j],那么这就是一组逆序对

    看下面一组例子

    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)

  • 相关阅读:
    496. 下一个更大元素 I 力扣(简单) 单调栈
    240. 搜索二维矩阵 II 力扣(中等) Z字型查找
    638. 大礼包 力扣(中等) 记忆化搜索,弱点
    453. 最小操作次数使数组元素相等 力扣(简单) 没想出来
    传纸条
    同余方程
    花匠
    华容道
    货车运输
    火柴排队
  • 原文地址:https://www.cnblogs.com/zhenglw/p/9507920.html
Copyright © 2011-2022 走看看