快排序算法

简介:

快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 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("\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函数的其他用法可以参考:《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;
    }
}
目录
相关文章
|
6月前
|
搜索推荐
简单的排序算法
简单的排序算法
45 1
|
6月前
|
搜索推荐 C++
7大排序算法C++实现
7大排序算法C++实现
67 0
|
2月前
|
搜索推荐 索引
排序算法详解
本文介绍了多种排序算法,包括插入排序(如直接插入排序和希尔排序)、选择排序(如直接选择排序和堆排序)、交换排序(如冒泡排序和快速排序)以及归并排序和计数排序。插入排序通过构建有序序列逐步插入元素;选择排序通过不断选择最小元素放置于序列起始;交换排序通过元素间的交换达到排序目的;归并排序采用分治法将序列分解再合并;计数排序则通过统计元素出现次数来排序。文章详细阐述了各种排序算法的原理及其实现方法。
37 6
排序算法详解
|
6月前
|
搜索推荐 算法
常见的排序算法(1)
常见的排序算法(1)
80 3
|
6月前
|
搜索推荐
直接选择排序算法
直接选择排序算法
42 0
|
6月前
|
搜索推荐 算法 NoSQL
10-千奇百怪的排序算法
10-千奇百怪的排序算法
|
6月前
|
搜索推荐 算法
常见排序算法实现(二)
常见排序算法实现(二)
51 0
|
搜索推荐 算法
14 排序算法
14 排序算法
31 0
|
算法 搜索推荐
排序算法的简单认识
在进行很多便捷算法之前总是要实现对象的有序化,而这就将使用到排序相关的算法,即使目前诸多高级语言已然完成对于排序算法的封装,用户只需导入对应库文件即可调用排序算法完成排序,无需手写排序算法,但具体的排序算法的选择就必须对于排序算法有所认识。本文就将介绍两个简单的排序算法:选择排序与冒泡排序。 选择排序 为什么称为选择排序? 该算法每次都是对于未排序的关键字进行比较,选择出最小或最大的关键字,再对其交换位置,实现一次排序,需进行多次比较。 选择排序法是一种不稳定的排序算法。它的工作原理是每一次从待排序的数据元
75 0
|
搜索推荐 算法 测试技术