刚开始学,认识很肤浅,就论现在的看法
这一部分和之前暴力法的侧重面还是有挺不同的,暴力法给我的感觉(虽然不会写题)就是找到正确的数据结构存储数据,分析终止条件,找到变化时的推导公式以及是否能给解答树剪枝,复杂的数据输入也是应该多加练习的地方。
高效算法设计里的东西会更加注重每个题的特点(找规律?)刚学也说不太清,和以往暴力求解不一样需要动脑子了,学一点有一点感触,就先记录下来
题目
给一个长度为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开始相加的,所以这个序列一定是连续的
划分问题:把序列分成元素个数尽量相等的两半
递归求解:把两半元素分别排序
合并问题:把两个有序表合成一个
因为是排序而且只有两半所以每次找两半中小(大)的元素放到另一个数组中就好
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;
}
作为程序员(滑稽)需要掌握的最基础的一个算法
题目
不需要全部排序,在期望意义下时间复杂度为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:从运行时间上也可以看出基本就差半个序列的排序时间
求下界和上界:在一个数组中加入有多个元素都是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的元素下标