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

    快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(nlogn) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实作出来。

     使用快速排序法对一列数字进行排序的过程

    本文地址:http://www.cnblogs.com/archimedes/p/quick-sort-algorithm.html,转载请注明源地址。

    算法原理

    快速排序采用一种“分而治之、各个击破”的观念。

    快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。

    步骤为:

    1、从数列中挑出一个元素,称为 "基准"(pivot),

    2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。

    3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

    递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。

    算法实现

    C代码如下:

    // Completed on 2014.10.9 19:09
    // Language: C99
    //
    // 版权所有(C)codingwu   (mail: oskernel@126.com) 
    // 博客地址:http://www.cnblogs.com/archimedes/
    #include <stdio.h>
    #include <stddef.h>
    void swap(int * a, int * b)  //交换函数
    {  
        int tmp = * a;
        * a = * b;
        * b = tmp;
    }
    void printArray(int a[], int count)   //打印数组元素
    {
        int i;
        for(i = 0; i < count; i++)
            printf("%d ",a[i]);
        printf("
    ");
    }
    size_t partition(int * ary, size_t len, size_t pivot_i) //划分函数
    {   
        size_t i = 0;
        size_t small_len = 0;
        int pivot = ary[pivot_i];
        swap(&ary[pivot_i], &ary[len - 1]);
         for (; i < len; i++) {
              if (ary[i] < pivot) {
                swap(&ary[i], &ary[small_len]);
                  small_len++;
            }
        }
        swap(&ary[len - 1], &ary[small_len]); //交换元素
        return small_len;
    }
    void quick_sort(int * ary, size_t len) 
    {
        if (len == 0 || len == 1) return;
        size_t small_len = partition(ary, len, 0);
        quick_sort(ary, small_len);
        quick_sort(&ary[small_len + 1], len - small_len - 1);
    }
    int main(void) 
    {
        int ary[] = {2, 4, 12, 25, 13, 5, 3, 1, 7, 6};
        size_t len = sizeof(ary) / sizeof(ary[0]);
        printArray(ary, len);
        quick_sort(ary, len);
        printArray(ary, len);
        return 0;
    }

    C89标准在stdlib.h中定义了抽象数据类型的快速排序函数qsort:

    // Completed on 2014.10.9 19:20
    // Language: C99
    //
    // 版权所有(C)codingwu   (mail: oskernel@126.com) 
    // 博客地址:http://www.cnblogs.com/archimedes/
    #include <stdio.h>
    #include <stdlib.h>
    static int cmp(const void *a,  const void *b)
    {
        return *(int *)a - *(int *)b;
    }
    void printArray(int a[], int count)   //打印数组元素
    {
        int i;
        for(i = 0; i < count; i++)
            printf("%d ",a[i]);
        printf("
    ");
    }
    int main()
    {
        int arr[10] = {5, 3, 7, 4, 1, 9, 8, 6, 2};
        size_t len = sizeof(arr) / sizeof(arr[0]);
        printArray(arr, len);
        qsort(arr, 10, sizeof(int), cmp);
        printArray(arr, len);
        return 0;
    }

    有关qsort函数的其他用法可以参考:《C语言中qsort函数的应用

    qsort函数实现

    参考minix内核代码中qsort函数的实现:

    整个qsort函数实现的是通用类型,也即是使用C模拟了泛型,使用了4个辅助函数,声明如下:

    static    void qsort1(char *, char *, size_t);
    static    int (*qcompar)(const char *, const char *); 
    static    void qexchange(char *, char *, size_t);
    static    void q3exchange(char *, char *, char *, size_t);

    在《C语言实现泛型编程》中有介绍,要实现泛型,对于简单的元素的交换问题,实现起来必须转换为字节块的交换,这里采用的是qexchange 函数来实现,代码如下:

    static void
    qexchange(register char *p, register char *q,
          register size_t n)
    {
        register int c;
    
        while (n-- > 0) {
            c = *p;
            *p++ = *q;
            *q++ = c;
        }
    }

    代码中还有一个增强版的交换函数q3exchange,顾名思义实现的是三个字节区域块内容的交换,代码如下:

    static void
    q3exchange(register char *p, register char *q, register char *r,
           register size_t n)
    {
        register int c;
        while (n-- > 0) {
            c = *p;
            *p++ = *r;
            *r++ = *q;
            *q++ = c;
        }
    }

    原理比较简单,我就不具体解释了。

    核心函数是qsort1,代码结构较为复杂,下面详细剖析:

    函数原型  static void qsort1(char *, char *, size_t);

    第一个参数传递数组首地址,第二个参数传递最后一个元素的首地址

    函数的划分元选取的是数组中间元素:

    left = a1;
    right = a2;
    lefteq = righteq = a1 + width * (((a2-a1)+width)/(2*width));

    lefteq和righteq分别指向左右两边区域的边界,对于左边区域,实行下面的代码:

    while (left < lefteq && (cmp = (*qcompar)(left, lefteq)) <= 0) {
        if (cmp < 0) {  
            left += width;
        } else {
            lefteq -= width;
            qexchange(left, lefteq, width);  
        }
    }        

    对于右边区域,实行下面的代码:

    while (right > righteq) {
        if ((cmp = (*qcompar)(right, righteq)) < 0) {    
            if (left < lefteq) {
                qexchange(left, right, width);
                left += width;
                right -= width;
                goto again;
            }
            righteq += width;
            q3exchange(left, righteq, right, width);
            lefteq += width;
            left = lefteq;
        } else if (cmp == 0) {
            righteq += width;
            qexchange(right, righteq, width);
        } else right -= width;
    }

    goto语句跳转到左边区域代码,直到左边区域元素均小于lefteq指向的元素,也即是中间元。之后left==lefteq,此时当cmp<0,此时左边已经没有空位,righteq += width操作向右移动让出一个位置,q3exchange(left, righteq, right, width)操作轮换三个位置的元素。

    完整代码如下:

    /*
     * (c) copyright 1987 by the Vrije Universiteit, Amsterdam, The Netherlands.
     * See the copyright notice in the ACK home directory, in the file "Copyright".
     */
    /* $Header: qsort.c,v 1.3 90/08/28 14:03:24 eck Exp $ */
    
    #include    <stdlib.h>
    
    static    void qsort1(char *, char *, size_t);
    static    int (*qcompar)(const char *, const char *);
    static    void qexchange(char *, char *, size_t);
    static    void q3exchange(char *, char *, char *, size_t);
    
    void
    qsort(void *base, size_t nel, size_t width,
          int (*compar)(const void *, const void *))
    {
        /* when nel is 0, the expression '(nel - 1) * width' is wrong */
        if (!nel) return;
        qcompar = (int (*)(const char *, const char *)) compar;
        qsort1(base, (char *)base + (nel - 1) * width, width);
    }
    
    static void
    qsort1(char *a1, char *a2, register size_t width)
    {
        register char *left, *right;
        register char *lefteq, *righteq;
        int cmp;
    
        for (;;) {
            if (a2 <= a1) return;
            left = a1;
            right = a2;
            lefteq = righteq = a1 + width * (((a2-a1)+width)/(2*width));
            /*
               Pick an element in the middle of the array.
               We will collect the equals around it.
               "lefteq" and "righteq" indicate the left and right
               bounds of the equals respectively.
               Smaller elements end up left of it, larger elements end
               up right of it.
            */
    again:
            while (left < lefteq && (cmp = (*qcompar)(left, lefteq)) <= 0) {
                if (cmp < 0) {
                    /* leave it where it is */
                    left += width;
                }
                else {
                    /* equal, so exchange with the element to
                       the left of the "equal"-interval.
                    */
                    lefteq -= width;
                    qexchange(left, lefteq, width);
                }
            }
            while (right > righteq) {
                if ((cmp = (*qcompar)(right, righteq)) < 0) {
                    /* smaller, should go to left part
                    */
                    if (left < lefteq) {
                        /* yes, we had a larger one at the
                           left, so we can just exchange
                        */
                        qexchange(left, right, width);
                        left += width;
                        right -= width;
                        goto again;
                    }
                    /* no more room at the left part, so we
                       move the "equal-interval" one place to the
                       right, and the smaller element to the
                       left of it.
                       This is best expressed as a three-way
                       exchange.
                    */
                    righteq += width;
                    q3exchange(left, righteq, right, width);
                    lefteq += width;
                    left = lefteq;
                }
                else if (cmp == 0) {
                    /* equal, so exchange with the element to
                       the right of the "equal-interval"
                    */
                    righteq += width;
                    qexchange(right, righteq, width);
                }
                else    /* just leave it */ right -= width;
            }
            if (left < lefteq) {
                /* larger element to the left, but no more room,
                   so move the "equal-interval" one place to the
                   left, and the larger element to the right
                   of it.
                */
                lefteq -= width;
                q3exchange(right, lefteq, left, width);
                righteq -= width;
                right = righteq;
                goto again;
            }
            /* now sort the "smaller" part */
            qsort1(a1, lefteq - width, width);
            /* and now the larger, saving a subroutine call
               because of the for(;;)
            */
            a1 = righteq + width;
        }
        /*NOTREACHED*/
    }
    
    static void
    qexchange(register char *p, register char *q,
          register size_t n)
    {
        register int c;
    
        while (n-- > 0) {
            c = *p;
            *p++ = *q;
            *q++ = c;
        }
    }
    
    static void
    q3exchange(register char *p, register char *q, register char *r,
           register size_t n)
    {
        register int c;
    
        while (n-- > 0) {
            c = *p;
            *p++ = *r;
            *r++ = *q;
            *q++ = c;
        }
    }
  • 相关阅读:
    web.xml中listener、 filter、servlet 加载顺序及其详解 从零开始
    网站运营之门外汉并且伪理解
    win7 旗舰版 64位注册dll(regsvr32)失败解决方法
    盖是乱盖,书童逆天之初创互联网企业常见弊病
    VirtualBox中安装Windows10
    jupyter导出pdf文件的方法
    com.ibm.mm.sdk.common.DKUsageError: DGL3616A: 发生意外的 SQL 错误; ICM7015: 在库服务器的 SQL 操作期间,发生意外错误。有关错误的详细信息,请参阅数据库文档。 (STATE) : [LS RC = 7015, SQL RC = 100
    Tomcat编译java文件没有同步问题
    Google APPS申请指南
    如何用C#语言构造蜘蛛程序
  • 原文地址:https://www.cnblogs.com/wuyudong/p/quick-sort-algorithm.html
Copyright © 2011-2022 走看看