算法学的实在是太la了,所以就要从头开始再来一遍,加油!
快排和归并都用到了分治的思想
快速排序:
最开始,随机选取一个值x作为分界点,一般是选择数组下标为中间值的那个数(即x=q[l+r>>1]),用到双指针i和j,i指向最左端元素的左边一个位置(即i=l-1),j指向最右端元素的右边一个位置(即j=r+1),接下来开始指针移动,满足q[i]<x,i++;满足q[j]>x,j--,就这样一直移动,直到i和j相遇,有些解释看下边的快排代码模板。
代码模板如下:
void quick_sort(int q[],int l,int r)
{
if(l>=r) return;
int x=q[l+r>>1],i=l-1,j=r+1;//l+r>>1等价于(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]);/*要满足x左边的数都小于x,x右边的数都大于x,所以在上边i和j都
停下来时(就是说i指向的数大于或等于x了,j指向的数小于或等于x了),就要交换q[i]和q[j],
保证满足上边的条件。*/
}
quick_sort(q,l,j);//虽然说是分左右两段在排,但是这边用的是j,因为有些情况下i会错误,而且y总的模板还是要得背会
quick_sort(q,j+1,r);
}
下面是一道例题:785. 快速排序 - AcWing题库
#include<iostream>
using namespace std;
const int N=1e6+10;
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(q[j]>x);
if(i<j) swap(q[i],q[j]);
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
int n,q[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&q[i]);
quick_sort(q,1,n);
for(int i=1;i<=n;i++) printf("%d ",q[i]);
return 0;
}
归并排序:
最开始,找下标为中间值的数作为分界点(即x=q[l+r>>1]),接着分别递归排序左边,右边,即merge_sort(q,l,mid),merge_sort(q,mid+1,r),最后归并,这里边还是需要用到两个指针i,j,另外需要定义一个临时数组temp[],还有该数组下标,i指向第一段的开头,j指向第二段的开头,比较q[i]和q[j]的大小,先把小的放在temp里面,然后指针向后挪,直到有一个指针到头了,就停止。最后把还没有填入temp中的数给填进去。最后一步就是把temp里的数给物归原主到q里边。
代码模板如下:
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];//事实上,这个一直没整大明白
}
下面是一道例题:787. 归并排序 - AcWing题库
#include<iostream>
using namespace std;
const int N=1e5+10;
int n;
int q[N];
int temp[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]) temp[k++]=q[i++];
else temp[k++]=q[j++];
}
while(i<=mid) temp[k++]=q[i++];
while(j<=r) temp[k++]=q[j++];
for(int i=l,j=0;i<=r;i++,j++) q[i]=temp[j];
}
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;
}
二分:
(1)整数二分:
模板很重要,二分有两个模板,(二分是一定有解的,但是题目不一定有解)要知道如何选择。如果有单调性一定可以二分,但可以二分的题目不一定非得有单调性,事实上二分的本质是将整个区间一分为二,找到一个性质是一半满足,一半不满足。关键是区间边界更新,先写一个check函数,根据这个函数看如何更新区间,如果更新区间为l=mid,r=mid+1那么前面mid要写(l+r+1)/2(加1是怕死循环);如果更新区间为r=mid,l=mid+1,那么前面mid就是(l+r)/2。事实上这个二分还真得自己悟,就是更新区间的问题,多做题。
下面是一道例题:789. 数的范围 - AcWing题库
#include<iostream>
using namespace std;
const int N=1e5+10;
int n,m,q[N],x;
int main()
{
int mid;
scanf("%d %d",&n,&m);
for(int i=0;i<n;i++) scanf("%d",&q[i]);
while(m--)
{
scanf("%d",&x);
int l=0,r=n-1;
while(l<r)
{
mid=(l+r)/2;
if(q[mid]>=x) 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)
{
mid=(l+r+1)/2;
if(q[mid]<=x) l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
}
return 0;
}
(2)浮点数二分:
浮点数比较简单,因为他不用处理边界
下面是一道例题:790. 数的三次方根 - AcWing题库
#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(mid*mid*mid>=n) r=mid;
else l=mid;
}
cout<<l<<endl;
return 0;
}