快速排序-分治
1.确定分界点:q[l] q[r] q[(l+r)/2] q[random]
2.*调整范围,使得左边一部分的数都<=分界点,右边部分的数都>=分界点
3.递归处理左右两段使得两段排好序,也就排好了整体
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 100010;
int q[N];
void quick_sort(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(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);
}
int main()
{
int n;;
scanf("%d",&n);
for(int i = 0; i < n; i++)
{
scanf("%d",&q[i]);
}
quick_sort(q,0,n-1); //注意0、n-1
for(int i = 0; i < n; i++)
{
printf("%d ",q[i]);
}
return 0;
}
归并排序-分治
1.确定分界点:mid=l+r>>1
2.递归排序左边部分和右边部分
3.归并-合二为一(即将原来左右两部分各自排好的序整合成一大部分排好的序)
#include <iostream>
using namespace std;
const int N = 1000010;
int a[N],tmp[N];
void merge_sort(int q[],int l,int r)
{
if(l>=r) return;
int mid = l+r>>1;
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]) tmp[k++]=q[i++];
else tmp[k++]=q[j++];
while(i<=mid) tmp[k++]=q[i++];
while(j<=r) tmp[k++]=q[j++];
for(i=l,j=0;i<=r;i++,j++) q[i]=tmp[j];//复制回q[]里头
}
int main()
{
int n;
scanf("%d",&n);
for(int i = 0; i < n; i++) scanf("%d",&a[i]);
merge_sort(a,0,n-1);
for(int i = 0;i<n; i++)
printf("%d ",a[i]);
return 0;
}
快速选择
类似于快排,但是他只用递归左半部分或右半部分
例题:
给定一个长度为n的整数数列,以及一个整数k,请用快速选择算法求出数列从小到大排序后的第k个数。
输入格式
第一行包含两个整数 n 和 k。
第二行包含 n 个整数(所有整数均在1~109范围内),表示整数数列。
输出格式
输出一个整数,表示数列的第k小数。
数据范围
1≤n≤100000,
1≤k≤n
输入样例:
5 3
2 4 1 5 3
输出样例:
3
#include <iostream>
using namespace std;
const int N=100010;
int q[N];
int n,k;
int quick_sort(int l,int r,int k)
{
if(l==r) return q[l]; //区间只有一个数时就是要求的数
int x=q[l],i=l-1,j=r+1;;
while(i<j)
{
while(q[++i]<x);
while(q[--j]>x);
if(i<j) swap(q[i],q[j]);
}
int sl=j-l+1;
if(k<=sl) quick_sort(l,j,k); //第k个数比划分的左半部分的个数少时,必定在左边,所以只要递归左边
else quick_sort(j+1,r,k-sl); //右边同理
}
int main()
{
cin >> n >> k;
for(int i = 0; i < n; i++) cin >> q[i];
cout << quick_sort(0,n-1,k)<<endl;
return 0;
}
二分
如果有单调性一定可以二分,可以二分的题目不一定非要有单调性,所以二分的本质并不是单调性。
整数二分(注意边界问题)
//模板一
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
return l;
}
//模板二
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;
}
分析-Acwing shibagugi用户、Miramo用户
假设有一个总区间,经由我们的 check 函数判断后,可分成两部分
我们以o作 true,…作 false 示意较好识别
若我们的目标是下面这个v,那麽就必须使用模板 1
…vooooooooo
假设经由 check 划分后,整个区间的属性与目标v如下,则我们必须使用模板 2
oooooooov…
所以下次可以观察 check 属性再与模板1 or 2 互相搭配就不会写错啦
模板1就是在满足chek()的区间内找到左边界,模板2在满足check()的区间内找到右边界。然后无论是左边界还是右边界,都应该是整个区间中某一段满足某性质(如单调不降)与另一段不满足该性质的分界点(也就是同学的v)
实数二分(不存在边界问题)
bool check(double x) {/* ... */} // 检查x是否满足某种性质
double bsearch_3(double l, double r) {
// 定义一个 eps, 处理精度问题
const double eps = 1e-6;
// 注意循环条件不要写错
while (r - l > eps) {
// 步骤 A: 找中间值
double mid = (l + r) / 2;
// 步骤 B: 判断是否满足性质
if (check(mid)) r = mid;
else l = mid;
}
return l;
}