基础算法(一)
快速排序
(时间复杂度nlog2(n)
——一共分成log2n层,每层比较n次)
思想–分而治之
1)在区间内任意选择一个数x作为分界点
2)将小于x的数全部转移到分界点左边
将大于x的数全部转移到分界点右边
方法:
创建下标标记使其指向数组左右两边
i=l-1
j=r+1
第二步使i,j分别向数组中心移动
倘若i指向的数>=x则i停下等待
j指向的数<=x则j停下等待
第三步交换i,j指向的两个数
直到i和j相遇
3)递归处理左右两边
#include<iostream>
using namespace std;
const int N =1e6+10;
int q[N];
int n;
void qucik_sort[int q[],int l,int r]{
if(l>=r)return;
int i=l-1, j=r+1,x=q[l+r>>1];//尽量选择中间作为x值,这样每次都分一半左右递归,这样一共分logn层,如果取左右端点的话就会分成n层,每次比较n个数复杂度会变成n^2
while(i<j){
while(q[++i]<x);//此处特别注意不能写为<=x和>=x,如果等于不交换的话,可能会一直保持x的位置不动,出现错误的排序
while(q[--j]>x);
if(i<j)swap(q[i],q[j]);
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);//若以i作为边界则是quick_sort(q,l,i-1);quick_sort(q,i+1,r)
}
int main (){
scanf("&d",&n);
for (int i=0;i<n;i++)scanf("%d",&q[i]);
quick_sort(q,0,n-1);
for (int i=0;i<n;i++)printf("%d",q[i]);
return 0;
}
当给定的序列有序时,如果每次选择区间左端点进行划分,每次会将区间[L, R]划分成[L, L]和[L + 1, R],那么相当于每次递归右半部分的区间长度只会减少1,所以就需要递归 n−1次了,时间复杂度会达到 n^2。但每次选择区间中点或者随机值时,划分的两个子区间长度会比较均匀,那么期望只会递归 logn层–yxc
//写道例题
#include<iostream>
using namespace std;
const int N=1e5+10;
int q[N];
int n,k;
int quick_sort(int q[],int l,int r,int k){
int i=l-1,j=r+1,x=q[l+r>>1];
if(i>=j)return q[l];
while(i<j){
while(q[++i]<x);
while(q[--j]>x);
if(i<j)swap(q[i],q[j]);
}
if((j-l+1)>=k)return quick_sort(q,l,j,k);
else return quick_sort(q,j+1,r,k-(j-l+1));
}
int main(){
scanf("%d %d",&n,&k);
for(int i=0;i<n;i++)scanf("%d",&q[i]);
printf("%d",quick_sort(q,0,n-1,k);)
return 0;
}
//这个题我觉得一般人就直接输出q[k-1]了吧…反正菜鸡我是这样
看了yxc大佬的代码果然大佬还是强,,,可能只是常规操作
这样写就直接减了一半的运算时间,复杂度降成了n(1+1/2+1/4+1/8…)<=2n
时间复杂度为O(n);
归并排序
(时间复杂度nlog2(n)
——一共分成log2n层,每层比较n次)
思想—分治
1)选择中间的数x((l+r)/2)作为分界点
2)递归处理左右两边
3)将左右两边的数组合二为一
分别定义两下标指向左右两端数组
依次向后作比较
将比较的较小值加入新的数组
两数相同时则加入第一个数组的值
比较完成得到新的数组
#include<iostream>
using namespace std;
const int N =1e5+10;
int q[N],temp[N];
int n;
void merge_sort (int q[],int l,int r){
if(l>=r)return;
int mid =l+r>>1;
int i=l,j=mid+1; //注意这里j必须取mid+1因为/2向下取整,j就尽量往上取,如果j取成mid~r,i取成l~mid-1就会有边界问题
int k=0;
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++];
for(i=l,k=0;i<=r;i++,k++)q[i]=temp[k];//尤其注意这一行,在利用temp数组向q赋值时,应注意temp下标从零开始,而q数组应从l取值到r
}
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;
}
//例题
这里是菜鸡本人的解法,没什么好说的,逐个比较,果不其然超时。。。
#include <iostream>
using namespace std;
const int N=1e5+10;
int q[N];
int n;
static long long x=0;
void method(int q[],int l,int r){
if(l>=r)return;
int mid;
mid=l+r>>1;
method(q,l,mid);
method(q,mid+1,r);
for(int i=l;i<=mid;i++){
for(int j=mid+1;j<=r;j++)
if(q[i]>q[j])x++;
}
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++)scanf("%d ",&q[i]);
method(q,0,n-1);
printf("%d",x);
return 0;
}
下面是yxc大佬的解法
简单叙述一下思路
首先还是一样的利用归并排序进行排序
在中间比较的时候采用一些技巧对逆序对累和
还是强?手写一遍表示敬意
#include<iostream>
using namespace std;
const int N =1e5+10;
int n;
int q[N],temp[N];
typedef long long LL;
LL merge_sort(int q[],int l,int r){
if(l>=r)return 0;
int mid=l+r>>1;
int i=l,j=mid+1,k=0;
LL res =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++];
res+=mid-i+1;//*********倘若q[i]>q[j]那么i到mid的数也大于q[j],,逆序对即为mid-i+1个
}
}
while(i<=mid )temp[k++]=q[i++];
while(j<=r)temp[k++]=q[j++];//扫尾处理
for(i=l,k=0;i<=r;i++,k++)q[i]=temp[i];//排序完成,将排得的数复制到原数组当中
return res;
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++)scanf("%d ", &q[i] );
printf("%ld",merge_sort(q,0,n-1));
return 0;
}
二分查找法(整数二分)
- 选择中间数mid–作为“性质“性质一定存在于符合条件的区间内
- check(mid)写判断依据作为更新区间的条件
- 更新区间
- 模板 1.(找左边界)
c h e c k > = x m i d = ( l + r ) / 2 i f — t u r e r = m i d f a l s e l = m i d + 1 \ check >=x \\ mid=(l+r)/2 \\ if—ture \qquad r=mid \\ false \qquad l=mid+1 check>=xmid=(l+r)/2if—turer=midfalsel=mid+1
- 模板2.(找右边界)
c h e c k < = x m i d = ( l + r + 1 ) / 2 i f — t r u e l = m i d f a l s e r = m i d − 1 \ check <= x \\ mid=(l+r+1)/2 \\ if—true \qquad l=mid \\ false \quad r=mid-1 check<=xmid=(l+r+1)/2if—truel=midfalser=mid−1
(如果mid=(l+r)/2当r=l+1并以true更新边界时,会进入死循环,,例如x=1,q[1]=1,l=1,r=2更新完之后仍然是l=1,r=2)
所谓的二分算法,就是我们知道当前的候选区间中,一定存在我们要找到的答案,而且我们发现这个区间拥有单调性质此类的性质,那么我们可以不停地缩减候选区间的范围,达到排除无用答案的效果
#include <iostream>
using namespace std;
const int N =1e5+10;
int q[N];
int n,k;
int main(){
scanf("%d %d",&n,&k);
for(int i=0;i<n;i++)scanf("%d",&q[i]);
while(k--){
int x;
scanf("%d",&x);
int l=0,r=n-1;//定义初始的左右边界
while(l<r){
int mid=l+r>>1;
if(q[mid]>=x)r=mid; //此时目标是寻找左边界,因为是整数二分,如果不存在目标值,会找到小于x的最大值
else l=mid+1;
}
if(q[l]!=x)printf("-1 -1\n");
else printf("%d ",l);
int l=0,r=n-1;//重新初始化l,r,找右边界
while(l<r){
int mid=l+r+1>>1;//当更新边界是l=mid时需要+1否则会造成边界问题
if(q[mid]<=x)l=mid;
r=mid-1;
}
printf("%d",l);
}
return 0;
}
浮点数二分
不用考虑边界问题
直接迫近即可
#include <iostream>
using namespace std;
//double 能精确到小数点后15——16位,而float只能精确到6-7位是不太够的
int main(){
double x;
cin>>x>>endl;
//选区间时只要包含答案即可(注意右边界必须大于一,否则求(0,1)区间会越取越大爆范围,只要右区间大于1即可)
double l=-100,r=100;
while(r-l>1e-8)//精确到1e-8
{
double mid=(l+r)/2;
if(mid*mid*mid>=x)r=mid; //因为区间内包含所有数据(不只是整数还有全部小数)所以数据不存在间断,此时用左右模板均可,并且不用考虑边界问题
else l=mid;
}
printf("%6lf",l);
return 0;
}