/* 版权所有(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. C# 《编写高质量代码改善建议》整理&笔记 --(五)类型设计

    1.区分接口和抽象类的应用场合 区别: ①接口支持多继承,抽象类则不能. ②接口可以包含方法,属性,索引器,事件的签名,但不能有实现,抽象类则可以. ③接口在增加新方法后,所有的继承者都必须重构,否则 ...

  2. DensityUtil【尺寸转换工具类(px、dp互相转换)】

    版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 用于项目中dp.px.sp之间的转换以及指定缩放值下的转换. 效果图 暂不需要 代码分析 常用的方法是px2dip.dip2px: ...

  3. Kubernetes的污点和容忍(下篇)

    背景 继上一篇<Kubernetes的污点和容忍(上篇)>,这是https://kubernetes.io/docs/concepts/configuration/taint-and-to ...

  4. SpringSecurity自定义AuthenticationProvider和AuthenticationFilter

    AuthenticationProvider 默认实现:DaoAuthenticationProvider 授权方式提供者,判断授权有效性,用户有效性,在判断用户是否有效性,它依赖于UserDetai ...

  5. Java进阶篇设计模式之四 -----适配器模式和桥接模式

    前言 在上一篇中我们学习了创建型模式的建造者模式和原型模式.本篇则来学习下结构型模式的适配器模式和桥接模式. 适配器模式 简介 适配器模式是作为两个不兼容的接口之间的桥梁.这种类型的设计模式属于结构型 ...

  6. RDIFramework.NET V3.3 WinForm版角色授权管理新增角色对操作权限项、模块起止生效日期的设置

    在实际应用在我们可能会有这样的需求,某个操作权限项(按钮)或菜单在某个时间范围内可以让指定角色访问.此时通过我们的角色权限扩展设置就可以办到. 在我们框架V3.3 WinForm版全新增加了角色权限扩 ...

  7. Data Lake Analytics + OSS数据文件格式处理大全

    0. 前言 Data Lake Analytics是Serverless化的云上交互式查询分析服务.用户可以使用标准的SQL语句,对存储在OSS.TableStore上的数据无需移动,直接进行查询分析 ...

  8. cocos creator主程入门教程(十一)—— 有限状态机和行为树

    五邑隐侠,本名关健昌,10年游戏生涯,现隐居五邑.本系列文章以TypeScript为介绍语言. 本篇介绍有限状态机和行为树.有限状态机用于有限的状态下的AI,由于同时只能处于一个状态,多个状态需要多个 ...

  9. .NET移动开发,关于发布IOS的方法(本人亲身经历折腾很久终于成功)

    前情提要:这位.NET程序员兄弟使用Smobiler开发了一个APP,尽管Smobiler云平台已经最大限度的简化了iOS应用的打包操作,但仍绕不开苹果公司强制要求的p12文件,p12文件需要开发者自 ...

  10. oracle非正常退出后重启实例

    sqlplus /nolog 回车 conn / as sysdba 回车 startup 回车(如果被告知已启动,应先执行 shutdown immediate 回车)