算法笔记系列:4.5 二分 4.6 two pointers
4.5.1 二分查找
- 基于有序序列的查找算法,时间复杂度为O(logn)
- 高效之处在于每一步都可以去除当前区间中的一半元素
# include<cstdio>
int binary_search(int a[],int left, int right,int x){
int mid;
while (left<=right){
mid=(left+right)/2;
if (a[mid]==x) return mid;
else if (a[mid]>x) right=mid-1;
else left=mid+1;
}
return -1;
}
int main(){
const int n=10;
int A[n]={1,3,4,6,7,8,10,11,12,15};
printf("%d %d\n",binary_search(A,0,n-1,6),binary_search(A,0,n-1,9));
return 0;
}
注意: left+right可能会超出Int的范围导致溢出,所以最好写left+(right-left)/2
4.5.2 二分法拓展
当查询的不是整数时,例如通过二分法计算$\sqrt{2}$
# include<cstdio>
const double eps=1e-5;
double f(double x){
return x*x;
}
double calsqrt(){
double left=1,right=2;
double mid;
while (right-left>eps){
mid=left+(right-left)/2;
if (f(mid)>2) right=mid;
else left=mid;
}
return mid;
}
4.5.3 快速幂
用二分法的思想求幂
-
递归写法
typedef long long LL; //求a^b%m,递归写法 LL binary_row(LL a,LL b,LL m){ if (b==0) return 1; if (b%2==1) return a*binary_row(a,b-1,m); else{ LL mul=binary_row(a,b/2,m); return mul*mul%m; } }
-
迭代写法
typedef long long LL; LL binary_pow(LL a,LL b, LL m){ LL ans=1; while (b>0) { if (b&1) ans=ans*a%m; a=a*a%m; b >>=1; } return ans; }
4.6.1 什么是two pointers
利用有序序列的枚举特性,使用两个下标对序列进行扫描的思想
4.6.2 归并排序
归并排序复杂度为O(nlogn),2-路归并排序的原理是将序列两两分组,组内进行单独排序,然后再合并,以此类推
const int maxn=100;
void merge(int A[],int L1,int R1, int L2,int R2){
int i=L1,j=L2;
int temp[maxn],index=0;
while(i<=R1&&j<=R2){
if (A[i]<=A[j]) temp[index++]=A[i++];
else temp[index++]=a[j++];
}
while(i<=R1) temp[index++]=A[i++];
while (j<=R2) temp[index++]=A[j++];
for (i=0;i<index;i++){
A[L1+i]=temp[i];
}
}
void mergeSort(int A[],int left, int right){
if (left<right){
int mid=(left+right)/2;
mergeSort(A,left,mid);
mergeSort(A,mid+1,right);
merge(A,left,mid,mid+1,right);
}
}
4.6.3 快速排序
平均时间复杂度在O(nlogn),原理是选定一个元素作为主元 保证左边元素不大于它,右边元素不小于它
int partition(int A[],int left,int right){
int temp=A[left];
while(left<right){
while(left<right&&A[right]>temp) right--;
A[left]=A[right];
while(left<right&&A[left]<=temp) left++;
A[right]=A[left];
}
A[left]=temp;
return left;
}
4.7.1 打表
- 在程序中一次性计算出所有需要用到的结果,之后的查询直接取这些结果
- 在程序B中分一次或多次计算出所有用到的结果,手工把结果写在程序A的数组中,然后在程序A就可以直接使用这些结果
- 对一些感觉不太会做的题目,先用暴力程序计算小范围数据的结果,然后找规律,或许能发现一些“蛛丝马迹”
4.7.2 活用递推
【PAT A1093】
# include<cstdio>
# include<cstring>
# include<iostream>
using namespace std;
const int mod=1000000007;
const int maxn=100010;
int left_num_p[maxn]={0};
int main(){
char str[maxn];
cin.getline(str,sizeof(str));
int len=strlen(str);
for (int i = 0; i < len; i++)
{
if(i>0) left_num_p[i]=left_num_p[i-1];
if(str[i]=='P') left_num_p[i]++;
}
int ans=0,num_right_T=0;
for(int i=len-1;i>=0;i--){
if(str[i]=='T') num_right_T++;
else if(str[i]=='A') ans=(ans+left_num_p[i]*num_right_T)%mod;
}
printf("%d\n",ans);
return 0;
}
4.7.3 随机选择算法
如何从无序数组中求出第K大的数,原理类似于快速排序算法,对主元进行排序完后,主元就是第M大的数,判断M和K的大小缩小范围,然后再一次排序,直到M=K就找到了,时间复杂度为O(n)