快速排序:
核心思想:分治
步骤:
1.确定分界点 $x$ 。
2.调整范围:该点x,将数划分为两边(左边都小于等于x,右边都大于等于x)。
注意:并不是把所有小于等于x的数都放在左边和把所有大于等于x的数都放在右边,例如当数等于x时左右两边都可以放。
3.递归处理左右两边。
但是怎么样去调整范围呢?(怎么样处理步骤二)
方法一:
可以先定义两个数组a[ ],b[ ],然后将数的范围里面小于等于x的数放在a[ ],将大于等于x的数放在b[ ]然后依次将a,b放入原数组中即可
方法二:
由于上面的方法过于暴力。为了显得优美些,我们可以设定两个指针 i和 j分别位于数组开头和末尾,并分别向中间靠拢。当 i指针指向的数大于等于x且 j指针指向的数小于等于x时则俩数交换直到 i和 j相遇为止。
那我们不妨设x=3,数据为3 1 2 3 5,红箭头为i,绿箭头为j ,则有:
当 j指向的数大于等于x时,指针向左移动一位。此时 j指向的数等于x,i指向的数也等于x,则两数交换(注意此处两数相等也要交换)。
当 i指向的数小于等于x时,指针向右移动一位。此时 j指向的数小于x,则不动。
此时 i指向的数小于x,向右移动一位。
此时 i指向的数小于x,向右移动一位。
两指针相遇后停止,这样我们两边的数都分别小于等于x和大于等于x了。
快排模板:(注意边界问题)
void q_sort(int a[],int l,int r){
if(l>=r)return;
int x=a[(l+r)/2],i=l-1,j=r+1;
while(i<j){
do i++;while(a[i]<x);
do j--;while(a[j]>x);
if(i<j)swap(a[i],a[j]);
}
q_sort(a,l,j);
q_sort(a,j+1,r);
}
【代码实现】:
#include<iostream>
using namespace std;
const int N=1e6+10;
int a[N];
void q_sort(int a[],int l,int r){
if(l>=r)return;
int x=a[(l+r)/2],i=l-1,j=r+1;
while(i<j){
do i++;while(a[i]<x);
do j--;while(a[j]>x);
if(i<j)swap(a[i],a[j]);
}
q_sort(a,l,j);
q_sort(a,j+1,r);
}
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
q_sort(a,0,n-1);
for(int i=0;i<n;i++){
printf("%d ",a[i]);
}
return 0;
}
归并排序:
核心思想:分治
步骤:
1.确定分界点 x=(l+r)/2 (快排是将分界点设为数组里随机的一个数,而归并是将分界点设为数组中间下标的值)
2.将数组一分为二,一个无序的数组成为两个数组.
3.合二为一,将两个有序数组合并成为一个有序数组.
(如动图所示)
归并排序里较为重要的一步就是第三个步骤:
在一二步完成之后,我们已得到两个有序数组,便于比较我们可以设两个指针分别指向两个数组的最左端(最小值的一端)。
并比较两指针所指的数 ,将更小的数放入一个新数组中,然后将该指针往后挪一步。
我们不妨用图片表示:
设有两个有序数列:
上面数组的指针更小,则往后移一位,并存入新数组中。
以此类推
‘当其中任意一个数组走完了,则将另一个数组全部放入新数组中
注意每次指针所指向的数都是该序列中的最小数。
【代码实现】:
#include<iostream>
using namespace std;
int n;
int tmp[1000010];
int q[1000010];
void m_sort(int l,int r){
if(l>=r)return ;
int mid=(l+r)>>1;
m_sort(l,mid),m_sort(mid+1,r);
int k=l;
int 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(int i=l;i<=r;i++)q[i]=tmp[i];
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
scanf("%d",&q[i]);
}
m_sort(0,n-1);
for(int i=0;i<n;i++){
printf("%d ",q[i]);
}
}
二分:
例题:数的范围
核心:边界问题
步骤:
1.确定分界点mid。mid= ( left + right (+1) ) / 2,视情况定要不要加1。
2.写两个二分,一个用来找到>=x的第一个数
当 a[ mid ]>=x 时,说明 mid 及其左边可能含有值为x的元素。
当a[mid]<x时,令l=mid+1,mid及其左边的位置被排除了,可能出现解的位置是mid+1及其后面的位置。
【模板】
int l=0,r=n-1;
while(l<r){
int mid=(r+l)>>1;
if(a[mid]>=x)r=mid;
else l=mid+1;
}
3.第二个用来找到<=x的最后一个数
【模板】
int l=0,r=n-1;
while(l<r){
int mid=(l+r+1)>>1;
if(a[mid]<=x)l=mid;
else r=mid-1;
}
注意l=mid要写成(l+r+1)>>1。因为当 l =r-1时,mid就会等于l而导致死循环
每次使用这这两个模板的时候,先想是找这个区间的左端点还是还是右端点,然后选择用模板,最后再去写判断条件
【代码实现】:
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int a[N];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
}
while(m--){
int x;
scanf("%d",&x);
int l=0,r=n-1;
while(l<r){
int mid=(r+l)>>1;
if(a[mid]>=x)r=mid;
else l=mid+1;
}
if(a[l]!=x)cout<<"-1 -1"<<endl;
else
{
cout<<l<<" ";
int l=0,r=n-1;
while(l<r){
int mid=(l+r+1)>>1;
if(a[mid]<=x)l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
}
return 0;
}