一般来说:
n<=8时,n的阶乘运算量算法就够了(排列枚举)
n<=20时,用到2^n运算量算法(子集枚举)
n<=300至少用n的三次方运算量算法
大O 大theta
不需要进行精确地分析,假定各种最坏情况同时取到得到上界,这就是大O表示法。
算法分析
用心体会左闭右开区间的使用方法,显得非常自然,[X,X)代表空区间。
[X,Y)程序中根本不会用到y位置,相当于一个结束符位置。
在处理数组分割时非常好用
递归子问题个数
T(n) = 2*T(n/2) + O(n)
问题规模 递归子问题规模 合并时间
时间复杂度为O(nlgn)
示例:连续子序列最大和问题可以优化为子序列的尾部的前j项之和与子序列的首部的前i项之和的差。注意减法的时候,被减数会被减掉。
int s[0]=0,best=A[1];
for(int i=1;i<=n;i++) s[i]=s[i-1]+A[i];//保存前i项的和
for(int i=1;i<=n;i++)//遍历所有的子序列
for(int j=i;j<=n;j++)
best=max(best,s[j]-s[i-1]);
此时时间复杂度就变为了O(n^2);
归并排序 O(nlgn)
对一个数列分两半,分别递归,当只有两个元素时递归结束,然后进行一般化合并处理。合并不是单纯的对两个子数列进行比较,而是每次取数列中的最小值先比较把小的数字删除并放入到新表中,大的数按原位保持不变。所以有可能在一个子数列中取多次值,只要两个子数列有一个不为空时就能放数字到新表中,采取短路运算符"||"(先判断第一个条件,判断第二个条件的前提是第一个条件不满足),然后再进行放入新表操作。
放入的新表只是当前递归的一个辅助空间,存放排好的序列,注意当前递归结束时一定要把新表赋值给原表,因为上一级栈帧需要使用已经排序好的表,这也是合并的精髓。
void mergesort(int* A,int* T,int x,int y)
{
//排序为左闭右开区间[x,y)
//直接输出A即可,T是一个辅助空间
if(y-x >1){
int m=x+(y-x)/2;
int p=x,q=m,i=x;
mergesort(A,T,x,m);
mergesort(A,T,m,y);
while(p<m || q<y)
{
//分三种情况讨论 1空2非 1非2空 1非2非
//1非2空 1非2非为一种情况,进行合并,q>=y时说明1非2空
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[i]=T[i];//注意这里,每次都要更新A的值来对下一次递归使用
}
}
二分思想 O(lgn)
一般写成非递归形式
二分返回第一个查找到的值,查找区间为左闭右开区间
int bsearch(int* A,int x,int y,int v)
{
int m;
while(x<y){
m=x+(y-x)/2;
if(A[m]==v) return m;
else if(A[m]>v) y=m;
else x=m+1;
}
return -1;
}
二分查找只适用于有序序列,但是二分的思想常常用在一些抽象的场合。
左闭右开区间,二分返回查找到的下界,当v存在时返回他出现的第一个位置
int bsearch(int* A,int x,int y,int v)
{
int m;
while(x<y){
m=x+(y-x)/2;
if(A[m]>=v) y=m;
else x=m+1;
}
return x;
二分返回查找到的上界,当v存在时返回出现的最后一个位置的后面一个位置
int bsearch