zoukankan      html  css  js  c++  java
  • 排序算法

    快速排序

    思想

      快速排序尤其适用于对大数据的排序,它的高速和高效无愧于“快速”两个字。虽然很快,但它也是逻辑最复杂、最难理解的算法,因为快速排序要用到递归和函数调用。

      快速排序所采用的思想是分治的思想。所谓分治,就是指以一个数为基准,将序列中的其他数往它两边“扔”。以从小到大排序为例,比它小的都“扔”到它的左边,比它大的都“扔”到它的右边,然后左右两边再分别重复这个操作,不停地分,直至分到每一个分区的基准数的左边或者右边都只剩一个数为止。这时排序也就完成了。

      所以快速排序的核心思想就是将小的往左“扔”,将大的往右“扔”,只要能实现这个,就与快速排序的思想吻合。从初学者的角度,“小的往左扔大的往右扔”首先能想到的往往是小的往前插,大的往后插。这确实是一个思路,但我们知道,数组是不擅长插入的。这种思路虽然能吻合快速排序的思想,但实现起来就不是“快速”排序,而是“慢速”排序了。所以这种方法是不可行的。于是就有了下面的“舞动算法”。

      “舞动算法”的思想是用交换取代插入,大大提高了排序的速度。下面首先详细地讲解一下数组快速排序的过程。

    假设序列中有 n 个数,将这 n 个数放到数组 A 中。原始的“舞动算法”中一趟快速排序算法是:

    • 1. 设置两个变量 i、j,排序开始的时候:i=0,j=n–1。
    • 2. 以数组第一个元素为关键数据,赋给变量 key,即 key=A[0]。
    • 3. 从 j 开始向前搜索,即由后开始向前搜索(j--),找到第一个小于 key 的值 A[j],将 A[j] 和 A[i] 互换。
    • 4. 然后再从 i 开始向后搜索,即由前开始向后搜索(i++),找到第一个大于 key 的 A[i],将 A[i] 和 A[j] 互换。
    • 5. 重复第 3、4 步,直到 i=j。此时就能确保序列中所有元素都与 key 比较过了,且 key 的左边全部是比 key 小的,key 的右边全部是比 key 大的。
    • 第一轮比较后序列就以 key 为中心分成了左右两部分,然后分别对左右两部分分别递归执行上面几个步骤,直到排序结束。

    下面列举一个简单的例子,比如对如下数组 a 中的元素使用快速排序实现从小到大排序:

    •   35  12  37  -58  54  76  22
    • 1) 首先分别定义 left 和 right 用于存储数组第一个元素的下标和最后一个元素的下标,即 left=0,right=6。
    • 2) 然后定义 key 用于存放基准数,理论上该基准数可以取序列中的任何一个数。此处就取数组的第一个元素,即把 a[left] 赋给 key。
    • 3) 然后 key 和 a[right] 比较,即 35 和 22 比较,35>22,则它们互换位置:
    •   22  12  37  -58  54  76  35
    • 4) 然后 left==1,key 和 a[left] 比较,即 35 和 12 比较,12<35,则不用互换位置;继续 left==2,然后 key 和 a[left] 比较,即 35 和 37 比较,37>35,则它们互换位置:
    •   22  12  35  -58  54  76  37
    • 5) 然后 right==5,key 和 a[right] 比较,即 35 和 76 比较,35<76,则不用互换位置;继续 right==4,然后 key 和 a[right] 比较,即 35 和 54 比较,35<54,则不用互换位置;继续 right==3,然后 key 和 a[right] 比较,即 35 和 -58 比较,35>–58,则它们互换位置:
    •   22  12  -58  35  54  76  37
    • 6) 然后 left==3,此时 left=right,第一轮比较结束。
    • 从最后得到的序列可以看出,35 左边的都比 35 小,35 右边的都比 35 大。这样就以 35 为中心,把原序列分成了左右两个部分。接下来只需要分别对左右两个部分分别重复上述操作就行了。

      实际上还可以对这个程序进行优化。在快速排序算法中,每轮比较有且仅有一个 key 值,但是 key 值的位置是不断变化的,只有比较完一轮后 key 值的位置才固定。每次执行 swap 时实际上交换的是 key 和 a[left] 或 key 和 a[right],而 key 的位置每次都是不一样的。所以既然 key 的位置是“动来动去”的,所以就不必将它赋到数组中了,等最后一轮比较结束后,它的位置“不动”了,再将它赋到数组中。这样就避免了因冗余的数据交换带来的额外开销。

    针对上面提到的例子,使用改进后的快速排算法进行第一趟排序:

    •   35  12  37  -58  54  76  22
    • 1) 首先分别定义 left 和 right 用于存储数组第一个元素的下标和最后一个元素的下标,即 left=0,right=6。
    • 2) 然后定义 key 用于存放基准数,理论上该基准数可以取序列中的任何一个数。此处就取数组的第一个元素,即把 a[left] 赋给 key。
    • 3) 然后 key 和 a[right] 比较,即 35 和 22 比较,35>22,则此时它们不再互换位置,而是把a[right]赋值给a[left]:
    •   22  12  37  -58  54  76  22  (key=35, 原数据22位置不动)
    • 4) 然后 left==1,key 和 a[left] 比较,即 35 和 12 比较,12<35,则不用变换;继续 left==2,然后 key 和 a[left] 比较,即 35 和 37 比较,37>35,则它们也不再互换位置,而是把37赋值给a[right]:
    •   22  12  37  -58  54  76  37  (key=35, 原数据37位置不动)
    • 5) 然后 right==5,key 和 a[right] 比较,即 35 和 76 比较,35<76,则不用变换;继续 right==4,然后 key 和 a[right] 比较,即 35 和 54 比较,35<54,则不用变换;继续 right==3,然后 key 和 a[right] 比较,即 35 和 -58 比较,35>–58,则把a[right]赋值给a[left]:
    •   22  12  -58  -58  54  76  37  (key=35, 原数据-58位置不动)
    • 6) 然后 left==3,此时 left=right,第一轮比较结束。此时把key值赋值给a[left]:
    •   22  12  -58  35  54  76  37

    快排核心代码

    void sort_Quick(int A[],int left, int right)
    {
        // 排序完成的标志
        if (left >= right)
            return;
        int i = left;
        int j = right;
        int key = A[left];
    
        while(i<j)
        {
            while (i<j && key<=A[j])
            {
                j--;
            }
            A[i] = A[j];
            while (i<j && key>=A[i])
            {
                i++;
            }
            A[j] = A[i];
        }
        A[i] = key;
       
        sort_Quick(A, left, i-1);
        sort_Quick(A, i+1, right);
    }

     注意:参考博文[2]中采用了与本文不同的数据交换方式

    博文[2]中所采用的标志位是从a[1]开始,而不是a[0]。而且直接交换第一个大于key值的a[i]与第一个小于key值的a[j]

    本文算法实现(C语言)

    # include <stdio.h>
    # define Max 7 // 待排数组长度
    int A[Max];
    void sort_Bubble(int A[]);
    void show_A(int A[]);
    void iput_A(int A[]);
    void sort_Quick(int A[], int left, int right);
    
    int main(int)
    {
        iput_A(A);
        // sort_Bubble(A);
        sort_Quick(A,0,Max-1);
        show_A(A);
        return 0;
    }
    
    void show_A(int A[])
    {
        int i;
        for (i=0; i<Max; i++)
        {
            printf("%d ",A[i]);
        }
        printf("
    ");
    }
    
    void iput_A(int A[])
    {
        int i;
        for (i=0; i<Max; i++)
        {
            scanf("%d",&A[i]);
        }
    }
    
    void sort_Bubble(int A[])
    {
        int i, j,temp;
        for (i=1; i<Max; i++)
        {
            for (j=0; j<i;j++)
            {
                if (A[i] < A[j])
                {
                    temp = A[i];
                    A[i] = A[j];
                    A[j] = temp;
                }
            }
        }
    }
    
    void sort_Quick(int A[],int left, int right)
    {
        // 排序完成的标志
        if (left >= right)
            return;
        int i = left;
        int j = right;
        int key = A[left];
    
        while(i<j)
        {
            while (i<j && key<=A[j])
            {
                j--;
            }
            A[i] = A[j];
            while (i<j && key>=A[i])
            {
                i++;
            }
            A[j] = A[i];
        }
        A[i] = key;
        show_A(A); //打印当前的数组
        sort_Quick(A, left, i-1);
        sort_Quick(A, i+1, right);
    }
    

    参考博文

    [1]快速排序算法,C语言快速排序算法深入剖析

    [2]理解快速排序算法

    本人计算机小白一枚,对编程有浓厚兴趣,在此贴出自己的计算机学习历程,还有很多不足,望多多指教! 读书后发现好多的内容与具体专业有偏差,没来得及完成,虽然“有时间我就会做...”是人生最大的谎言,但有时间我会继续搞定未完成的内容,有始有终,兴趣使然!
  • 相关阅读:
    Haskell语言学习笔记(76)Data.Tree
    C++17尝鲜:编译期 if 语句
    C++17尝鲜:variant
    Haskell语言学习笔记(75)Conduit
    C++17尝鲜:string_view
    Haskell语言学习笔记(74)GADTs
    Haskell语言学习笔记(73)Existentials
    Haskell语言学习笔记(72)Free Monad
    sum of powers
    「2017 山东一轮集训 Day7」逆序对
  • 原文地址:https://www.cnblogs.com/Robin5/p/11605005.html
Copyright © 2011-2022 走看看