• 快速排序 Gnu glibc qsort


    文笔不好,想不了太好的标题,所以就简单的将关键字列出来作为标题.

    先从一个小例子开始,这个例子是使用C library中的qsort函数完成一个数组的排序:

    /* qsort example */
    #include <stdio.h>
    #include <stdlib.h>
    
    int values[] = { 40, 10, 100, 90, 20, 25 };
    
    int compare (const void * a, const void * b)
    {
      return ( *(int*)a - *(int*)b );
    }
    
    int main ()
    {
      int n;
      qsort (values, 6, sizeof(int), compare);
      for (n=0; n<6; n++)
         printf ("%d ",values[n]);
      return 0;
    }
    

    代码如上,这不是我写的,是cplusplus.com上面提供的实例代码,不用质疑了. 注意一下这种使用的方法,

    好吧,qsort函数参数中需要一个compare形式的函数指针,猜想一个结论:qsort函数的源码中,排序的方向

    是不固定的,也就是说根据用户提供的比较函数来定,那么这个函数指针做的任务就只是确定排序方向吗?另外,

    如果就是简单的确定方向问题,那么为什么不采用使用固定函数参数的形式呢?例如提供使用一个整形参数,

    然后判断参数就确定排序方向. ... 这些都是我的猜想,要想知道答案,就需要看一下源码.

    qsort函数式是C标准库中的函数,我们也都知道C标准以及变种,我最熟悉的就是GNU C.所以今天的qsort函数

    源码来自GLIBC.另外有一天需要说明,看到很多网友在说qsort函数的时候就直接将Qsort.c的源码贴出来翻译

    一下英文注解就完了.我不知道这种做法对不对,也不去评论.但是在本文中,我将会啰嗦一下,说一下是如何找到

    函数源码的.(下面就是Qsort.c的源码,感兴趣的可以先看一下.)

    View Code
      1 /* Copyright (C) 1991,1992,1996,1997,1999,2004 Free Software Foundation, Inc.
      2    This file is part of the GNU C Library.
      3    Written by Douglas C. Schmidt (schmidt@ics.uci.edu).
      4 
      5    The GNU C Library is free software; you can redistribute it and/or
      6    modify it under the terms of the GNU Lesser General Public
      7    License as published by the Free Software Foundation; either
      8    version 2.1 of the License, or (at your option) any later version.
      9 
     10    The GNU C Library is distributed in the hope that it will be useful,
     11    but WITHOUT ANY WARRANTY; without even the implied warranty of
     12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13    Lesser General Public License for more details.
     14 
     15    You should have received a copy of the GNU Lesser General Public
     16    License along with the GNU C Library; if not, write to the Free
     17    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
     18    02111-1307 USA.  */
     19 
     20 /* If you consider tuning this algorithm, you should consult first:
     21    Engineering a sort function; Jon Bentley and M. Douglas McIlroy;
     22    Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993.  */
     23 
     24 #include <alloca.h>
     25 #include <limits.h>
     26 #include <stdlib.h>
     27 #include <string.h>
     28 
     29 /* Byte-wise swap two items of size SIZE. */
     30 #define SWAP(a, b, size)                              \
     31   do                                          \
     32     {                                          \
     33       register size_t __size = (size);                          \
     34       register char *__a = (a), *__b = (b);                      \
     35       do                                      \
     36     {                                      \
     37       char __tmp = *__a;                              \
     38       *__a++ = *__b;                              \
     39       *__b++ = __tmp;                              \
     40     } while (--__size > 0);                              \
     41     } while (0)
     42 
     43 /* Discontinue quicksort algorithm when partition gets below this size.
     44    This particular magic number was chosen to work best on a Sun 4/260. */
     45 #define MAX_THRESH 4
     46 
     47 /* Stack node declarations used to store unfulfilled partition obligations. */
     48 typedef struct
     49   {
     50     char *lo;
     51     char *hi;
     52   } stack_node;
     53 
     54 /* The next 4 #defines implement a very fast in-line stack abstraction. */
     55 /* The stack needs log (total_elements) entries (we could even subtract
     56    log(MAX_THRESH)).  Since total_elements has type size_t, we get as
     57    upper bound for log (total_elements):
     58    bits per byte (CHAR_BIT) * sizeof(size_t).  */
     59 #define STACK_SIZE    (CHAR_BIT * sizeof(size_t))
     60 #define PUSH(low, high)    ((void) ((top->lo = (low)), (top->hi = (high)), ++top))
     61 #define    POP(low, high)    ((void) (--top, (low = top->lo), (high = top->hi)))
     62 #define    STACK_NOT_EMPTY    (stack < top)
     63 
     64 
     65 /* Order size using quicksort.  This implementation incorporates
     66    four optimizations discussed in Sedgewick:
     67 
     68    1. Non-recursive, using an explicit stack of pointer that store the
     69       next array partition to sort.  To save time, this maximum amount
     70       of space required to store an array of SIZE_MAX is allocated on the
     71       stack.  Assuming a 32-bit (64 bit) integer for size_t, this needs
     72       only 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes).
     73       Pretty cheap, actually.
     74 
     75    2. Chose the pivot element using a median-of-three decision tree.
     76       This reduces the probability of selecting a bad pivot value and
     77       eliminates certain extraneous comparisons.
     78 
     79    3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leaving
     80       insertion sort to order the MAX_THRESH items within each partition.
     81       This is a big win, since insertion sort is faster for small, mostly
     82       sorted array segments.
     83 
     84    4. The larger of the two sub-partitions is always pushed onto the
     85       stack first, with the algorithm then concentrating on the
     86       smaller partition.  This *guarantees* no more than log (total_elems)
     87       stack size is needed (actually O(1) in this case)!  */
     88 
     89 void
     90 _quicksort (void *const pbase, size_t total_elems, size_t size,
     91         __compar_d_fn_t cmp, void *arg)
     92 {
     93   register char *base_ptr = (char *) pbase;
     94 
     95   const size_t max_thresh = MAX_THRESH * size;
     96 
     97   if (total_elems == 0)
     98     /* Avoid lossage with unsigned arithmetic below.  */
     99     return;
    100 
    101   if (total_elems > MAX_THRESH)
    102     {
    103       char *lo = base_ptr;
    104       char *hi = &lo[size * (total_elems - 1)];
    105       stack_node stack[STACK_SIZE];
    106       stack_node *top = stack;
    107 
    108       PUSH (NULL, NULL);
    109 
    110       while (STACK_NOT_EMPTY)
    111         {
    112           char *left_ptr;
    113           char *right_ptr;
    114 
    115       /* Select median value from among LO, MID, and HI. Rearrange
    116          LO and HI so the three values are sorted. This lowers the
    117          probability of picking a pathological pivot value and
    118          skips a comparison for both the LEFT_PTR and RIGHT_PTR in
    119          the while loops. */
    120 
    121       char *mid = lo + size * ((hi - lo) / size >> 1);
    122 
    123       if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
    124         SWAP (mid, lo, size);
    125       if ((*cmp) ((void *) hi, (void *) mid, arg) < 0)
    126         SWAP (mid, hi, size);
    127       else
    128         goto jump_over;
    129       if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)
    130         SWAP (mid, lo, size);
    131     jump_over:;
    132 
    133       left_ptr  = lo + size;
    134       right_ptr = hi - size;
    135 
    136       /* Here's the famous ``collapse the walls'' section of quicksort.
    137          Gotta like those tight inner loops!  They are the main reason
    138          that this algorithm runs much faster than others. */
    139       do
    140         {
    141           while ((*cmp) ((void *) left_ptr, (void *) mid, arg) < 0)
    142         left_ptr += size;
    143 
    144           while ((*cmp) ((void *) mid, (void *) right_ptr, arg) < 0)
    145         right_ptr -= size;
    146 
    147           if (left_ptr < right_ptr)
    148         {
    149           SWAP (left_ptr, right_ptr, size);
    150           if (mid == left_ptr)
    151             mid = right_ptr;
    152           else if (mid == right_ptr)
    153             mid = left_ptr;
    154           left_ptr += size;
    155           right_ptr -= size;
    156         }
    157           else if (left_ptr == right_ptr)
    158         {
    159           left_ptr += size;
    160           right_ptr -= size;
    161           break;
    162         }
    163         }
    164       while (left_ptr <= right_ptr);
    165 
    166           /* Set up pointers for next iteration.  First determine whether
    167              left and right partitions are below the threshold size.  If so,
    168              ignore one or both.  Otherwise, push the larger partition's
    169              bounds on the stack and continue sorting the smaller one. */
    170 
    171           if ((size_t) (right_ptr - lo) <= max_thresh)
    172             {
    173               if ((size_t) (hi - left_ptr) <= max_thresh)
    174         /* Ignore both small partitions. */
    175                 POP (lo, hi);
    176               else
    177         /* Ignore small left partition. */
    178                 lo = left_ptr;
    179             }
    180           else if ((size_t) (hi - left_ptr) <= max_thresh)
    181         /* Ignore small right partition. */
    182             hi = right_ptr;
    183           else if ((right_ptr - lo) > (hi - left_ptr))
    184             {
    185           /* Push larger left partition indices. */
    186               PUSH (lo, right_ptr);
    187               lo = left_ptr;
    188             }
    189           else
    190             {
    191           /* Push larger right partition indices. */
    192               PUSH (left_ptr, hi);
    193               hi = right_ptr;
    194             }
    195         }
    196     }
    197 
    198   /* Once the BASE_PTR array is partially sorted by quicksort the rest
    199      is completely sorted using insertion sort, since this is efficient
    200      for partitions below MAX_THRESH size. BASE_PTR points to the beginning
    201      of the array to sort, and END_PTR points at the very last element in
    202      the array (*not* one beyond it!). */
    203 
    204 #define min(x, y) ((x) < (y) ? (x) : (y))
    205 
    206   {
    207     char *const end_ptr = &base_ptr[size * (total_elems - 1)];
    208     char *tmp_ptr = base_ptr;
    209     char *thresh = min(end_ptr, base_ptr + max_thresh);
    210     register char *run_ptr;
    211 
    212     /* Find smallest element in first threshold and place it at the
    213        array's beginning.  This is the smallest array element,
    214        and the operation speeds up insertion sort's inner loop. */
    215 
    216     for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)
    217       if ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)
    218         tmp_ptr = run_ptr;
    219 
    220     if (tmp_ptr != base_ptr)
    221       SWAP (tmp_ptr, base_ptr, size);
    222 
    223     /* Insertion sort, running from left-hand-side up to right-hand-side.  */
    224 
    225     run_ptr = base_ptr + size;
    226     while ((run_ptr += size) <= end_ptr)
    227       {
    228     tmp_ptr = run_ptr - size;
    229     while ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)
    230       tmp_ptr -= size;
    231 
    232     tmp_ptr += size;
    233         if (tmp_ptr != run_ptr)
    234           {
    235             char *trav;
    236 
    237         trav = run_ptr + size;
    238         while (--trav >= run_ptr)
    239               {
    240                 char c = *trav;
    241                 char *hi, *lo;
    242 
    243                 for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)
    244                   *hi = *lo;
    245                 *hi = c;
    246               }
    247           }
    248       }
    249   }
    250 }

     《C标准库》这本书中有讲解qsort函数,感兴趣的可以看看这本书,了解一下C标准库也不错,熟悉一下函数.

    如果没有这本书,那么获取标准库函数也可以使用WIKI,途径还是蛮多的.下面就不多说了,直入正题吧.

    1 /* Sort NMEMB elements of BASE, of SIZE bytes each,
    2    using COMPAR to perform the comparisons.  */
    3 extern void qsort (void *__base, size_t __nmemb, size_t __size,
    4            __compar_fn_t __compar) __nonnull ((1, 4));
    5 #ifdef __USE_GNU
    6 extern void qsort_r (void *__base, size_t __nmemb, size_t __size,
    7              __compar_d_fn_t __compar, void *__arg)
    8   __nonnull ((1, 4));
    9 #endif

     除了提供qsort函数之外,还有一个qsort_r的函数,两者之间的名字如此相似,先不看下面一个,我们先来看一下qsort

    的源码.(使用Source Insight的搜索功能很容易找得到.)

    1 void
    2 qsort (void *b, size_t n, size_t s, __compar_fn_t cmp)
    3 {
    4   return qsort_r (b, n, s, (__compar_d_fn_t) cmp, NULL);
    5 }

    到这里就可以看得到,qsort函数其实是调用qsort_r函数,而且依赖qsort_r函数,可以说是完全依赖.为什么这样说,

    因为qsort函数在实现中仅仅是返回qsort_r函数的执行结果,没有其他任何操作,所以说是完全依赖.下面去看看qsort_r

    函数.

      1 void
      2 qsort_r (void *b, size_t n, size_t s, __compar_d_fn_t cmp, void *arg)
      3 {
      4   size_t size = n * s;
      5   char *tmp = NULL;
      6   struct msort_param p;
      7 
      8   /* For large object sizes use indirect sorting.  */
      9   if (s > 32)
     10     size = 2 * n * sizeof (void *) + s;
     11 
     12   if (size < 1024)
     13     /* The temporary array is small, so put it on the stack.  */
     14     p.t = __alloca (size);
     15   else
     16     {
     17       /* We should avoid allocating too much memory since this might
     18      have to be backed up by swap space.  */
     19       static long int phys_pages;
     20       static int pagesize;
     21 
     22       if (phys_pages == 0)
     23     {
     24       phys_pages = __sysconf (_SC_PHYS_PAGES);
     25 
     26       if (phys_pages == -1)
     27         /* Error while determining the memory size.  So let's
     28            assume there is enough memory.  Otherwise the
     29            implementer should provide a complete implementation of
     30            the `sysconf' function.  */
     31         phys_pages = (long int) (~0ul >> 1);
     32 
     33       /* The following determines that we will never use more than
     34          a quarter of the physical memory.  */
     35       phys_pages /= 4;
     36 
     37       pagesize = __sysconf (_SC_PAGESIZE);
     38     }
     39 
     40       /* Just a comment here.  We cannot compute
     41        phys_pages * pagesize
     42        and compare the needed amount of memory against this value.
     43        The problem is that some systems might have more physical
     44        memory then can be represented with a `size_t' value (when
     45        measured in bytes.  */
     46 
     47       /* If the memory requirements are too high don't allocate memory.  */
     48       if (size / pagesize > (size_t) phys_pages)
     49     {
     50       _quicksort (b, n, s, cmp, arg);
     51       return;
     52     }
     53 
     54       /* It's somewhat large, so malloc it.  */
     55       int save = errno;
     56       tmp = malloc (size);
     57       __set_errno (save);
     58       if (tmp == NULL)
     59     {
     60       /* Couldn't get space, so use the slower algorithm
     61          that doesn't need a temporary array.  */
     62       _quicksort (b, n, s, cmp, arg);  // 这里才调用Qsort.c中的 _quicksort函数.
     63       return;
     64     }
     65       p.t = tmp;
     66     }
     67 
     68   p.s = s;
     69   p.var = 4;
     70   p.cmp = cmp;
     71   p.arg = arg;
     72 
     73   if (s > 32)
     74     {
     75       /* Indirect sorting.  */
     76       char *ip = (char *) b;
     77       void **tp = (void **) (p.t + n * sizeof (void *));
     78       void **t = tp;
     79       void *tmp_storage = (void *) (tp + n);
     80 
     81       while ((void *) t < tmp_storage)
     82     {
     83       *t++ = ip;
     84       ip += s;
     85     }
     86       p.s = sizeof (void *);
     87       p.var = 3;
     88       msort_with_tmp (&p, p.t + n * sizeof (void *), n);
     89 
     90       /* tp[0] .. tp[n - 1] is now sorted, copy around entries of
     91      the original array.  Knuth vol. 3 (2nd ed.) exercise 5.2-10.  */
     92       char *kp;
     93       size_t i;
     94       for (i = 0, ip = (char *) b; i < n; i++, ip += s)
     95     if ((kp = tp[i]) != ip)
     96       {
     97         size_t j = i;
     98         char *jp = ip;
     99         memcpy (tmp_storage, ip, s);
    100 
    101         do
    102           {
    103         size_t k = (kp - (char *) b) / s;
    104         tp[j] = jp;
    105         memcpy (jp, kp, s);
    106         j = k;
    107         jp = kp;
    108         kp = tp[k];
    109           }
    110         while (kp != ip);
    111 
    112         tp[j] = jp;
    113         memcpy (jp, tmp_storage, s);
    114       }
    115     }
    116   else
    117     {
    118       if ((s & (sizeof (uint32_t) - 1)) == 0
    119       && ((char *) b - (char *) 0) % __alignof__ (uint32_t) == 0)
    120     {
    121       if (s == sizeof (uint32_t))
    122         p.var = 0;
    123       else if (s == sizeof (uint64_t)
    124            && ((char *) b - (char *) 0) % __alignof__ (uint64_t) == 0)
    125         p.var = 1;
    126       else if ((s & (sizeof (unsigned long) - 1)) == 0
    127            && ((char *) b - (char *) 0)
    128               % __alignof__ (unsigned long) == 0)
    129         p.var = 2;
    130     }
    131       msort_with_tmp (&p, b, n);
    132     }
    133   free (tmp);
    134 }

    好吧,到这里我们需要找的源码以及他们之间的关系都理清楚了.在解释qsort_r源码之前,先看一下qsort函数.

    Q1: __nonnull ((1, 4)  这个是什么,看到qsort函数的实现函数后,我们知道这不是一个参数,而且它是一个宏,那么到底是什么呢?

                                    其实这还是很好想到的,即使你没有去查看它的定义.看一下这个宏里面的参数,两个数字,我在前面说到GNU

               C attribute的时候有说过.这个宏是限制参数1和参数4不能为空,注意,参数顺序是从1开始的,不是我们习惯的

                     从0开始.都说到这里了,那我就复习一下,顺便截取一下GNU GCC手册中关于attribute nonnull的部分贴在

                                   下面.

     1 nonnull (arg-index, ...)
     2 The nonnull attribute specifies that some function parameters should be non-null pointers. For instance, the declaration:
     3           extern void *
     4           my_memcpy (void *dest, const void *src, size_t len)
     5                   __attribute__((nonnull (1, 2)));
     6      
     7 causes the compiler to check that, in calls to my_memcpy, arguments dest and src are non-null. If the compiler determines 
    that a null pointer is passed in an argument slot marked as non-null, and the -Wnonnull option is enabled, a warning is issued.
    The compiler may also choose to make optimizations based on the knowledge that certain function arguments will not be null. 8 9 If no argument index list is given to the nonnull attribute, all pointer arguments are marked as non-null. To illustrate,
    the following declaration is equivalent to the previous example: 10 11 extern void * 12 my_memcpy (void *dest, const void *src, size_t len) 13 __attribute__((nonnull));

     顺便贴一下这个宏:

    1 Cdefs.h (misc\sys):# define __nonnull(params) __attribute__ ((__nonnull__ params))

    Q2: __compar_fn_t 这个应该是函数指针类型.根据前面的猜想,它应该是一个两个参数,而且返回整形的一个函数

                                 指针类型.至于它是不是宏呢?不确定.查看一下这个指针可以证明猜想,下面贴出这个指针类型的源码. 

    1 typedef int (*__compar_fn_t) (__const void *, __const void *);

    0.0. 它不是一个宏,宏大多都是以大写字母开始的,但它是一个函数指针,其中的宏__const就是const.这个都可以想的到.

    好了,问题都解决了,那么GNU中的qsort是完全依赖qsort_r函数实现的,那么qsort_r中时如何使用__compar_fn_t类型指针的呢?

    这需要仔细去看看qsort_r的源码,以及涉及到的Qsort.c文件中的__quicksort函数的源码.下面我们就开始吧~~(源码都给出出了)

    额~ 开始不了了.. 足球比赛马上开始了~ 反正文章写的也比较长了,就到这里吧~ 

    另外,最近在看一部关于特种兵生活的电视剧,对战友情狠感兴趣.不断的想,到底战友情是一种怎样的情感~怎样的情感~

     
  • 相关阅读:
    网站上线的过程
    PHP的四种基本算法
    YII框架第三方微博登录
    《正三角》《倒三角》
    PHP实现四种基本排序
    php实现快速排序
    iwebshop 简介
    收集的伪静态中经常使用的一些参数
    我与AI的相识
    phpstudy下的nginx服务器显示目录
  • 原文地址:https://www.cnblogs.com/respawn/p/2722582.html
Copyright © 2020-2023  润新知