[算法竞赛入门经典]高效算法设计(1)排序与检索

(1)排序与检索

​ 刚开始学,认识很肤浅,就论现在的看法

​ 这一部分和之前暴力法的侧重面还是有挺不同的,暴力法给我的感觉(虽然不会写题)就是找到正确的数据结构存储数据,分析终止条件,找到变化时的推导公式以及是否能给解答树剪枝,复杂的数据输入也是应该多加练习的地方。

​ 高效算法设计里的东西会更加注重每个题的特点(找规律?)刚学也说不太清,和以往暴力求解不一样需要动脑子了,学一点有一点感触,就先记录下来

1.最大连续和

题目

给一个长度为n的序列,求最大连续和

用几个for循环找或者找前n项和做减法都是一般的做法,这里用的是分治法

分治法

划分问题:把问题的实例划分成子问题

递归求解:递归解决子问题

合并问题:合并子问题的解得到原问题的解

划分就是把这个序列分成两个元素个数尽量相等的部分,分别递归求出两个部分的最大连续和,合并是寻找起点位于左半终点位于右半的最大连续和,再分别和两个子问题的最优解比较

int maxsum(int* A,int x,int y)//返回数组在左闭右开区间[x,y)中的最大连续和
{
    int v,L,R,maxn;
    if(y-x==1)	return A[x];//只有一个元素返回这个元素
    int m=x+(y-x)/2;
    int maxn=max(maxsum(A,x,m),maxsum(A,m,y));//分治,递归求解
    v=0,L=A[m-1];
    for(int i=m-1;i>=x;--i)//合并(1)——从分界点向左的最大连续和
        L=max(L,v+=A[i]);
    v=0,R=A[m];
    for(int i=m;i<y;++i)   //合并(2)——从分界点向右的最大连续和
        R=max(R,v+=A[i]);
    return max(maxn,L+R);  //子问题的解maxn和左右最优解比较
}

关于maxn和L+R比较,因为L和R是从分界点m开始相加的,所以这个序列一定是连续的

2.归并排序以及逆序对问题

归并排序

划分问题:把序列分成元素个数尽量相等的两半

递归求解:把两半元素分别排序

合并问题:把两个有序表合成一个

因为是排序而且只有两半所以每次找两半中小(大)的元素放到另一个数组中就好

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++];
        }
        for(i=x;i<y;++i)//把排好序的这一部分还给a数组
            A[i]=T[i];
    }
}

排序的原理很简单就是左右谁小先复制谁,这个特性很好用,因为左右分开的情况下,下标有大小关系,元素也有大小关系,也就引出了逆序对问题

逆序对问题

题目

(没有AC,有50%的错误,不清楚在哪里)

#include
const int maxn=600000+5;
int arr[maxn],n;
int cnt=0;
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 main()
{
    scanf("%d",&n);
    for(int i=0;i<n;++i)
        scanf("%d",&arr[i]);
    int t[maxn];
    merge_sort(arr,0,n,t);
    printf("%d\n",cnt);
    return 0;
}

3、快速排序

作为程序员(滑稽)需要掌握的最基础的一个算法

快速选择问题

题目

不需要全部排序,在期望意义下时间复杂度为O(n)

#include
#include
const int maxn=5000000+5;
int n,k,arr[maxn],flag=0;

void quick_sort(int left,int right)
{
    if(left==right)
    {
        flag=left;
        return;
    }
    if(right-left>=1)
    {
        int i=left,j=right,temp=arr[left];
        while(i!=j)
        {
            while(i<j&&arr[j]>=temp)  --j;
            while(i<j&&arr[i]<=temp)  ++i;
            if(i<j)
            {
                int t=arr[i];
                arr[i]=arr[j];
                arr[j]=t;
            }
        }
        arr[left]=arr[i];
        arr[i]=temp;
        if(i==k)
        {
            flag=i;
            return;
        }
        if(i<k)//小于k,那么排序右边
            quick_sort(i+1,right);
        else
            quick_sort(left,i-1);
    }
}

int main()
{
    //freopen("1.in","r",stdin);
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i)
        scanf("%d",&arr[i]);
    //std::sort(arr+1,arr+n+1);
    //printf("%d %d\n",arr[k],k);
    quick_sort(1,n);
    printf("%d\n",arr[flag]);
    return 0;
}

PS:

这个题也就是k大数题目,想着写完了找个地方提交,第一个想的是蓝桥杯那,看了看题目不一样,考察点也不太相同,所以也就没改,于是又去找洛谷,题意倒是一样,不过题判不出来不知道什么情况,就只能自己造数据试了,怕答案不对就又用sort排序对了一遍答案,所以上面代码中有sort,造数据就挺好玩了,用到随机数。

#include
#include
int random(int n)
{
    return (long long)rand()*rand()%n;
}
int main()
{
    FILE *fp;
    fp=fopen("5.in","a");
    int n=random(5000000)+1;
    int m=2147483647;
    fprintf(fp,"%d %d\n",n,random(n));
    for(int i=1;i<=n;++i)
        fprintf(fp,"%d ",random(m));
    fprintf(fp,"\n");
    fclose(fp);
    return 0;
}

在我的电脑上100万个数用sort和这个快排才差0.5秒(STL厉害啊),上oj判更是基本没区别,这不行啊,卡不了时间啊,于是试了个1000万,造出来的数94mb有点大,于是就500万,电脑运行差1秒(1.132,2.152),oj上差400ms,不过可算是卡的1秒开外了,还挺好玩的

PPPS:从运行时间上也可以看出基本就差半个序列的排序时间

4、二分查找

求下界和上界:在一个数组中加入有多个元素都是v,求排序好后第一个v出现位置(下界),最后一个v出现的位置(上界)(下标大小,小就下界,大就上界)

int lower_bound(int* A,int x,int y,int v)
{
    while(x<y)
    {
        int m=x+(y-x)/2;
        if(A[m]>=v)	
            y=m;	 
        else
            x=m+1;
         //if(A[m]<=v)	upper_bound
         //	x=m+1;
         //else
         //	y=m;
    }
    return x;
}

关键在于等号的位置

在STL中有同名函数lower_bound和upper_bound

注:要求有序

upper_bound(arr+i,arr+j,x)-arr;//返回第一个大于x的元素下标
lower_bound(arr+i,arr+j,x)-arr;//返回第一个小于等于x的元素下标

你可能感兴趣的:(算法)