/* 版权所有(C) 1991-2019 自由软件资金会。
该文件属于是GUN C语言函数库,由Douglas C. Schmidt(schmidt@ics.uci.edu)所写。
GUN C语言函数库是自由软件;如果你拥有2.1版本及以后的GUN自由软件基金发布的GUN 小众通用公共许可证,你可以重写或者修改它。
GUN C语言库致力于希望它是公用的,但不是没有任何授权的,甚至没有隐含的适销性或适合某一特定用途的保证。详情请看GUN 小众通用公共许可证。
如果你没有得到GUN 小众通用公共许可证,请看 (http://www.gnu.org/licenses/)。*/
/* 在你更改这个算法之前你应该先咨询排序算法工程师Jon Bentley 和 M. Douglas McIlory;
软件已通过测试;Vol. 23 (11), 1249-1265, 1993。*/
#include <alloca.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
/* 按字节交换两个SIZE大小的变量 */
#define SWAP(a, b, size)
do \
{ \
size_t __size = (size); \
char *__a = (a), *__b = (b); \
do \
{ \
char __tmp = *__a; \
*__a++ = *__b; \
*__b++ = __tmp; \
} while (--__size > ); \
} while ()
/* 1)size可以增加代码的灵活性,不再由数据类型限制,这点可以学习一下;
2)写成宏的形式将代码直接复制到宏的适用处,免除了函数调用,对于一个经常适用的SWAP功能来说提高了执行速度 */ /* 当快速排序算法的分区大小小于4时,将会停止使用快速排序算法 */
#define MAX_THRESH 4
/* 适用栈来存储超过分区大小的数据 */
typedef struct
{
char *lo;
char *hi;
} stack_node;
/* 接下来的4个define宏说明了快速的内联栈操作 */
/* 栈的结点数为log(total_elements)(甚至我们可以减去log(MAX_THRESH));
因为tatol_elements是size_t类型,所以我们得到log(total_elements)的上限为:(CHAR_BIT) * sizeof(size_t),其中CHAR_BIT为每字节的位数 */
#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)
/* 1)栈为顺序栈,每个数据元素包含一个内存块的低地址和高地址;2)通过char*打破数据类型的限制,使得多种数据类型都可以使用。 */ /* 在规定大小内使用快速排序。这个实现包含了Sedgewick讨论的四个优化:
1. 非递归,使用一个存放者指向下一个待排序的分区数据的指针的栈来保存现场。为了节省时间,要求在栈上分配最大分区数量的空间。
假设size_t的大小为32bit整数(64bit),它只需要32 * sizeof(stack_node) == 256 byte(64bit: 1024bytes),实际可能更少。
2. 通过三值取中的方法选择枢轴元素。这减少了选择了一个不好的枢轴值得可能性并且较少了一些额外的比较。
3. 快速排序只划分 TOTAL_ELEMS / MAX_THRESH 个分区,划分后的分区最多4个数据元素,使用插入排序算法进行排序。
这样做有一个很大的优势,插入排序对于小规模的数组排序速度更快。
4. 将两个分区中较大的分区先入栈,然后将算法作用于较小的分区。这保证了实际并不需要log (total_elems)的栈长(在这种情况下空间复杂度为O(1))。*/
void
_quicksort (void *const pbase, size_t total_elems, size_t size, __compar_d_fn_t cmp, void *arg)
{
char *base_ptr = (char *) pbase;
const size_t max_thresh = MAX_THRESH * size;
if (toal_elems == )
/* 待排序元素为0 */
return;
if (total_elems > MAX_THRESH)
{
char *lo = base_ptr;
char *hi = &lo[size * (total_elems - )];
stack_node stack[STACK_SIZE];
stack_node *top = stack;
PUSH(NULL, NULL); /* 初始化 */
while (STACK_NOT_EMPTY)
{
char *left_ptr;
char *right_ptr;
/* 从头元素、尾元素和中间元素中选择中间值。重新排序头元素和尾元素知道三个元素有序。
将中间值作为枢轴值并且在整个过程中跳过了左值和右值的比对。 */
char *mid = lo + size * ((hi - lo) / size >> );
if ((*cmp) ((void *) mid, (void *) lo, arg) < )
SWAP(mid, lo, size);
if ((*cmp) ((void *) hi, (void *) mid, arg) < )
SWAP(hi, mid, size);
else
goto jump_over;
if ((*cmp) ((void *) mid, (void *) lo, arg) < )
SWAP(mid, lo, size);
jump_over:;
left_ptr = lo + size;
right_ptr = hi - size;
/* 这里有个有名的快速排序上下界限问题。就像它一直运行在内层循环。主要原因是这个算法运行速度比其他真是快太多了。 */
do
{
while ((*cmp) ((void *) left_ptr, (void *) mid, arg) < )
left_ptr += size;
while ((*cmp) ((void *) mid, (void *) right_ptr, arg) < )
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)
/* 设置下一个迭代对象的指针。首先判断左右两个分区的大小是否小于门槛值。如果是,那么将小于门槛值的略过。
否则,将较大的分区压入栈中,继续排序较小的分区。 */
if ((size_t) (right_ptr - lo) <= max_thresh)
{
if ((size_t) (hi - left_ptr) <= max_thresh)
/* 略过两个大小小于等于4的分区 */
POP(lo, hi);
else
/* 略过大小小于等于4的左分区 */
lo = left_ptr;
}
else if ((right_ptr - lo) > (hi - left_ptr))
{
/* 将较大左分区的索引入栈 */
PUSH(lo, right_ptr);
lo = left_ptr;
}
else
{
/* 将较大的右分区索引入栈 */
PUSH(left_ptr, hi);
hi = right_ptr;
}
}
}
/* 一旦BASE_PTR指向的数组被快速排序算法划分出大小下于等于4的分区,划分出的小分区完全右插入排序算法进行排序。
BASE_PTR指向待排序数组的开始地址,END_PTR指向数组的最后一个元素的起始地址(没有超过数组地址范围)。 */
#define min(x, y) ((x) < (y) ? (x) : (y))
{
char *const end_ptr = &base_ptr[size * (total_elems - )];
char *tmp_ptr = base_ptr;
char *thresh = min(end_ptr, base_ptr + max_thresh);
char *run_ptr;
/* 查找数组中最小的数据元素并且将其放在数组的首元素位置,这个操作将加速插入排序的内层循环 */
for (run_ptr = tmp_ptr + size; run_ptr <= thresh; run_ptr += size)
if ((*cmp) ((void *) run_ptr, (void *) tmp_ptr, arg) < )
tmp_ptr = run_ptr;
if (tmp_ptr != base_ptr)
SWAP(tmp_ptr, base_ptr, size);
/* 插入排序, 从数组的左边运行到右边 */
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) < )
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;
}
}
}
}
}

qsort.c源码的更多相关文章

  1. C语言标准库 qsort bsearch 源码实现

    C语言是简洁的强大的,当然也有很多坑.C语言也是有点业界良心的,至少它实现了2个最最常用的算法:快速排序和二分查找. 我们知道,对于C语言标准库 qsort和 bsearch: a. 它是“泛型”的, ...

  2. Openssl编程--源码分析

    Openssl编程 赵春平 著 Email: forxy@126.com 第一章 基础知识 8 1.1 对称算法 8 1.2 摘要算法 9 1.3 公钥算法 9 1.4 回调函数 11 第二章 ope ...

  3. 侯捷STL课程及源码剖析学习1

    1.C++标准库和STL C++标准库以header files形式呈现: C++标准库的header files不带后缀名(.h),例如#include <vector> 新式C hea ...

  4. 源码分析:动态分析 Linux 内核函数调用关系

    源码分析:动态分析 Linux 内核函数调用关系 时间 2015-04-22 23:56:07  泰晓科技 原文  http://www.tinylab.org/source-code-analysi ...

  5. PHP与Memcached服务器交互的分布式实现源码分析

    转自: http://blog.csdn.net/hguisu/article/details/7353595 前段时间,因为一个项目的关系,研究了php通过调用memcache和memcached ...

  6. Redis系列(十):数据结构Set源码解析和SADD、SINTER、SDIFF、SUNION、SPOP命令

    1.介绍 Hash是以K->V形式存储,而Set则是K存储,空间节省了很多 Redis中Set是String类型的无序集合:集合成员是唯一的. 这就意味着集合中不能出现重复的数据.可根据应用场景 ...

  7. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  8. C# ini文件操作【源码下载】

    介绍C#如何对ini文件进行读写操作,C#可以通过调用[kernel32.dll]文件中的 WritePrivateProfileString()和GetPrivateProfileString()函 ...

  9. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

随机推荐

  1. DateTimeHelper【日期类型与字符串互转以及日期对比相关操作】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 实现日期和字符串之间的转换以及日期的相关操作: 1.日期格式的字符串输出为Date类型: 2.将Date类型以指定格式输出: 3.将 ...

  2. Spring:(一)入门基础学习

    前述 因为前几日刚刚学完Spring的基础,因此写一篇博客整理一下相关的基础知识. 什么是Spring? Spring 是一个轻量级的 DI / IoC 和 AOP 容器的开源框架,帮助分离项目组件之 ...

  3. 4.3dotnet watch run「深入浅出ASP.NET Core系列」

    希望给你3-5分钟的碎片化学习,可能是坐地铁.等公交,积少成多,水滴石穿,谢谢关注. dotnet run的麻烦 如果您使用的是vs code进行跨平台开发,那么dotnet watch run对你的 ...

  4. 使用cobbler工具实现centos 6,7系统的自动化安装

    vmware里面准备两台虚拟机,一台用于安装cobbler服务器,另一台当作测试机使用,cobbler服务器需要两块网卡,一块需要连接外网,需要使用epel源.测试机使用一块仅主机的模式的网卡,注意要 ...

  5. Odoo 强大的开源微信模块 oejia_wx

    详见:http://oejia.net/blog/2018/10/24/oejia_wx_v054.html oejia_wx Odoo 的微信模块,提供了对微信公众号.企业号(企业微信)及小程序的接 ...

  6. arcgis api 3.x for js 实现克里金插值渲染图不依赖 GP 服务(附源码下载)

    前言 关于本篇功能实现用到的 api 涉及类看不懂的,请参照 esri 官网的 arcgis api 3.x for js:esri 官网 api,里面详细的介绍 arcgis api 3.x 各个类 ...

  7. jQuery元素操作

    jQuery中创建元素及追加元素 DOM中可以动态创建元素:document.createElement(“标签的名字”); jQuery中同样可以创建元素标签,并且返回的就是jQuery对象,可以直 ...

  8. iBatis第四章:动态SQL的用法

    一.什么是动态SQL,以及使用动态SQL的好处 所谓动态SQL,是针对静态SQL而言的,静态SQL的SQL语句是固定的,使用动态SQL是为了增强SQL的灵活性和复用性,可以用一个动态SQL达到在不同条 ...

  9. Django的URL调度

    1.URLconf (URL configuration):(Django版本1.11.20,其它版本可能各有差异.) 在Django中Python后端与前端URL进行交互,是通过一个名为urlcon ...

  10. socket.io 出现的WebSocket is closed before the connection is established

    WebSocket is closed before the connection is established 最近socket.io是挺流行的,幼麟棋牌和一些好的开源项目也使用这个框架,在搭建其平 ...