第一讲 基础算法
快速排序
快速排序的主要思想基于分治。
规定:待排序数组为q,第一个数组元素下标是L,最后一个数组元素下标是R
快速排序的原理:
1.确定分界点。分界点可以是q[L]、q[(L+R)/2]、q[R]或者一个随机的数组元素
2.调整范围,挑选出x(x是个值),使得第一个区间里的所有数都小于等于x,第二个区间里的所有数都大于等于x
3.递归处理左右两个区间
暴力解法:
①开辟额外的数组空间a[]和b[]。
②随后遍历q数组,将所有小于q[x]的元素全部放入a[]中,将所有大于q[x]的元素全部放入b[]中,再把a[]和b[]里的每个元素重新赋值给数组q
更优解:
①声明两个指针i、j。i最开始指向第一个元素,j最开始指向最后一个元素。
②先从i开始,如果 q[i] < q[x],那么i向后移动一位(i++),直到 q[i] >= q[x]。
转换到q[j],如果q[j] > q[x],那么j向前移动一位(j–),直到 q[j] <= q[x],swap q[i]和q[j]。
这样,在任意时刻,i前面的所有数都是小于等于x 的,j右边的所有数都是大于等于x的。
两个指针相遇或者穿过时,结束循环。
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5+7;
int q[N];
//快速排序模板
void quickSort(int q[], int l, int r){
if(l>=r) return;
int i = l -1,j = r + 1,x=q[l+r>>1];
while(i < j){
do i++; while(x > q[i]);
do j--; while(x < q[j]);
if(i<j) swap(q[i],q[j]);//容易忽略if条件
}
quickSort(q,l,j);
quickSort(q,j+1,r);
}
int main(){
int n;
cin>>n;
for(int i = 0;i<n;i++) scanf("%d",&q[i]);
quickSort(q,0,n-1);
for(int i = 0;i<n;i++) printf("%d ",q[i]);
return 0;
}
归并排序
归并排序也是分治的思想。
规定:待排序数组为q,第一个数组元素下标是L,最后一个数组元素下标是R
归并排序的原理:
- 确定分界点:mid=(l+r)/2
- 递归处理左右两边
使用双指针法来把两个有序的数组合并成一个有序数组
#include <iostream>
using namespace std;
const int N=100000;
int n;
int q[N],temp[N];
void merge_sort(int q[],int l,int r){
if(l>=r) return;
int mid=(l+r)>>1,k=0,i=l,j=mid+1;
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
//用双指针法将左右两个有序区间合并
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++];
//将temp数组中的有序的元素一一赋值给q数组
for(i=l,j=0;i<=r;i++,j++) q[i]=temp[j];
return;
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&q[i]);
}
merge_sort(q,0,n-1);
for(int i=0;i<n;i++){
printf("%d ",q[i]);
}
return 0;
}
归并排序是稳定的。
稳定:如果原序列里两个数的值是相同的,排序后位置如果不发生变化,那么这个排序就是稳定的。
整数二分
tips:二分的本质并不是单调性。如果有单调性,就一定可以二分;没有单调性,未必不能二分。
假设有一个已经存在的性质,在左半边区间不满足这个性质,在右半边区间满足这个性质,那么就可以将整个区间一分为二。
那么二分就可以寻找性质的边界,既可以找到左半边靠右的边界点也可以找右半边靠左的边界点。
二分寻找左半边靠右
mid=(l+r+1)/2
if(check(mid)) 来判断mid是否满足性质
如果true,mid的左边都满足左半边性质,说明答案(左半边靠右边界点)在[mid,r](闭区间 包含mid) 更新方式为l=mid,r不变
如果false,mid的右边都不满足左半边性质,说明答案(左半边靠右边界点)在[l,mid-1] 更新方式为r=mid-1,l不变
二分寻找右半边靠左边界点
mid=(l+r)/2
if(check(mid))
如果true,mid的右边都满足右半边性质,说明答案(右半边靠左边界点)在[l,mid],更新方式为r=mid,l不变
如果false,mid的左边都不满足右半边性质,说明答案(右半边靠左边界点)在[mid+1,r],更新方式为l=mid+1,r不变
//整数二分法模板
bool check(int x) {/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
#include <iostream>
using namespace std;
const int N = 100010;
int q[N],n,m;
int main(){
cin>>n>>m;
for(int i = 0; i < n ; i++) cin>>q[i];
while(m--){
int x;
cin>>x;
int l = 0,r= n-1;
while(l<r){
int mid=l+r>>1;
if(x<=q[mid]) r = mid;
else l = mid+1;
}
if(q[l]!=x) cout<<"-1 -1"<<endl;
else{
cout<<l<<" ";
int l = 0 ,r =n-1;
while(l<r){
int mid=l+r+1>>1;
if(x>=q[mid]) l = mid;
else r = mid-1;
}
cout<<l<<endl;
}
}
return 0;
}
浮点二分
//浮点数二分模板
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r)
{
const double eps = 1e-6; // eps 表示精度,取决于题目对精度的要求
while (r - l > eps)
{
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
return l;
}
#include<iostream>
using namespace std;
int main(){
double n;
cin>>n;
double l = -10000,r=10000;
while(r-l>1e-8){
double mid = (l+r)/2;
if(n<= mid *mid *mid) r=mid;
else l= mid;
}
printf("%lf\n",l);
return 0;
}