目录
快速排序
思路
利用分治的思想。将待排序的区间分为两部分:全部小于等于一个数x,右边一部分全部大于等于一个数x。层层递归至区间没有数或者只剩一个数时。<--粉色高光是递归出口
基本步骤
1.确定分界点x(这里x的选择大有名堂,涉及到后面的卡边界)
主要可以包括,左、右、中间或者随机下标的数
2.调整区间:就是“实现全部小于等于一个数x,右边一部分全部大于等于一个数x”这一过程。
简单汪两句:
用双指针算法,i,j分别从头尾开始遍历,当i找到大于等于x的值时停下来,当j找到小于等于x的值停下来。如果没有出现i,j交叉就swap交换两个数。
模板
#include<iostream>
using namespace std;
const int N=1e6+10;
int q[N];
int n;
void quick_sort(int l,int r){
if(l>=r) return;
int i=l-1,j=r+1,x=(l+r+1)/2;
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(l,j);
quick_sort(j+1,r);
}
int main(){
cin>>n;
for(int i=0;i<n;i++) scanf("%d",&q[i]);
quick_sort(0,n-1);
for(int i=0;i<n;i++) printf("%d ",q[i]);
return 0;
}
练习题
#include<iostream>
#include<algorithm>
#include<
using namespace std;
const int N=1e5+10;
int n,k;
int q[N];
void quick_sort(int l,int r){
if(l>=r) return;
int i=l-1,j=r+1,x=q[(l+r)/2];
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(l,j);
quick_sort(j+1,r);
return;
}
int main(){
cin>>n>>k;
for(int i=0;i<n;i++) scanf("%d",&q[i]);
quick_sort(0,n-1);
printf("%d",q[k-1]);
return 0;
}
可以注意一下:这里分界点取得是q[(r+l)/2],如果是取q[l]或者q[r]的话会TLE ,这里就引入下面的改进算法啦!
改进
1.就是取中点下标的值作为分界点,就是上面的做法。
2.取随机下标的值:int index=rand()%(r-l+1)+l;(其中r-l+1为数组个数)
#include<iostream>
#include<algorithm>
#include<ctime>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
const int N=1e5+10;
int n,k;
int q[N];
void quick_sort(int l,int r){
if(l>=r) return;
int y=rand()%(r-l+1)+l;
int i=l-1,j=r+1,x=q[y];
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(l,j);
quick_sort(j+1,r);
}
int main(){
cin>>n>>k;
for(int i=0;i<n;i++) scanf("%d",&q[i]);
quick_sort(0,n-1);
cout<<q[k-1];
return 0;
}
3.仅限于第k个数这一道题。
将k传入排序函数,判断k在左右哪一边,然后就仅仅排序所在的边。
#include<iostream>
#include<algorithm>
using namesapce std;
const int N=1e5+10;
int n,k;
int q[N];
int quick_sort(int l,int r,int k){
if(l>=r) return q[l];
int i=l-1,j=r+1,x=q[(l+r)/2];
while(i<j){
do i++; while(q[i]<x);
do j--; while(q[j]>x);
if(i<j) swap(q[i],q[j]);
}
int s=j-l+1;
if(s>=k) return quick_sort(l,j,k);
else return quick_sort(j+1,r,k-s);
}
int main(){
cin>n>>k;
for(int i=0;i<n;i++) scanf("%d",&q[i]);
cout<<quick_sort(0,n-1,k);
return 0;
}
归并排序
思路
将数组分成两个部分,先用递归排好两部分的序(从小到大)。在此基础上,我们进行归并排序:用两个指针分别指向第一个数组a,第二个数组b的第一下标i,j。然后比较a[i]与a[j]的大小。把较小的那个数放入临时数组tmp[k]。知道任意一个数组走到尾。最后将临时数组覆盖待排序的数组。
将两个排好序(从小到大)的数组合并成一个从小到大的数组。
基本步骤:
1.确定分界点(一般就是对半分)
2.递归排序两部分
3.归并两部分到一个临时数组
4.临时数组覆盖
模板
#include<iostream>
using namespace std;
const int N=1e5+10;
int n;
int q[N],tmp[N];
void merge_sort(int l,int r){
if(l>=r) return;
int mid=(l+r)/2;
merge_sort(l,mid);
merge_sort(mid+1,r);
int i=l,j=mid+1,k=0;
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,k=0;i<=r;i++,k++) q[i]=tmp[k];
return;
}
int main(){
cin>>n;
for(int i=0;i<n;i++) scanf("%d",&q[i]);
merge_sort(0,n-1);
for(int i=0;i<n;i++) cout<<q[i]<<" ";
return 0;
}
(标点符号真的会让我哭泣)
练习题
这里主要是理解归并的过程,并作出适当的改动
我们可以想到,在合并过程中,a数组下标在前,如果恰好a[i]>b[j],则a数组后面的所有数都是相对于b[j]的逆序对,个数(mid-i+1),那就直接开一个全局变量咯?
#include<iostream>
using namespace std;
const int N=1e5+10;
int n,q[N],tmp[N];
long long int res;
void merge_sort(int l,int r){
if(l>=r) return;
int mid=(l+r)>>1;
merge_sort(l,mid);
merge_sort(mid+1,r);
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r){
if(q[i]<=q[j]) tmp[k++]=q[i++];
else{
res+=(mid-i+1);
tmp[k++]=q[j++];
}
}
while(i<=mid) tmp[k++]=q[i++];
while(j<=r) tmp[k++]=q[j++];
for(int i=l,j=-;i<=r;) q[i++]=tmp[j++];
}
int main(){
cin>>n;
for(int i=0;i<n;i++) scanf("%d",&q[i]);
merge_sort(0,n-1);
cout<<res;
return 0;
}
注意注意res可能很大,所以我们来一个long long吧。
其实不用全局变量也可以
long long int merge_sort(int l,int r){
if(l>=r) return 0;
int mid=(l+r)>>1;
long long int res=merge_sort(l,mid)+merge_sort(mid+1,r);
.....
}
二分
思路
“核心不一定是单调性,单调性可以二分,但没有单调性的一些情况也可以二分。”
总的思路就是二分找到某一个性质的分界点。(应该算是一种思想上的二分)
整数二分:
分为两种情况:
如图:红绿代表两种性质,他们之间有分界点,二分就是取他们的分界点。
第一种情况: 取红色边界
先进行对半取(l+r)/2,验证他是否满足红色部分的性质,如果满足就将区间缩小至[mid,r](这里尤其要注意,mid满足该性质所以可以去到mid),不满足就将区间缩小至[l,mid-1],继续进行二分。跳出的条件就是l、f是否重合or交叉。
说明一点特殊情况:由于整数除法是整除,会进行向下取整。所以这里当区间调整至[mid,r]时,需要在mid的赋值时变为(l+r+1)/2;
这个例子当l与r就相差1时,范围就会一直是[l,r]死循环。
第二种情况 取绿色边界
思路大概跟上面时相同的,只要记住改变后的区间一定是满足这个判断性质的点
这里因为更新的是r,mid赋值就直接可以(l+r)/2
模板
一道例题送给大家
#include<iostream>
using namespace std;
const int N=1e5+10;
int num[N],n,q,k;
int main(){
cin>>n>>q;
for(int i=0;i<n;i++) scanf("%d",&num[i]);
while(q--){
cin>>k;
int l=0,r=n-1;
while(l<r){
int mid=(l+r)/2;
if(num[mid]>=k) r=mid;
else l=mid+1;
}
if(num[l]!=k) cout<<"-1 -1";
else{
cout<<l<<" ";
l=0,r=n-1;
while(l<r){
int mid=(l+r+1)/2;
if(num[mid]<=k) l=mid;
else r=mid-1;
}
cout<<l;
}
}
return 0;
}
这个例子可以很好地帮助我们理解二分!!
浮点数二分
相较于整数二分他没有这么多的边界问题,有的话那也只是精度控制。
我们直接给一个例题的板子
#include<iostream>
using namespace std;
int main(){
double n,mid;
cin>>n;
double l=-10000,r=10000;
while(r-l>1e-8){
mid=(l+r)/2;
if(mid*mid*mid>n) r=mid;
else l=mid;
}
printf("%.6lf",mid);
return 0;
}