一、回顾

时间复杂度 是稳定排序 是原地排序
冒泡排序 O(n2)
插入排序 O(n2)
选择排序 O(n2) ×
快速排序 O(nlogn) ×
归并排序 O(nlogn) ×
计数排序 O(n+k),k是数据范围 ×
桶排序 O(n) ×
基数排序 O(dn) d是维度 ×
  • 线性排序算法的时间复杂度比较低,适用场景比较特殊。
  • 小规模数据进行排序 ==》时间复杂度是 O(n2) 的算法;
  • 大规模数据进行排序 ==》时间复杂度是O(nlogn) 的算法 ==》一般也是首选

二、快速排序的优化

关键点:合理选择分区点,来避免时间复杂度退化为O(n2)

理想的分区点:被区分点分开的两个分区中,数据的数量差不多。
==》常用、简单的分区算法

1、三数取中法

从区间的首、尾、中间,分别取出一个数,然后进行比较,取三个数的中位数作为分区点。
==》扩展:数据大时,“五数取中”、“十数取中”

2、随机法

每次从要排序的区间中,随机选择一个元素作为分区点。
特点:虽不能保证每次分区点都选的比较好,但是从概率角度分析,不太可能出现每次分区点都很差的情况。

3、警惕堆栈溢出

快速排序使用堆栈实现的。==》 递归要警惕堆栈溢出

解决方法:
(1)限制递归深度。一旦递归过深(超过事先设定的阈值),就停止递归。
(2)通过在堆上模拟实现一个函数调用栈,手动模拟递归压栈、出栈的过程,使得系统栈大小没有限制。

三、qsort()函数的分析

1、源码

/* Copyright (C) 1991,1992,1996,1997,1999,2004 Free Software Foundation, Inc.This file is part of the GNU C Library.Written by Douglas C. Schmidt (schmidt@ics.uci.edu).The GNU C Library is free software; you can redistribute it and/ormodify it under the terms of the GNU Lesser General PublicLicense as published by the Free Software Foundation; eitherversion 2.1 of the License, or (at your option) any later version.The GNU C Library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNULesser General Public License for more details.You should have received a copy of the GNU Lesser General PublicLicense along with the GNU C Library; if not, write to the FreeSoftware Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA02111-1307 USA.  *//* If you consider tuning this algorithm, you should consult first:Engineering a sort function; Jon Bentley and M. Douglas McIlroy;Software - Practice and Experience; Vol. 23 (11), 1249-1265, 1993.  */#include <alloca.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>/* Byte-wise swap two items of size SIZE. */
/* 以字节为单位交换两个SIZE长度数组的SWAP宏 */
#define SWAP(a, b,size)
do
{  register size_t __size = (size);register char *__a = (a), *__b = (b); do{har __tmp = *__a;*__a++ = *__b;*__b++ = __tmp;} while (--__size > 0);
} while (0)/* Discontinue quicksort algorithm when partition gets below this size.This particular magic number was chosen to work best on a Sun 4/260. */
/* 当一个数据段的长度小于这个值的时候,将不用快排对其进行排序。因为这个特别的的魔术数在Sun 4/260下的性能最好*/
#define MAX_THRESH 4
/* Stack node declarations used to store unfulfilled partition obligations. */
/* 用来存储还没处理的数据段索引*/
typedef struct
{char *lo;char *hi;
} stack_node;/* The next 4 #defines implement a very fast in-line stack abstraction. */
/* 下面四个宏实现了一个非常快速的栈(的确很快,但是现实中真要这样做,请确保1. 这是核心代码。2. 这些代码百分比正确。要不然出错了,调试调死你,维护的人骂死你。)*/
/* The stack needs log (total_elements) entries (we could even subtract log(MAX_THRESH)).Since total_elements has type size_t, we get as upper bound for log (total_elements):bits per byte (CHAR_BIT) * sizeof(size_t).  */
/* 栈需要log(total_elements)个元素(当然我们也可以在这个基础上减去log(MAX_THRESH)。)(PS:log(x/y) = log(x) - log(y))因为传入数据个数total_elements的类型是size_t,所以我们可以把栈元素上限设为:size_t的位数(CHAR_BIT) * sizeof(size_t)。PS:栈用来记录的是还没处理的数据段索引,最坏的情况是分开的两个数据段其中一个已经不用再分,这个时候栈不许要任何记录。最好的情况是进行log(total_elements)次划分,此时栈需要记录log(total_elements)个索引,但是这个算法在一个分片的元素个数小于MAX_THRESH便不再划分,所以实际只需log(total_elements / MAX_THRESH)个空间。CHAR_BIT在limits.h有定义,意思是一个字节有多少位,因为sizeof是算出一种类型占几个字节,所以(CHAR_BIT) * sizeof(size_t)是当total_elements去最大值的值,也就是这里栈元素个数的上限。*/
#define STACK_SIZE     (CHAR_BIT * sizeof(size_t))
#define PUSH(low, high)  ((void) ((top->lo = (low)), (top->hi = (high)), ++top))
#define POP(low, high)  ((void) (--top, (low = top->lo), (high = top->hi)))
#define STACK_NOT_EMPTY  (stack < top)/* Order size using quicksort.  This implementation incorporatesfour optimizations discussed in Sedgewick:这个快排的程序实现了Sedgewick书中讨论的四个优化,下面从大到小说明:(大概这意思...)1. Non-recursive, using an explicit stack of pointer that store thenext array partition to sort.  To save time, this maximum amountof space required to store an array of SIZE_MAX is allocated on thestack.  Assuming a 32-bit (64 bit) integer for size_t, this needsonly 32 * sizeof(stack_node) == 256 bytes (for 64 bit: 1024 bytes).Pretty cheap, actually.1. 不用递归,用了显示的指针栈存储下一段要排序的数据。为了节省时间,为栈申请了最大的存储空间。假设size_t是一个32位(64位)的整数,这里仅需要 32 * sizeof(stack_node) = 256 bytes(对于64位:1024bytes)。事实上很小。(一个栈节点两指针,32位就是2 * 4 个字节,64位是8 * 2位字节)2. Chose the pivot element using a median-of-three decision tree. This reduces the probability of selecting a bad pivot value andeliminates certain extraneous comparisons.2. 用中值决策树选择关键值。这减小了选择一个差关键值的可能性和排除特定的无关的比较。3. Only quicksorts TOTAL_ELEMS / MAX_THRESH partitions, leavinginsertion sort to order the MAX_THRESH items within each partition.This is a big win, since insertion sort is faster for small, mostlysorted array segments.3. 只用快排对TOTAL_ELEMS / MAX_THRESH个数据段进行了排序,用插入排序对每个数据段的MAX_THRESH个数据进行排序。这是一个很好的改进,因为插入排序在处理小的、基本有序的数据段时比快排更快。
4. The larger of the two sub-partitions is always pushed onto thestack first, with the algorithm then concentrating on thesmaller partition.  This *guarantees* no more than log (total_elems)stack size is needed (actually O(1) in this case)!4. 大的数据分段通常先压入栈内,算法优先处理小的数据分段。这就保证栈的元素不会超过log(total_elems)(事实上这里只用了常数个空间)。
*/
void _quicksort (void *const pbase, size_t total_elems, size_t size, __compar_d_fn_t cmp, void *arg)
{/* 寄存器指针,最快的指针,当然系统不一定会把它放到寄存器。register只是一种建议。*/register char *base_ptr = (char *) pbase;const size_t max_thresh = MAX_THRESH * size;if (total_elems == 0)/* Avoid lossage with unsigned arithmetic below.  */return;if (total_elems > MAX_THRESH){char *lo = base_ptr;char *hi = &lo[size * (total_elems - 1)];/* 因为用了上面栈的宏,所以下面两个变量的名字一定不能改...*/stack_node stack[STACK_SIZE];stack_node *top = stack;PUSH (NULL, NULL);while (STACK_NOT_EMPTY){char *left_ptr;char *right_ptr;/* Select median value from among LO, MID, and HI. RearrangeLO and HI so the three values are sorted. This lowers theprobability of picking a pathological pivot value and skips a comparison for both the LEFT_PTR and RIGHT_PTR in the while loops. *//* 在数组的第一位、中间一位、最后一位中选出一个中值。同时也会对第一位和最后一位进行排序以达到这三个值都是有序的目的。这降低了选择一个很烂的关键值的可能性,同时也跳过了左指针和右指针在while循环里面的一次比较。*/char *mid = lo + size * ((hi - lo) / size >> 1);if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)SWAP (mid, lo, size);if ((*cmp) ((void *) hi, (void *) mid, arg) < 0)SWAP (mid, hi, size);elsegoto jump_over;if ((*cmp) ((void *) mid, (void *) lo, arg) < 0)SWAP (mid, lo, size);
jump_over:;left_ptr  = lo + size;right_ptr = hi - size;/* Here's the famous ``collapse the walls'' section of quicksort.Gotta like those tight inner loops!  They are the main reasonthat this algorithm runs much faster than others. *//* 这里就是快排中著名的“collapse the walls(推墙??)”。和那些紧凑的内层循环非常像!它们是这个算法比其他算法快的主要原因。PS:了解过快排的应该对下面这一段都比较熟悉,就是把比关键值小的移到数组左边,比关键值大的移到数组右边,把数据分成大小两段的过程*/do{while ((*cmp) ((void *) left_ptr, (void *) mid, arg) < 0)left_ptr += size;while ((*cmp) ((void *) mid, (void *) right_ptr, arg) < 0)right_ptr -= size;if (left_ptr < right_ptr){SWAP (left_ptr, right_ptr, size);if (mid == left_ptr)mid = right_ptr;else if (mid == right_ptr)mid = left_ptr;left_ptr += size;right_ptr -= size;}else if (left_ptr == right_ptr){left_ptr += size;right_ptr -= size;break;}}while (left_ptr <= right_ptr);/* Set up pointers for next iteration.  First determine whetherleft and right partitions are below the threshold size.  If so,ignore one or both.  Otherwise, push the larger partition'sbounds on the stack and continue sorting the smaller one. *//*  给下次迭代的指针赋值。首先判断左右两段数据的元素个数是否小于阈值,如果是,跳过这一个或两个分段。如若不是,把大的数据段的开始和结束指针入栈,继续划分小的数据段。*/if ((size_t) (right_ptr - lo) <= max_thresh){/* 左右两个数据段的元素都小于阈值,取出栈中数据段进行划分*/if ((size_t) (hi - left_ptr) <= max_thresh)/* Ignore both small partitions. */POP (lo, hi);else/* Ignore small left partition. (只有左边大于阈值)*/lo = left_ptr;}else if ((size_t) (hi - left_ptr) <= max_thresh)/* Ignore small right partition. (只有右边大于阈值)*/hi = right_ptr;else if ((right_ptr - lo) > (hi - left_ptr)){/* Push larger left partition indices. *//* 两个数据段的元素个数都大于阈值,大的入栈,小的继续划分。 */PUSH (lo, right_ptr);lo = left_ptr;}else{/* Push larger right partition indices. *//* 两个数据段的元素个数都大于阈值,大的入栈,小的继续划分。 */PUSH (left_ptr, hi);hi = right_ptr;}}}/* Once the BASE_PTR array is partially sorted by quicksort the restis completely sorted using insertion sort, since this is efficientfor partitions below MAX_THRESH size. BASE_PTR points to the beginningof the array to sort, and END_PTR points at the very last element inthe array (*not* one beyond it!). *//*  当数组经过快排的排序后,已经是整体有序了。剩下的排序由插入排序完成,因为数据段小于MAX_THRESH时,插入排序效率更高。此时排序的首指针是数组的首指针,尾指针是数组的尾指针(不是倒数第二个)*/#define min(x, y) ((x) < (y) ? (x) : (y)){char *const end_ptr = &base_ptr[size * (total_elems - 1)];char *tmp_ptr = base_ptr;char *thresh = min(end_ptr, base_ptr + max_thresh);register char *run_ptr;/* Find smallest element in first threshold and place it at thearray's beginning.  This is the smallest array element,and the operation speeds up insertion sort's inner loop. *//* 找出第一段的最小一个值并把它放在数组的第一个位置。这是数组的最小元素(用快排排过,应该比较容易理解),这一步可以加入插入排序的内层循环*/for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)if ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)tmp_ptr = run_ptr;if (tmp_ptr != base_ptr)SWAP (tmp_ptr, base_ptr, size);/* Insertion sort, running from left-hand-side up to right-hand-side.  *//* 从左到右执行插入排序。*/run_ptr = base_ptr + size;while ((run_ptr += size) <= end_ptr){/*上面说的加速内层循环,就是不用在这里判断*/tmp_ptr = run_ptr - size;/*一直往回找直到找到大于或等于当前元素的元素*/while ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < 0)tmp_ptr -= size;/*把当前元素移到找出元素的后面去*/tmp_ptr += size;if (tmp_ptr != run_ptr){char *trav;trav = run_ptr + size;while (--trav >= run_ptr){char c = *trav;char *hi, *lo;/*这个内层循环只是把每个元素的最后一位移到后面一个元素去*/for (hi = lo = trav; (lo -= size) >= tmp_ptr; hi = lo)* hi = *lo;*hi = c;}}}}
}

2、分析

通过多种排序算法实现的:

  • 当数据量较小是,qsort() 会优先使用归并算法来排序输入数据(原因:空间复杂度为O(n),空间换时间)
  • 当数据量太大,qsort() 使用快速排序,分区点选择方法:“三数取中法”
  • 递归太深会导致堆栈溢出问题的解决方法:qsort()实现了一个堆上的栈,手动模拟递归来解决该问题。
  • 当排序的区间中的元素小于等于4时,qsort()就退化为插入排序,不再使用递归来做快速排序。原因:在小规模数据面前,O(n2时间复杂度并不一定比O(nlogn)的算法执行时间长)
    • 时间复杂度代表增长趋势,但是在表示的过程中,我们会省略低阶、系数和常数。例:O(knlogn + c)时间复杂度中的 k 和 c 可能还是一个比较大的数。

七、排序(4)——qsort()相关推荐

  1. c语言qsort函数简介,C语言排序函数—qsort函数

    前言: 在一些编程题中经常需要你按照某个指标按照从小到大或从大到小输出一些数据,这时你可以自己写一个排序函数进行排序,但是其实C语言函数库中就有一个排序函数--qsort函数,使用它可以节省你单独写排 ...

  2. 数据结构 实验七 排序技术的编程实现

    实验七  排序技术的编程实现 [实验目的] 排序技术的编程实现 要求: 排序技术的编程实现(2学时,综合型),掌握排序技术的编程实现,可以实现一种,也可以实现多种.也鼓励学生利用基本操作进行一些应用的 ...

  3. C语言排序函数——qsort

    C语言排序函数--qsort: qsort函数原型: void __cdecl qsort(void *_Base,size_t _NumOfElements,size_t _SizeOfElemen ...

  4. C语言学习笔记---C库排序函数qsort()

      在处理数据的时候,由于排序方法会经常用到,所以C语言的库函数里面自带了快速排序的函数qsort(),对大型数据而言,"快速排序"方法是最有效的排序方法之一.它是把数组不断的分成 ...

  5. 排序函数qsort和sort那点事

    目录 前言 qsort函数(c语言用) 各参数及其说明 qsort用法实例 sort函数(用于c++) sort模板及其参数说明 sort实例 注意 : 前言 咱们初学编程的可能最先接触的算法就是排序 ...

  6. C语言-排序-快速排序-qsort<stdlib.h>

    想到排序,大多数人第一个想到的都是冒泡排序,今天介绍一种函数,叫快速排序qsort函数,在讲这个函数之前,先将冒泡排序(数字)的代码给大家,如果想排序字符串,请大家使用strcmp函数即可 这是C语言 ...

  7. C语言排序函数qsort( )

    关于c语言排序的问题,以前我都是用冒泡排序或者快速排序,知道这几天,在杭电上面打ACM的时候,才发现又一种排序的函数,就是qsort(): qsort()括号里面有4个参数 第一个参数是将要排序的数组 ...

  8. C语言的排序函数qsort()详解

    一.qsort()函数的用法及使用说明: 目录 一.qsort()函数的用法及使用说明: 二.使用qsort()函数来求关于各种类型的(降序)排序 1.int类型的数组进行排序 2.char类型的数组 ...

  9. QT QVectorQPairQString, qint64 qSort 排序

    QT的算法与容器之类的与存C++有一些区别. 头文件: #include <qalgorithms.h> //这个用于qt排序算法qSort的. #include <QVector& ...

最新文章

  1. HANA 数据库备份hang住的解决办法
  2. 第十、十一周项目一-点-圆-圆柱类族的设计(1)
  3. Wireshark — Packet size limited during capture
  4. java applet 官网_java applet
  5. RocketMQ-初体验RocketMQ(06)-使用API操作RocketMQ ,理解RocketMQ的存储结构
  6. [转载]如何将Putty生成的PrivateKey转换为SecureCRT所需的PublicKey
  7. sde oracle11g,Arcsde post oracle11g报错解决办法
  8. 拒绝无用的长篇大论!仅12张图片,最全的中台精华都在这里了
  9. 1010 一元多项式求导(C语言)
  10. python查找字符串所在行_使用Python CSV和glob查找匹配的字符串和打印行
  11. 不是一流大学毕业,却通过自学软件测试,进了阿里年薪初始22K
  12. python进程socket通信_python3 进程间通信之socket.socketpair()
  13. 为什么选择红黑树作为底层实现
  14. 多种视觉SLAM方案对比
  15. Vue - 选择器拼音快速检索目标(pinyin-match)
  16. Win11磁盘碎片整理在哪?Win11机械硬盘磁盘碎片整理方法
  17. 6 数据的表现形式常量以及常量的几种类型
  18. OpenGL纹理过滤以及纹理Wrapping mode
  19. 结对编程-俞神JAVA代码赏析
  20. 处处吻(粤语汉字英译)

热门文章

  1. 将信息写入TXT文本中(java)
  2. matlab画倾斜的椭球,在MATLAB中绘制椭圆和椭球
  3. python语言的解释性特点指的是编写的程序不需要编译_解释性与编译型 Python2和python3的区别...
  4. 关于string转int、float、double常用方法
  5. 同济大学计算机基础教研网,消除部分依赖S1(Snum,Sname,Sdept-同济大学计算机基础教研室.ppt...
  6. Linux进程和计划任务管理
  7. 用php怎么输出饼状图,php绘图之生成饼状图的方法_PHP
  8. vb.net如何查询电脑麦克风收到声音_如何正确使用苹果电脑?
  9. 用Go开发支持百万级数据量的高性能缓存服务
  10. 山体等高线怎么看_怎么判断牙龈已经萎缩?