工学1号馆

home

« | 返回首页 | »

快排序算法

By Wu Yudong on July 25, 2016

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

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

算法原理

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

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

步骤为:

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

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

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

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

算法实现(C代码如下):

// Completed on 2014.10.9 19:09
// Language: C99
//
// 版权所有(C)wuyudong
// 博客地址:http://www.wuyudong.com
#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("\n");
}
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("\n");
}
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函数实现

参考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);

要实现泛型,对于简单的元素的交换问题,实现起来必须转换为字节块的交换,这里采用的是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;
    }
}

 

如果文章对您有帮助,欢迎点击下方按钮打赏作者

Comments

No comments yet.
To verify that you are human, please fill in "七"(required)