快排和归并

注:不适合对完全没学过的,仅供自己发现问题后重新找找是不是以前的问题。

突然发现堆排竟然被放在了useless里面hhhhh,那就先不学吧,掌握好快排和归并

 快排

分治与递归结合

快排基于分治

简要说下之前对快排的误解:

1. 为什么要分两边

   + 首先这样子做可以把比这个数小的重新排序,比这个数大的也重新排序

   + 如果按照自己以为的,快排就会出现最小值或最大值恰在一端时的死循环。(左key右先左最小卡死,右key左先右最大卡死,不信可以试试)

   + 所以我们将其分为两块重新排序。

   + 相对来说比分块要占用的少,不容易出现段错误(RE)

2. 左边key为什么一定要从右边开始

   + 因为如果左指针先走,找到了位置,然而右指针却没找到比key小的值,这时候就会发生key值与右指针所指交换,这是错误的,所以我们取一边为key,让另一边先走。

3. 最后的分组到底怎么分,为什么不是左+右的和除2分开

   + 因为此时begin已经到达了此处了,我们不需要再计算一遍,此乃其一

   + 其二是真的正好是在中间吗?不能在边上吗?为何一定在中间呢?快排可是存在一圈没找到key恰为最小这种情况的,怎么能直接平分中间呢?

   + 所以必须如此,也最方便,毕竟此时begin==end,以其平分,这个数正好就在这个位置,所以一侧-1,一侧+1,不要把这个数字带进去。

自己敲的代码,算是半个独立吧:

如果你是用c写的:

1.头文件iostream换stdio

2.cin换scanf()

3.cout换printf()

4.swap用指针写

#include<iostream>

#include<time.h>

#include<stdlib.h>

using namespace std;



int a[1000];



void swap(int &a,int &b){

    int temp=a;

    a=b;

    b=temp;

    return ;

}



void quicksort(int left,int right){

    if(left>=right){

        return ;

    }

    int begin=left,end=right;

    int key=a[begin];

    while(begin<end){

        while(a[end]>=key&&begin<end){

            --end;

        }

        while(a[begin]<=key&&begin<end){

            ++begin;

        }

        swap(a[begin],a[end]);

    }

    swap(a[begin],a[left]);

    quicksort(left,begin-1);

    quicksort(begin+1,right);

    return ;

}



int main(){

    int n;

    cin>>n;

    srand((int)time(0));

    for(int i=0;i<n;i++){

        a[i]=rand()%100+1;

    }

    quicksort(0,n-1);

    for(int i=0;i<n;i++){

        cout<<a[i]<<' ';

    }

    return 0;

}

归并

归并排序,采用的也是分治和递归的思想

当我把一堆数据无限细分成单个,此时我就可以两两比较放局部有序,然后递归回去排就行

所以整个算法实际上分拆到最后不可再拆和归并

还是先提注意事项:

1. + 不要理解错误,不是让把原来的数组真正物理意义上的分开,我们完全可以限定区间操作做到逻辑意义上的分开。

   + 真正需要开辟空间的是我们每次取元素后要放的位置,而不是开数组放原来数组内容。原来的数组此段是我们即将放入局部有序序列的容器。

2. + 归并的范围,这里好好说说,毕竟是写过一次后开始自己推敲了半个多小时的。

   + 首先,我最后一定要把这个东西给分为单个元素,一定一定是单个元素,否则两个元素的话,比如91,那么我插入顺序就是9和1,不一定会是有序的。

   + 所以我们一定一定要把范围确定好。什么时候结束?当这个东西,范围恰好只有一个,即【0,0】这样子的,此时作为结束条件,即left>=right

   + 为什么在这个时候作为结束条件呢?因为首先,这个区间不能再分了,否则可以无限循环的,是个死循环。其次,我只要让其一个元素为1了,另一个元素也分为1或者不为1但再分为1,就可以作为局部有序了。一个元素必定有序啊。

   + 所以我们返回后,取中间,这样两边都是1个元素,这样的话,那么就可以进行分治了

   + 我们取中间,一段为中间-左端+1,一段为右边-中间。为什么呢,因为本身都是+1,但是右端需要往右一下,所以少了那个1。拿实例说话,比如【0,2】这个区间,这时候一共可是3个元素,所以取中间后是0-1,因为c么,从0计数,所以是两个元素了,那么就是1-0+1,即取n1=mid-left+1,n2=right-mid。实际上为right-(mid+1)+1,为mid右移一位后right-mid加1,所以就直接right-mid了。

3. + 划分好区间后就开始局部排序了。这时候首先我要开辟空间,大小上文提到了,不多说。

   + 两个数组要分别获取对应区间中的元素,如上文所说以mid为中心分开,至于取的a的元素,自然也就与此时的区间有关。前面一个区间从left开始取,每次+i,后面一个区间,如上文所说,从mid+1开始取,然后+i。当然你这里都可以换成j,没有影响。

   + 然后是排序,统一方法是比较俩个数组,找到较小的添上去,较大的不变。由于我们在排序之前的两个区间都是局部有序,那么我们可以只比较首元素就行。

   + 注意比较首元素的时候需要一个i一个j,不能合在一块儿。

   + 而被赋值的a的起始左边为什么呢?为left,令一个数等于left后就让他往后动就行,否则你只打算让数组在某一个地方来回动么?

   + 一方率先空后,另一方把剩下的全部按顺序加入数组中。

   + 记得解放空间。

嫑废话,上代码

#include<iostream>

#include<cstdio>

#include<cmath>

#include<cstdlib>

#include<malloc.h>

#include<ctime>

using namespace std;



int a[1000];



void merge(int left,int right){

    int i,j,k=0;

    int mid=(left+right)/2;

    int n1=mid-left+1;

    int n2=right-mid;

    int *front=(int *)malloc(n1*sizeof(int));

    int *base=(int *)malloc(n2*sizeof(int));

    for(i=0;i<n1;i++){

        front[i]=a[i+left];

    }

    for(i=0;i<n2;i++){

        base[i]=a[i+mid+1];

    }

    i=0;

    j=0;

    k=left;

    while(i<n1&&j<n2){

        if(front[i]<base[j]){

            a[k++]=front[i++];

        }else{

            a[k++]=base[j++];

        }

    }

    while(i<n1){

        a[k++]=front[i++];

    }

    while(j<n2){

        a[k++]=base[j++];

    }

    free(front);

    free(base);

    return ;

}



void mergesort(int left,int right){

    if(left>=right){

        return ;

    }

    mergesort(left,(left+right)/2);

    mergesort((left+right)/2+1,right);

    merge(left,right);

}



int main(){

    int n;

    cin>>n;

    srand((int)time(0));

    for(int i=0;i<n;i++){

        a[i]=rand()%250+1;

    }

    mergesort(0,n-1);

    for(int i=0;i<n;i++){

        cout<<a[i]<<' ';

    }

    return 0;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值