先分治后递归的思想推导快速排序的代码,告别不理解地背模板

一.摘要:

如何给一组数据按照从小到大的顺序排序?显然将这些数据存放到一个数组中,然后对这个数组进行排序是较为简单的做法.但是如何对一个数组进行排序呢?这就催生了很多排序算法,写这篇博客的目的就是为了讲解快排代码的推导过程.快排是英国计算机科学家东尼.霍尔想出来的,虽然不知道他是怎么想出来的,但是我们可以按照我们的理解去推导一下.推导的大致思路就是先进行分治而后进行递归.下面来看详细推导过程吧.(参考:y总的视频讲解中加入了自己的一些理解)

二.拿到任意一个数组怎么排序---->分治+递归

关键问题是你要怎么设计一个函数对这个数组进行排序呢?我们知道一个函数分为四部分(返回值类型 函数名 形参列表 以及函数体) 既然是排序,就没必要返回某些数据,直接将这个数组排好序就行,然后结束函数,回到调用这个排序函数的语句,继续执行main函数下面的语句就可以了,所以返回值类型是void. 那给这个函数起个什么名字呢?这个就无所谓了,不妨就叫quick_sort吧. 那形参列表怎么设置呢?这就麻烦了,这需要推导一下大致的排序过程才可以确定下来.同样函数体也需要知道排序的大致过程才可以写出来.接下来跟随我来进入推导的过程吧,希望对你有所启发.

试想一下,拿到一个数组,里面有未知个数据,假设计算机有思想的话,它会怎么做呢?假设你已经写好了一个排序函数,并且调用了这个函数,让计算机去执行排序的任务.你肯定要传递一个数组作为实参,函数中用一个数组或者指针作为形参接收实参.计算机肯定事先不知道你要它排序的数组到底有多少数据.它会先去判断一下, 怎么判断呢?这时候你就不能仅仅传递一个实参了,还应该在传递两个实参用于判断数组元素个数是不是小于等于1的,一个实参是左边界的下标,即零.另一个实参是右边界的下标n-1,(n是数组的个数),如果0==n-1,只有一个元素,若0>n-1,说明没有元素即(n==0,n-1==-1<0)      如果你要它排序的数组只有一个元素或者没有元素,那么这个数组就是有序的就没必要排序了,排序函数就可以结束了,即return.但是大部分情况下数组中数据有很多,这时候计算机该怎么做呢?对这个数组进行排序,即最终的结果是数组中每个元素都是从小到大的一个顺序,这有点复杂,不妨将数组的左边界元素作为分界的基准,而后将这个数组分成两个子区间,左边区间的数据都是小于等于基准元素,右边区间的数据都大于等于基准元素.可以发现这两个子区间本身未必是有序的,因为我们没有对子区间进行排序.但是将子区间看作是一个整体的话,这两个子区间就是从小到大的顺序.这时候,我们惊讶地发现,这个数组要想有序,只需要每个子区间都是从小到大的有序区间可以了.                                                                                                                                                   所以对这个数组排序的问题就转化为了分别对这两个子区间进行排序的问题了.子区间也是一个数组呀,所以对数组的排序,计算机呢,先去判断一下这个数组有几个数据,如果发现只有一个数据或者没有数据,那么这个数组,即子区间就是有序的.如果这个数组中数据的个数大于等于2呢?就先把这个数组以左边界的元素作为基准分成两个子区间,左边区间的所有数据都小于等于基准元素,右边区间的所有数据都大于等于基准元素.每个子区间都看作是一个整体,这两个子区间就是有序的,那么这个数组的有序就等价于每个子区间都是有序的, 那就接着再对子区间进行排序.好了,推导到这里,我们惊讶地发现,似乎在循环,即对一个给定数组排序,如果只有一个元素或者没有元素,那就是有序的,否则的话,以数组左边界的元素作为基准将这个数组分成两个子区间,左边区间的元素小于等于基准元素,右边区间的元素大于等于基准元素,将每个子区间都看作是一个整体,虽然子区间本身未必是有序的,但是这两个子区间是从小到大的顺序,整个数组要想有序,只需要每个子区间本身是有序的就可以.问题就转化为对子区间的排序,这不就是分治吗!而将子区间排成有序的方法和这个数组是一样的,直到分成的子区间中元素个数是一个或者是零个,子区间就是有序的了,这不就是递归吗!

好的,现在我们回到函数的设计上,之前我们已经设计好了函数的返回值类型,函数名,还剩下形参列      表和函数体没有完善,现在我们将这个函数的形参列表和函数体完善,那么这个函数我们就设计好了.我们知道排序的过程,先判断数组的元素个数以确定它本身是不是有序的,这时候我们需要传递三个实参(数组名,左边界的下标,和右边界的下标),那么形参就需要三个(指针或者数组   int型left,int 型right),接下来看看函数体如何完善, if (left >= right) return ; 接着回忆排序的过程,如果数组的数据个数超过了一个,先将数组分成两个子区间,如何分呢,代码怎么写呢?先看模型图,   数组名为q,小写的L和r 分别是左边界的下标, x 是int 型变量,存储左边界元素q[L]作为基准,i 和 j 是指针,其实是int 型变量,用来表示数组的下标,初始位置分别指向数组的两边,而非边界,即i = L - 1, j = r + 1;.图中有步骤的顺序,下面大家可以看着文字配合图来理解.第一步,先移动指针i ,  即自增++,然后判断,即如果i 所指向的元素小于基准x,继续向中间移动,直到所指向的元素大于等于x停下来,代码就是while(q[++i] < x);  i 停下来后,移动j , 即自减--, 然后判断,即如果j 所指向的元素大于x, 继续向中间移动, 直到所指向的元素小于等于x停下来 , j 的代码为while(q[--j] > x);这时候i 和 j 都停下来了,如果i 和 j 没有相遇过(分为两种情况,停在同一位置,或者二者穿过,即i >= j) , 就交换i 和 j 所指向的数据, 代码为if(i < j) { int t = q[i] ; q[i] = q[j] ; q[j] = t;} 然后i 接着移动,而后判断,(注意:不是先判断再移动,这也是为什么i 和 j 的初始值不是左右边界的下标, 就是因为先移动再判断) , 直到停下来, 这时候该j 移动了, 然后j 停下来,即图中的第四步.先观察一下图, 这时候i 左边的元素都是<= x 的,j 左边的元素都是>= x 的, 判断i 和 j 有没有相遇过, 如果没有继续, 移动i , 然后移动j , 直到i 和 j 相遇, 即i > =j, 因为只有当i >= j 的时候,我们才完成了分区, 图中显示的是i == j 的情况, 说明二者都在这里停下来,这个元素即>=x 又<= x, 那么这个元素就等于x. 如果这个元素大于了x , i 已经在这里停了下来, 当j 过来的时候由于这个元素> x ,所以j 不会停 , 会继续走,穿过i , 走到i 的左边, 但是我们直到i 的左边的所有数据都是<=x 的, j 一旦穿过i 就会立即停下来, 这时候i > j, 不管是i==j 还是 i>j , 只要不是i<j , 我们就完成了分区, j 就可以作为分界的标准, 因为j 所指向的元素及其左边的元素都是<=x , j 右边的元素都是>= x, 所以左区间就是L到j , 右区间就是j + 1 到 r. 所以这段代码为

while (i < j)
{
    while (q[++i] < x);
    while (q[--j] > x);
    if (i < j)
    {
        int t = q[i];
        q[i] = q[j];
        q[j] = t;
    }    
}

    然后接着对每个子区间进行排序就好了,由于排序过程和这个数组的过程一样,那就递归调用这个函数就好了,下面来看完整代码,这篇博客的代码我都是用c写的(模仿的y总)

#include <stdio.h>

int n, q[1000000];//要录入数据的个数和一个数组接收数据

void quick_sort(int q[], int l, int r)
{
    if(l >= r) return ;//说明数组只有一个元素或者没有数据,那么就是有序的,直接return

    int x = q[l], i = l - 1, j = r + 1;

    while (i < j)
    {
        while (q[++i] < x);
        while (q[--j] > x);
        if (i < j)
        {
            int t = q[i];
            q[i] = q[j];
            q[j] = t;
        }
    }

    quick_sort (q, l, j);//递归左区间
    quick_sort (q, j + 1, r);//递归右区间
 }
 
int main ()
{
    scanf ("%d", &n);
    for (int i = 0; i < n; i++) scanf ("%d", q + i);

    quick_sort (q, 0, n - 1);

    for (int i = 0; i < n; i++) printf ("%d ", q[i]);

    return 0;
}

                                                                                                                                     

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值