`
cloudtech
  • 浏览: 4814972 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

qsort 源代码

 
阅读更多
/***
*qsort.c - quicksort algorithm; qsort() library function for sorting arrays
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*       To implement the qsort() routine for sorting arrays.
*
*****************************************************************************

**/

#include <cruntime.h>
#include <stdlib.h>
#include <search.h>
#include <internal.h>

/* 加快运行速度的优化选项 */
#pragma optimize("t", on)

/* 函数原型*/
static void __cdecl shortsort(char *lo, char *hi, size_t width,
                int (__cdecl *comp)(const void *, const void *));
static void __cdecl swap(char *p, char *q, size_t width);

/* this parameter defines the cutoff between using quick sort and
   insertion sort for arrays; arrays with lengths shorter or equal to the
   below value use insertion sort */

/* 这个参数定义的作用是,当快速排序的循环中遇到大小小于CUTOFF的数组时,就使用插入
排序来进行排序,这样就避免了对小数组继续拆分而带来的额外开销。这里的取值8,是
经过测试以后能够时快速排序算法达到最快的CUTOFF的值。*/

#define CUTOFF 8            /* testing shows that this is good value */

 
/* 源代码中这里是qsort的代码,但是我觉得先解释了qsort要调用的函数的功能比较

好。

    shortsort函数:

    这个函数的作用,上面已经有提到。就是当对快速排序递归调用的时候,如果遇到
大小小于CUTOFF的数组,就调用这个函数来进行排序,而不是继续拆分数组进入下一层
递归。因为虽然这里用的是基本排序方法,它的运行时间和O(n^2)成比例,但是如果是
只有8个元素,它的速度比需要递归的快速排序要快得多。另外,在源代码的注释中,说
这是一个插入排序(insertion sort),但是我觉得这个应该是一个选择排序才对
(selection sort)。至于为什么用选择排序而不用插入排序,应该是和选择排序的元素
交换次数少有关系,只需要N-1次交换,而插入排序平均需要(N^2)/2次。之所以要选择
交换次数少的算法,是因为有可能数组里面的单个元素的大小很大,使得交换成为最主
要的性能瓶颈。

参数说明:

char *lo;    指向要排序的子数组的第一个元素的指针
char *hi;    指向要排序的子数组的最后一个元素的指针
size_t width;  数组中单个元素的大小
int (__cdecl *comp)(const void *,const void *);   用来比较两个元素大
小的函数指针,这个函数是你在调用qsort的时候传入的参数,当前一个指针指向的元素
小于后一个时,返回负数;当相等时,返回0;当大于时,返回正数。*/

//选择排序 
static void __cdecl shortsort (
    char *lo,
    char *hi,
    size_t width,
    int (__cdecl *comp)(const void *, const void *)
    )
{
    char *p, *max;
    /* Note: in assertions below, i and j are alway inside original bound of array to sort. */
    while (hi > lo) {
        max = lo;
      /*下面这个for循环作用是从lo到hi的元素中,选出最大的一个,max指针指向这个最大项*/
        for (p = lo+width; p <= hi; p += width) {
            if (comp(p, max) > 0) {
                max = p;
            }
        }
      /*这里把最大项和hi指向的项向交换*/
        swap(max, hi, width);
       /*hi向前移动一个指针。经过这一步,在hi后面的是已经排好序的比未排序部分所有的数要大的数。*/
        hi -= width;
    }
}

 

/*下面分析swap函数:
这个函数比较简单,就是交换两个项的操作,不过是用指针来实现的。
*/

static void __cdecl swap (
    char *a,
    char *b,
    size_t width
    )
{
    char tmp;
    if ( a != b )
        /* Do the swap one character at a time to avoid potential alignment
           problems. */
        while ( width-- ) {
            tmp = *a;
            *a++ = *b;
            *b++ = tmp;
        }
}

 

/*下面是最重要的部分,qsort函数:*/
/*使用的是非递归方式,所以这里有一个自定义的栈式结构,下面这个定义是栈的大小
*/

#define STKSIZ (8*sizeof(void*) - 2)

void __cdecl qsort (
    void *base,
    size_t num,
    size_t width,
    int (__cdecl *comp)(const void *, const void *)
    )
{
   /*由于使用了某些技巧(下面会讲到),使得栈大小的需求不会大于1+log2(num),因此30的栈大小应该是足够了。为什么说是30呢?
   其实在上面STKSIZ的定义中可以计算出sizeof(void*)=4,所以8*4-2=30*/
    char *lo, *hi;              /* ends of sub-array currently sorting   数组的两端项指针,用来指明数组的上界和下界*/
    char *mid;                  /* points to middle of subarray  数组的中间项指针*/
    char *loguy, *higuy;        /* traveling pointers for partition step  循环中的游动指针*/
    size_t size;                /* size of the sub-array  数组的大小*/
    char *lostk[STKSIZ], *histk[STKSIZ];
    int stkptr;                 /* stack for saving sub-array to be processed  栈顶指针*/

/*如果只有一个或以下的元素,则退出*/
    if (num < 2 || width == 0)
        return;                 /* nothing to do */
        
    stkptr = 0;                 /* initialize stack */

    lo = base;
    hi = (char *)base + width * (num-1);        /* initialize limits */

    /*这个标签是伪递归的开始*/
recurse:

    size = (hi - lo) / width + 1;        /* number of el's to sort */
   /*当size小于CUTOFF时,使用选择排序算法更快*/
    if (size <= CUTOFF) {
        shortsort(lo, hi, width, comp);
    }
    else {
      /*首先我们要选择一个分区项。算法的高效性要求我们找到一个近似数组中间值
的项,但我们要保证能够很快找到它。我们选择数组的第一项、中间项和最后一项的中
间值,来避免最坏情况下的低效率。测试表明,选择三个数的中间值,比单纯选择数组
的中间项的效率要高。

       我们解释一下为什么要避免最坏情况和怎样避免。在最坏情况下,快速排序算法
的运行时间复杂度是O(n^2)。这种情况的一个例子是已经排序的文件。如果我们选择最
后一个项作为划分项,也就是已排序数组中的最大项,我们分区的结果是分成了一个大
小为N-1的数组和一个大小为1的数组,这样的话,我们需要的比较次数是N + N-1 + N-2 
+ N-3 +...+2+1=(N+1)N/2=O(n^2)。而如果选择前 中 后三个数的中间值,这种最坏情况的
数组也能够得到很好的处理。*/

        mid = lo + (size / 2) * width;      /* find middle element */
       /*第一项 中间项 和最后项三个元素排序*/

        /* Sort the first, middle, last elements into order */
        if (comp(lo, mid) > 0) {
            swap(lo, mid, width);
        }
        if (comp(lo, hi) > 0) {
            swap(lo, hi, width);
        }
        if (comp(mid, hi) > 0) {
            swap(mid, hi, width);
        }

 /*下面要把数组分区成三块,一块是小于分区项的,一块是等于分区项的,而另一块是大于分区项的。*/
/*这里初始化的loguy 和 higuy两个指针,是在循环中用于移动来指示需要交换的两个元素的。
higuy递减,loguy递增,所以下面的for循环总是可以终止。*/

        loguy = lo; /* traveling pointers for partition step  循环中的游动指针*/
        higuy = hi; /* traveling pointers for partition step  循环中的游动指针*/

        /* Note that higuy decreases and loguy increases on every iteration,
           so loop must terminate. */
        for (;;) {
           /*开始移动loguy指针,直到A[loguy]>A[mid]*/
            if (mid > loguy) {
                do  {
                    loguy += width;
                } while (loguy < mid && comp(loguy, mid) <= 0);
            }

            /*如果移动到loguy>=mid的时候,就继续向后移动,使得A[loguy]>a[mid]。
            这一步实际上作用就是使得移动完loguy之后,loguy指针之前的元素都是不大于划分值的元素。*/
            if (mid <= loguy) {
                do  {
                    loguy += width;
                } while (loguy <= hi && comp(loguy, mid) <= 0);
            }

           /*执行到这里的时候,loguy指针之前的项都比A[mid]要小或者等于它*/
           
            /*下面移动higuy指针,直到A[higuy]<=A[mid]*/
            do  {
                higuy -= width;
            } while (higuy > mid && comp(higuy, mid) > 0);

           /*如果两个指针交叉了,则退出循环。*/

            if (higuy < loguy)
                break;

            /* 此时A[loguy]>A[mid],A[higuy]<=A[mid],loguy<=hi,higuy>lo。*/
           /*交换两个指针指向的元素*/
            swap(loguy, higuy, width);

            /* If the partition element was moved, follow it.  Only need
               to check for mid == higuy, since before the swap,
               A[loguy] > A[mid] implies loguy != mid. */

           /*如果划分元素的位置移动了,我们要跟踪它。

              因为在前面对loguy处理的两个循环中的第二个循环已经保证了loguy>mid,

             即loguy指针不和mid指针相等。

             所以我们只需要看一下higuy指针是否等于mid指针,

            如果原来是mid==higuy成立了,那么经过刚才的交换,中间值项已经到了

loguy指向的位置(注意:刚才是值交换了,但是并没有交换指针。当higuy和mid相等,交换higuy和loguy指向的内容,higuy依然等于mid),所以让mid=loguy,重新跟踪中间值。*/

            if (mid == higuy)
                mid = loguy;

            /* A[loguy] <= A[mid], A[higuy] > A[mid]; so condition at top
               of loop is re-established */

            /*这个循环一直进行到两个指针交叉为止*/
        }

        /*     A[i] <= A[mid] for lo <= i < loguy,
               A[i] > A[mid] for higuy < i < hi,
               A[hi] >= A[mid]
               higuy < loguy
           implying:
               higuy == loguy-1
               or higuy == hi - 1, loguy == hi + 1, A[hi] == A[mid] */

       /*上一个循环结束之后,因为还没有执行loguy指针和higuy指针内容的交换,所以loguy指针的前面的数组元素都不大于划分值,而higuy指针之后的数组元素都大于划分值,所以此时有两种情况:

       1)  higuy=loguy-1

       2)  higuy=hi-1,loguy=hi+1

       其中第二种情况发生在一开始选择三个元素的时候,hi指向的元素和mid指向的元素值相等,而hi前面的元素全部都不大于划分值,使得移动loguy指针的时候,一直移动到了hi+1才停止,再移动higuy指针的时候,higuy指针移动一步就停止了,停在hi-1处。

       */

        /* Find adjacent elements equal to the partition element.  The
           doubled loop is to avoid calling comp(mid,mid), since some
           existing comparison funcs don't work when passed the same value
           for both pointers. */

        higuy += width;
        if (mid < higuy) {
            do  {
                higuy -= width;
            } while (higuy > mid && comp(higuy, mid) == 0);
        }
        if (mid >= higuy) {
            do  {
                higuy -= width;
            } while (higuy > lo && comp(higuy, mid) == 0);
        }

        /* OK, now we have the following:
              higuy < loguy
              lo <= higuy <= hi
              A[i]  <= A[mid] for lo <= i <= higuy
              A[i]  == A[mid] for higuy < i < loguy
              A[i]  >  A[mid] for loguy <= i < hi
              A[hi] >= A[mid] */

   /* We've finished the partition, now we want to sort the subarrays
           [lo, higuy] and [loguy, hi].
           We do the smaller one first to minimize stack usage.
           We only sort arrays of length 2 or more.*/

       /*
         我们可以想像一下,对于一个已经排序的数组,如果每次分成N-1和1的数组,
        而我们又每次都先处理N-1那一半,那么我们的递归深度就是和N成比例,这样对于大N,栈空间的开销是很大的。
        如果先处理1的那一半,栈里面最多只有2项。当划分元素刚好在数组中间时,栈的长度是logN。
         对于栈的操作,就是先把大的数组信息入栈。
       */

        if ( higuy - lo >= hi - loguy ) {
            if (lo < higuy) {
                lostk[stkptr] = lo;
                histk[stkptr] = higuy;
                ++stkptr;
            }                           /* save big recursion for later */

            if (loguy < hi) {
                lo = loguy;
                goto recurse;           /* do small recursion */
            }
        }
        else {
            if (loguy < hi) {
                lostk[stkptr] = loguy;
                histk[stkptr] = hi;
                ++stkptr;               /* save big recursion for later */
            }

            if (lo < higuy) {
                hi = higuy;
                goto recurse;           /* do small recursion */
            }
        }
    }

    /* We have sorted the array, except for any pending sorts on the stack.
       Check if there are any, and do them. */

   /*出栈操作,直到栈为空,退出循环*/

    --stkptr;
    if (stkptr >= 0) {
        lo = lostk[stkptr];
        hi = histk[stkptr];
        goto recurse;           /* pop subarray from stack */
    }
    else
        return;                 /* all subarrays done */
}

分享到:
评论

相关推荐

    VC中qsort源代码

    ### VC中qsort源代码解析 #### 知识点概览 在VC(Visual C++)环境中,`qsort`函数是C/C++标准库中用于数组排序的关键函数之一,其内部实现采用了快速排序算法。本文将深入分析VC中`qsort`源代码的核心逻辑、算法...

    C标准库源代码(学习C/C++必备)

    C标准库源代码,能提高对C的理解,不错的哦 下载文件列表 Pack : clibsource.rar C 标准库源代码\ABORT.C C标准库源代码\ABS.C C标准库源代码\ACCESS.C C标准库源代码\ADJUSTFD.C C标准库源代码\ALGRITHM C标准库源...

    qsort快排函数源代码

    ### qsort快排函数源代码解析 #### 一、引言 `qsort`是C语言标准库中的一个强大而灵活的排序函数,用于对数组进行排序。它使用了快速排序算法作为其内部实现,因此被称为“快速排序”或简称“快排”。本文将详细...

    C库函数qsort七种使用方法示例

    qsort 函数可以对 int 类型数组进行排序,下面是一个简单的示例代码: ```c int num[100]; int cmp ( const void *a , const void *b ) { return *(int *)a – *(int *)b; } qsort(num, 100, sizeof(num[0]), cmp)...

    C标准库源代码.

    在深入探讨C标准库之前,我们先来理解一下源代码的概念。 源代码是程序员用特定编程语言编写的文本文件,它是程序的原始形式,可以被编译器或解释器转化为机器可执行的二进制代码。C标准库的源代码就是用C语言编写...

    C标准源代码 C函数库

    标题“C标准源代码 C函数库”暗示了我们将探讨C语言标准库的源代码。C标准库由ISO/IEC 9899定义,包括了诸多头文件,如、、等,每个头文件代表一类功能。通过查看源代码,我们可以深入了解这些函数的工作原理,这...

    C 标准库 源代码

    《C标准库源代码》是编程领域中一份重要的学习资源,它揭示了C语言标准库背后的实现细节。标准库是C语言程序开发的基础,提供了大量的功能函数,如输入/输出操作、内存管理、字符串处理、数学计算等。这份源代码是由...

    员工工资管理系统源代码.pdf

    - **文件名**:员工工资管理系统源代码.pdf - **描述**:该文件包含了员工工资管理系统的源代码。 - **标签**:资料 ### 2. 源代码概述 源代码主要实现了员工工资管理系统的功能,包括账号密码输入、员工信息录入、...

    分离出的微软qsort算法

    微软的QSort算法是针对C语言标准库中的`qsort`函数的一种优化实现。`qsort`函数在C语言中被广泛用于对数组进行快速排序,它是一个通用的...通过深入研究源代码,我们可以学习到更多关于算法优化和C语言编程的实践知识。

    C标准库源代码 很全

    这个压缩包包含的“C标准库源代码”是C语言爱好者和开发者宝贵的参考资料,可以深入理解这些基础功能的实现原理。 C标准库基于ISO/IEC 9899:2011标准,包含了大量用于输入/输出、内存管理、字符串处理、数学计算等...

    qsort C语言版

    ### qsort 在 C 语言中的应用与理解 #### 概述 `qsort` 是一个在 C 语言标准库 `&lt;stdlib.h&gt;` 中定义的函数,用于对数组进行快速排序。它是一个非常强大的工具,可以用来对多种数据类型进行排序,包括基本数据类型如...

    C标准库源代码C标准库源代码

    这个压缩包包含的源代码,是C语言标准库的具体实现,对于学习C语言、理解底层原理以及进行系统级编程的开发者来说,是一份宝贵的参考资料。 C标准库由ISO/IEC 9899:2011定义,通常被称作C11标准。这个库包括了以下...

    北大acm 1001-1008 C源代码

    【北大ACM程序设计竞赛C源代码解析】 北京大学在计算机科学教育方面享有盛誉,其组织的ACM(Association for Computing Machinery)程序设计竞赛旨在培养学生的算法设计与编程能力。这些竞赛题目通常涵盖基础到高级...

    sort与qsort1

    但请注意,使用 `qsort` 需要手动定义比较函数,这可能引入潜在的错误源。 - 对于涉及 `std::string` 的排序,强烈推荐使用 `sort`,因为它提供了更方便的接口和更好的性能。 在实际编程中,了解这两个函数的差异和...

    C++Effective的代码,qsort的使用,字符转化的过程,内核泄漏检测.zip

    在这个压缩包中,可能包含的源代码示例会详细展示上述概念的实际应用。通过G2或其他图形化工具,我们可以更直观地理解这些复杂的编程概念,比如查看排序过程的可视化结果,或者看到字符转换前后的差异。对于内核泄漏...

    C++中的Qsort

    ### C++中的Qsort与Sort详解 在C++编程领域,`qsort()`与`sort()`函数是处理数据排序的两个重要工具。虽然`qsort()`源自C语言,...在选择使用哪种排序方法时,应综合考虑数据类型、性能需求以及代码的可读性和维护性。

    POJ1002-487-3279【Hash+Qsort】

    描述中的“解题报告+AC代码”表明,这个压缩包包含了完成该题目的解题思路报告以及已经通过所有测试用例(Accepted, AC)的源代码。解题报告会详述解题步骤、思路以及可能遇到的问题和解决方案。AC代码则为实际实现...

    CObList sort desc asc 排序 仿qsort 升序 降序

    具体实现细节需要查看源代码才能进一步解析和讨论。 总的来说,`CObList`的排序涉及到自定义比较函数和`Sort`成员函数的使用,可以实现升序和降序排列。如果需要实现类似于`qsort`的排序功能,需要根据`CObList`的...

    编程珠玑全部源代码 分享

    qsortints.c -- Sort with C library qsort. bitsortgen.c -- Generate random integers for sorting. Column 2: Test and time algorithms rotate.c -- Three ways to rotate the elements of a vector. The ...

    王小平版遗传算法的光盘源代码

    王小平版遗传算法的光盘源代码 SGPC: Simple Genetic Programming in C by Walter Alden Tackett and Aviram Carmi (gpc@ipld01.hac.com) Version 1.1 (c) 1993 by Walter Alden Tackett and Aviram ...

Global site tag (gtag.js) - Google Analytics