快速排序(超超详细,因为自己也不是很会)

简介: 快速排序(超超详细,因为自己也不是很会)
void Swap(int*a,int*b){          //第一种方法
    int tmp=*b;
    *b=*a;
    *a=tmp;
}
void Swap(a,int left,int right){      //第二种方法,但是不好
    int tmp=a[left];
    a[left]=a[right];
    a[right]=tmp;
}

交换函数,避免代码重复写,导致代码冗余。(key和本文的keyi一个意思)

这里有个很重要的建议,交换还是建议拿第一个,因为换成数组有最重要的数组越界问题,而且很不容易去查看,所以一律推荐用第一个交换

//老版本快排
int Partion(int*a,int left,int right){
    int keyi=left;
    while(left<right){
        while(a[right]>=a[keyi]&&left<right){               
           -- right;           (右边是找小,所以比他大的直接跳过)
        }
        while(a[left]<=a[keyi]&&left<right){
            ++left;}
        Swap(&a[left],&a[right]);
    }
    Swap(&a[left],&a[keyi]);
    return left;
    }

快排排序思路

                   1.如果选最左边的做key,右边先走(这样会使相遇的时候数字小于key)

  选择最右边做key,左边先走(相遇数字大于key)(右边做keyi,需要左边先走:为了保证相遇的时候大于keyi)

  分为两个一个是L,一个是R,这里假设是左边作为key。

   R先走,遇到比key小的(比key大就一直走)就停下,然后L开始走找到比Key大的就停下,            然后交换R和L,然后继续R开始继续找比他小的,然后停住,L开始走然后接着找大的,互相换位置,最后把key和a的L也就是剩下的那个,换位置。

   注意一件事:上述代码一个最重要的就是:改了两天,连带问人,突然发现这里错误了

while(left<right){
       while(a[right]>=a[keyi]
&&left<right),因为有可能在下面这层循环中,right--和left++;

都有可能出现加冒了的情况。

Partion:这个函数是来干嘛的,我认为可以认为他是预排这种,在区间内部分有序,也就是这个一次后会变成3,1,2,5,4,6,9,7,10,8。在keyi之前都比keyi小,keyi之后都比他大。

void QuickSort(int*a,int left,int right){
    if(left>=right)
        return ;
        int keyi=Partion(a,left,right);
    QuickSort(a, left, keyi-1);
    QuickSort(a, keyi+1, right);
}
int main(){
    int a[10]={6,1,2,7,9,3,4,5,10,8};
    QuickSort(a, 0, 9);
    for(int i=0;i<10;i++){
        printf("%d",a[i]);}
    return 0;
}

QuickSort:这个是递归,一次递归只能保证所谓的keyi在正确的位置,那么也就是我们只要保证在小区间内,分别确定好位置,也就是在多个相对有序,实现有序,先是0-9这个区间,然后

3,1,2,5,4,6,9,7,10,8              此时的keyi是5,然后是进入小区间0-4,再次进行排序

0    1    2    3     4    5    6    7    8    9              3为keyi那么,排好序的下一步结果是1,2,3,5,4

              (下面的是序号)                                                                                           0,1,2,3,4

也就是(此时要记住是主要的位置也就是left是0,keyi-1为1),然后将下标0-1排,3,4排,

0-1的区间右变为【0, -1】和【1,1】,然后慢慢返回。(这里说一下返回 5和4(不是下标)的情况,左边的5是keyi也是left,然后left++到了4的值,所以5和4互换。一次次的递归,这样就会使顺序变为有序。

    讲讲快排的优点

                     1.只要你要排序的数字越乱越好,越乱越快,当keyi一直是中位数的时候最快

不断分为小区间,那么这个东西就很像我们之前的二分查找那种东西,O(n*logn)

logn层(二叉树哈)n个来调整

          但是缺点也明显假如说数很整齐,图不补全了,毕竟没啥必要,切一块就明白,和没切没有区别,那就是说相当于还是一个一个排(而且用的是递归:肯定递归是比直接还是性能差那么一点,其次这么分会使递归次数太多,导致栈调用次数过多,会栈溢出。)

    那么我们应该如何确保keyi一直是中位数呢:

int Middle(int*a,int left,int right){
    int mid=left+(right-left)/2;//防止越界
    int i=0;
   //非常重要的想法
   int max1=a[left]>a[mid]?a[left]:a[mid];
    max1=max1>a[right]?max1:a[right];
    int min1=a[left]>a[mid]?a[mid]:a[left];
    min1=min1>a[right]?a[right]:min1;
    int mid1=a[left]+a[right]+a[mid]-max1-min1;
    for(i=0;i<10;i++){
        if(a[i]==mid1){
            return i;
            break;}
        else{
            continue;
        }
    }
    return 0;
}

之前看视频中的讲解,取中太过浪费时间和消耗逻辑,不如这样,三个数相加减去最大和最小的就会剩下中间的,由于我们要求的是下标,所以要查找一下下标。然后返回相对应的下标。

这样就会尽量避免上面那种很少很少的分割这种情况。

调用的递归核心不变,但是调位置换为填坑法

int Partion2(int*a,int left,int right){
    int mid=Middle(a, left, right);
    Swap(&a[mid],&a[left]);
    int key=a[left];
    int pivot=left;
    while(left<right){
        while(left<right&&a[right]>=key){
            --right;
        }
        a[pivot]=a[right];
        pivot=right;
        while(left<right&&a[left]<=key){
            ++left;
        }
        a[pivot]=a[left];
        pivot=left;
    }a[pivot]=key;
    return pivot;
}

pivot为坑,选择最左边的第一个数据作为keyi和坑,然后R先走找小于keyi的,将坑和R换位置,L开始走,找大于keyi的,然后L和坑再次换位置

         其实细细想一下,坑也就代表一个位置也不能说算是空,只能说算是(仍为原来的数,而不是空)被覆盖了,把值和下标都给找到的;R开始是找到5 然后坑到5这个位置,同时坑的下标变为7(原来5的位置),那么原先坑的的值变为5,也就是说换的是坑的位置,而不是坑的值,L在开始找到7,坑又和7换,换完7,再换4,后换9,最后换3,坑在3这个位置,然后坑和keyi换,返回坑的位置,其实自己一想和上面那种排序差不多含义,就是实现的方法思路有一丢丢不同。

          方法3:前后指针法

int Partion3(int*a,int left,int right){
    int keyi=left;
    int prev=left;
    int cur=prev+1;
    while(cur<=right){
        if(a[cur]<a[keyi]&&++prev!=cur){           //(++prev!=cur是不让他和自己换,浪费时间
            Swap(&a[cur],&a[++prev]);
        }
        ++cur;
    }
    Swap(&a[prev],&a[keyi]);
    return prev;
}

cur遇到比keyi小的就停下来,++prev然后交换cur和prev的值, 刚开始cur和prev相差一个,然后cur开始,因为prev++后和cur相同,不交换,到了7以后就开始拉开身位了,cur到了3的位置,然后prev还是在2,prev++变为7,然后3和7换,cur到了4,4和9换,cur到5和7换(原来是3的位置),cur到8的位置走完,然后prev(最后在5(开始的3)的位置)和6换

小区间优化

void QuickSort(int*a,int left,int right){
    if(left>right)
        return ;
    if(right-left<10){
        Insertsort(a+left,right-left+1);
    }
    int keyi=Partion3(a,left,right);
    QuickSort(a, left, keyi-1);
    QuickSort(a, keyi+1, right);
}

看这张图就可以知道,递归次数多是分散到各个小区间里,所以小区间会导致递归次数很多,所以我们将最底下那些递归次数最多的小区间,都换为(相对有一定顺序了)直接插入很好用。

非递归的快排(马上会把栈搞一手,学的时候还没有搞这个栈)

void QuickSortNonR(){
    ST st;
    StackInit(&st);
    Stackpush(&st,left);
    StackPush(&st,right);
    while(!StackEmpty(&st)){
        int end=StackTop(&st);      (取当前栈顶数字)
        StackPop(&st);               (删除当前栈顶数字
        int begin=StackTop(&st);
        StackPop(&st);
        int keyi=Partion3(a,begin,end);
        if(keyi+1<end){
            Stackpush(&st,end);        (插入栈顶数字)
        } 
        if(begin<keyi-1){
            Stackpush(&st,begin);
            StackPush(&st,keyi-1);
        }
}
}

这是通过栈来进行操作,后进的会先出来,(说得都是下标)所以先插入0-9(这里有一个删除,但是删除之前存了他们的数)然后取出他们分别的下标0-9

然后要想从左边开始排就要,先插入6-9,然后插入0-4,对0-4,再次分开删除0-4换为(应该正常是分为3-4和0-1,然后0-1不进入栈顶)但是我们这个例子中因为keyi会有一点小区别,会0-3,0-2,0-1不进入栈顶走完

进入栈顶指的是最后两个if

                   总结:快排很好,真的蛮费劲的,一起加油

                      章诺楠yyds


相关文章
|
7月前
快速排序
快速排序
26 0
|
7月前
|
搜索推荐 C++
C++快速排序的实现
C++快速排序的实现
|
7月前
|
算法
快速排序(三)——hoare法
快速排序(三)——hoare法
82 1
|
C++
C++快速排序
C++快速排序
60 1
|
算法 搜索推荐 测试技术
快速排序详解
快速排序详解
126 0
重新理解快速排序
重新理解快速排序
59 0
|
机器学习/深度学习
785. 快速排序
785. 快速排序
70 0
【1. 快速排序】
思路: > 1. 确定分界点:q[l] , q[(1 + r)/2] , q[r] , 随机 > 2. 调整区间的范围:使得在`分界点左侧是数都<= x`, `分界点右侧的数都>= x `(`重点处理`)
92 0
【1. 快速排序】