今天,算法篇要续更了。内容的话对于一些算法竞赛是适用的。内容涵盖:
- 基础算法:排序,二分,前缀和差分等等。
- 数据结构:栈,队列,堆,哈希等等。
- 图论与搜索:图的应用+DFS+BFS等等。
- 贪心算法:区间问题+huffman树等等。
- 动态规划:线性,区间等等。
- 数学类:快速幂,博弈论等等。(这个看时间)
理论补充完后,将补充习题,正文开始。
排序包含:快速排序和归并排序。
快速排序:
1.确定枢轴量x,以及双指针i,j。
2.交换元素,使得左区间元素小于等于x,右区间大于等于x。
3.递归左区间,递归右区间。
算法模板如下:
#include<iostream>
using namespace std;
int q[n]; //存放待排序元素
void quick_sort(int q[],int l,int r){
if(l>=r) return; //边界处理,递归终点
int x=q[(l+r)/2],i=l-1,j=r+1;
while(i<j){
do i++; while(q[i]<x);
do j--; while(q[j]>x);
if(i<j) swap(q[i],q[j]); //内置交换函数
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
上面的模板,可以解决所有的边界问题。因此,可以直接用,要是调整边界的话,注意修改的正确性。
归并排序
1.确定边界值mid=(l+r)/2;
2.归并排序左半边,归并排序右半边。
3.归并,将两个有序序列归并为一个序列。
模板:
#include<iostraem>
using namespace std;
int q[n],temp[n]; //q待排序数组;temp辅助数组,是必要的。
void merge_sort(int q[],int l,int r){
if(l>=r) return;
int mid=(l+r)/2;
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
int k=0,i=l,j=mid+1;
while(i<=mid&&j<=r){
if(q[i]<=q[j]) temp[k++]=q[i++];
else temp[k++]=q[j++];
}
while(i<=mid) temp[k++]=q[i++];
while(j<=r) temp[k++]=q[j++];
for(int i=l,j=0;i<=r;i++,j++) q[i]=temp[j];
}
二分
二分的思想:将区间分为两部分,左半部分满足某种性质,右半部分满足另一种性质。在查找的过程中,一种指针mid快速缩小查找范围,时间复杂度是O(logn)。分为整数二分与浮点数二分,以具体例子降解。:
整数二分:
《数的范围》
题目附上,直接讲解。根据题目数据量,main函数的时间复杂度不能超过O(nlogn),直接遍历很显然不行,考虑到题目的数据是升序的。自然容易想到二分,无序的话用归并排序,归并排序的时间复杂度是O(nlogn)。这样也不超时。AC代码如下:
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=1e6 + 5;
int a[maxn],n,q;
int main(){
cin>>n>>q;
for(int i=0;i<n;i++) scanf("%d",&a[i]);
while(q--){
int k;
scanf("%d",&k);
int l=0,r=n-1;
while(l<r){
int mid = l + r >> 1 ;
if(a[mid]<k) l=mid+1;
else r=mid;
}
if(a[l]!=k) cout<<"-1 -1"<<endl;
else{
cout<<l<<" ";
int l=0,r=n-1;
while(l<r){
int mid = (l + r + 1) >> 1 ;
if(a[mid]>k) r=mid-1;
else l=mid;
}
cout<<r<<endl;
}
}
return 0;
}
重要的是模板,整数二分有两套模板:
//假定 区间左边的数a满足的性质为: a<=x;区间右边的数b满足的性质为: b>x;
// 我们的目标是找x,它在q中。
int q[n]; //q中,拥有n个数,升序排列。
int l = 0,r= n-1; //区间左右边界
while(l<r){
int mid = l + r >> 1;
if(q[mid]<=x) l = mid;
else r = mid -1 ;
}
//假定 区间左边的数a满足的性质为: a<=x;区间右边的数b满足的性质为: b>x;
// 我们的目标是找x,它在q中。
int q[n]; //q中,拥有n个数,升序排列。
int l = 0,r= n-1; //区间左右边界
while(l<r){
int mid = l + r + 1 >> 1;
if(q[mid]>=x) r = mid;
else l = mid + 1 ;
}
仔细比较两者区别,合理使用。这两套模板可以解决所有的边界问题。
浮点数二分:
浮点数二分,好处是没有边界问题,比较简便,但是要注意精度。
数的三次方根
直接上AC代码:
#include<cstdio>
#include<iostream>
#include<cmath>
using namespace std;
int main(){
double n ;
cin>>n;
double l=0, r=abs(n);
if (r<=1) r=1;
while((r - l) > 1e-8){
double mid = (l + r) / 2;
if(mid*mid*mid > abs(n)) r = mid ;
else l = mid;
}
if (n>=0) printf("%.6f",r);
else printf("%.6f",r*-1);
return 0;
}
点一下用到的技巧:
1.正负数统一处理,将输入的负数转变为正数,输出时记得判断符号。
2.注意在[0,1]区间上,x三次方根大于x。注意将搜索区间扩大到l=0,r=1。不然会出错。
模板如下:
//假定 区间左边的数a满足的性质为: a<=x;区间右边的数b满足的性质为: b>x;
// 我们的目标是找x的平方根,其中x>1。
double l = l_bound,r= r_bound; //区间左右边界
while( (r-l) > 1e-8){
double mid = (l + r) / 2 ;
if(mid * mid >=x) r = mid;
else l = mid;
}
主要是注意精度问题,小数点保留6位,就需要精度为1e-8,4位就1e-6。以此类推。