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

文章详细介绍了快速排序算法的实现,包括两种交换函数的使用,重点分析了Partion函数的逻辑,讨论了如何选择key以避免数组越界问题。同时,提出了通过取三数中值法选取key以提高效率,并引入了前后指针法优化Partion函数。此外,还提到了针对小规模数据使用插入排序以及非递归实现快速排序的方法,以减少递归次数和栈空间的使用。
摘要由CSDN通过智能技术生成
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

狗哥不是甜妹

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

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

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

打赏作者

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

抵扣说明:

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

余额充值