【快速排序&&归并排序&&二分】

目录

快速排序

思路

模板

练习题

 改进

归并排序

思路

模板

练习题

二分

思路

模板


快速排序

思路

利用分治的思想。将待排序的区间分为两部分:全部小于等于一个数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;
}

 

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值