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

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

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狗哥不是甜妹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值