• 求逆序对的各种算法


    (A)为一个有(n)个数字的有序集((n>1)),其中所有数字各不相同。

    如果存在正整数(i,j)使得(1 ≤ i < j ≤ n)而且(A[i] > A[j]),则(<A[i], A[j]>)这个有序对称为(A)的一个逆序对,也称作逆序数。
    ——百度

    1.冒泡排序

    思想

    我们的冒泡排序的思想十分简单,我们每一次循环都将最大的数排到最后面。我们每交换一次就是一个逆序对

    我们冒泡排序有一个简单的小优化:如果我们某一轮没有交换了,我们的序列即为有序。

    代码:

    for(int i=1;i<=n;i++) {
    	bool flag=1;
    	for(int j=1;j<=n-i;j++)
    		if(a[j]>a[j+1]) swap(a[j],a[j+1]),flag=0,ans++;
    	if(flag) break;
    }
    

    时间复杂度(O(n^2))


    2.归并排序

    思想

    我们首先来了解一下归并排序的思想:二分和合并。


    三部曲:
    1.划分:把序列分成元素个数尽量相等的两半
    2.递归求解:把两半元素分别排序
    3.合并问题:把两个有序表合并为一个

    合并:每次只需把左右两边序列的最小元素进行比较,把其中较小的元素加入到合并后的辅助数组中。

    void msort(int s,int t) {
    	if(s==t) return ;
    	int mid=(s+t)>>1;
    	msort(s,mid),msort(mid+1,t);
    	
    	int i=s,j=mid+1,k=s;
    	while(i<=mid && j<=t) {
    		if(a[i]<=a[j]) r[k]=a[i],k++,i++;
    		else r[k]=a[j],k++,j++;
    	}
    	
    	while(i<=mid) r[k]=a[i],k++,i++;
    	while(j<=t) r[k]=a[j],k++,j++;
    	for(int i=s;i<=t;i++) a[i]=r[i];
    	return ;
    }
    

    我们可以假设左边序列为({3,4,7,9}),右边为({1,5,8,10})

    我们的第一步就是比较(1)(3),因为(1<3),所以(1)入预备数组。

    我们这时候可以发现,我们的(1)与左边序列的(3)(3)之后的数都是逆序对,一共就(4)对了。这也是归并排序找逆序对快的原因。

    我们只需要在(a[i]>a[j])时,答案加上(mid-i+1)

    代码

    void msort(int s,int t) {
    	if(s==t) return ;
    	int mid=(s+t)>>1;
    	msort(s,mid),msort(mid+1,t);
    	
    	int i=s,j=mid+1,k=s;
    	while(i<=mid && j<=t) {
    		if(a[i]<=a[j]) r[k]=a[i],k++,i++;
    		else r[k]=a[j],k++,j++,ans+=mid-i+1;
    	}
    	
    	while(i<=mid) r[k]=a[i],k++,i++;
    	while(j<=t) r[k]=a[j],k++,j++;
    	for(int i=s;i<=t;i++) a[i]=r[i];
    	return ;
    }
    

    时间复杂度(O(n log n))


    3.树状数组

    树状数组
    我们的树状数组也是可以求逆序对的。

    思想

    实际上就是统计当前元素的前面有几个比它大的元素的个数,然后把所有元素比它大的元素总数累加就是逆序对总数。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    int n,a[1005],lsh[1005],BIT[1005];
    
    int lowbit(int x) { return x & -x; }
    
    void update(int k,int x) {
    	for(int i=k;i<=n;i+=lowbit(i)) BIT[i]+=x;
    	return ;
    }
    
    int ask(int x) {
    	int ans=0;
    	for(int i=x;i;i-=lowbit(i)) ans+=BIT[i];
    	return ans;
    }
    
    int main() {
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]),lsh[i]=a[i];
    	sort(lsh+1,lsh+n+1);//离散化
    	unique(lsh+1,lsh+n+1)-lsh-1;
    	
    	for(int i=1;i<=n;i++) a[i]=lower_bound(lsh+1,lsh+n+1,a[i])-lsh;
    	int ans=0;
    	for(int i=1;i<=n;i++) {
    		update(a[i],1);//在a[i]这个位置上加1
            ans+=i-ask(a[i]);
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    时间复杂度(O(n log n))


    4.线段树

    思想

    逆序对可以表示成一个数前面有几个比这个数大的数,就表示这个数所形成的逆序对数。

    我们查找就是:
    (a[i]+1)~(Max)的值。

    我们加入就是:
    (a[i])这个位置的数量加(1)

    我们最后把查找到的所有对数输出即可。

    但我们的数可能很大,我们这时就需要使用到离散化了。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    int n,a[500005],lsh[500005],maxx;
    struct SementTree{ int l,r,sum; }t[2000005];
    
    void read(int &x) {
        int f=1; x=0; char ch=getchar();
        while(ch<'0' || ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
        while(ch>='0' && ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        x*=f;
    }
    
    void init() {
    	sort(lsh+1,lsh+n+1);
    	unique(lsh+1,lsh+n+1)-lsh-1;
    	for(int i=1;i<=n;i++) a[i]=lower_bound(lsh+1,lsh+n+1,a[i])-lsh;
    	return ;
    }
    
    void build(int p,int l,int r) {
    	t[p].l=l,t[p].r=r,t[p].sum=0;
    	if(l==r) { return ; }
    	int mid=(l+r)>>1;
    	build(p<<1,l,mid),build(p<<1|1,mid+1,r);
    	return ;
    }
    
    void update(int p,int u) {
    	if(t[p].l==t[p].r) { t[p].sum++; return ; }
    	int mid=(t[p].l+t[p].r)>>1;
    	if(u<=mid) update(p<<1,u);
    	else update(p<<1|1,u);
    	t[p].sum=t[p<<1].sum+t[p<<1|1].sum;
    	return ;
    }
    
    int ask(int p,int l,int r,int ll,int rr) {
    	if(ll<=l && r<=rr) return t[p].sum;
    	int mid=(l+r)>>1;
    	int ans=0;
    	if(ll<=mid) ans+=ask(p<<1,l,mid,ll,rr);
    	if(mid<rr) ans+=ask(p<<1|1,mid+1,r,ll,rr);
    	return ans;
    }
    
    int main() {
    	read(n);
    	for(int i=1;i<=n;i++) read(a[i]),lsh[i]=a[i],maxx=max(maxx,a[i]);
    	
    	init();
    	
    	build(1,0,maxx);
    	
    	int ans=0;
    	for(int i=1;i<=n;i++) {
    		ans+=ask(1,0,maxx,a[i]+1,maxx);
    		update(1,a[i]);
    	}
    	printf("%d",ans);
    	return 0;
    }
    
  • 相关阅读:
    java实现操作系统磁盘寻道先来先服务算法
    专业素养:发布文件,别忘了给出校验信息
    vue系列教程-08vue的动画和过渡效果
    vue系列教程-07vue动态绑定样式
    vue系列教程-06vue的事件处理
    vue系列教程-05vue常用指令
    vue系列教程-04vue数据处理和页面渲染
    vue系列教程-03vuejs的结构和生命周期
    vue系列教程-01第一个vue程序
    vue系列教程-02什么是mvvm和spa
  • 原文地址:https://www.cnblogs.com/sxqn/p/14082241.html
Copyright © 2020-2023  润新知