快排
模板如下
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(x<q[j]);
if(i<j) swap(q[i],q[j]);
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
值得注意的是边界问题。为什么**(l+r)/2** 可不可以是 (l+r+1)/2?
答案是不可以。例如数组为{1 , 2} 则会陷入死循环。
或者说为了避免
quick_sort(q,l,j) 陷入死循环。
复杂度是nlog(n),其实可以看做树,每一次调用都会确定一个元素的具体位置。一般情形树高为log(n),每一层复杂度为n(遍历一遍数组)。极端情形退化为链表,高度为n。(采用随机化取pivot避免)
第K大
快排的衍生物。
将模板的:
quick_sort(q,l,j);
quick_sort(q,j+1,r);
改成:
if(k-1<=j) quick_sort(q,l,j);//第k大的坐标是k-1
else quick_sort(q,j+1,r);
即可。
顺便得修改一下返回值,返回第k个数值
归并排序
void merge(int q[],int l,int r){
if(r<=l) return;
int mid=(l+r)/2;
merge(q,l,mid);
merge(q,mid+1,r);
int i=l,j=mid+1,k=l;
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(int i=l;i<=r;i++,l++) q[i]=tmp[l];
//如果最后一行用 while() q[l]=tmp[l++]会错的很离谱,要写成
//q[l++]=tmp[l]
}
主要就是分治,加双指针排序。显然要额外的空间。
逆序对数
把模板的
while(i<=mid&&j<=r)
if(q[i]<q[j]) tmp[k++]=q[i++];
else tmp[k++]=q[j++];
改成:
while(i<=mid&&j<=r)
if(q[i]<=q[j]) tmp[k++]=q[i++];
else tmp[k++]=q[j++],ret+=(mid-i+1);
顺便把返回值改成long long。
这里的逆序对数怎么理解?
如果 q[i]<=q[j] , 显然是顺序的。
如果 q[i]>q[j] , 是逆序的,那么有多少对呢?
显然从当前的 q[i] 到 q[mid] 为止的所有数都和 q[j] 构成逆序对。
原因:(之前的递归调用,保证了两个数组都是有序的)
想起来高代滕**老师讲的一句话:数逆序对,要么数后面,要么数前面,要不然很容易乱,确实如此。这里我们数的是后面数组,即对每一个q[j],计算其逆序对数。
顺便说一句,滕老师对学生真的好。
二分
二分模板:
bool check(){}//这里check的是一个性质,该性质是在一个区间内。
下面的整数二分,其实有两种模板,关键还是向上取整还是向下取整
while(l<r){
int mid = (l+r)/2;
if(check()) r=mid;
else l=mid+1;
}
OR
while(l<r){
int mid = (l+r+1)/2;
if(check()) l=mid;
else r=mid-1;
}
浮点数二分与整数二分类似,多一个esp,且不用考虑向上或向下取整。
数的范围
给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1
关键点:两个位置所满足的性质是什么?第一个满足大于等于的数,和最后一个满足小于等于的数。
scanf("%d",&k);
int l=0,r=n-1,mid;
while(l<r){
mid=(l+r)/2;
if(arr[mid]>=k) r=mid;//这里的是第一个大于等于
else l=mid+1;
}
if(arr[l]==k) printf("%d ",l);//可能不存在,下面同理。
else printf("%d ",-1);
------------------------------------------找到起始位置
l=0,r=n-1;
while(l<r){
mid=(l+r+1)/2;
if(arr[mid]<=k) l=mid;//最后一个小于等于
else r=mid-1;
}
if(arr[l]==k) printf("%d\n",l);
else printf("%d\n",-1);
-----------------------------------------找到结束位置
为什么 l=mid 就需要向上取整呢?
因为每一次二分都是缩小一半范围
在最后的只剩两个的边界条件:
如:arr[2] = {1,2} mid=0,l=0,r=1会无限循环。
三次方根
需要判断正负号,以及是否在[-1,1]之内(影响到r的初始取值)。