通过前面问题以及引入了“信息熵”的概念,我们可以重新来理解排序的本质:

一组未排序的N个数字,它们一共有N!种重排,其中只有一种排列是满足题意的(譬如从大到小排列)。

换句话说,排序问题的可能性一共有N!种。任何基于比较的排序的基本操作单元都是“比较a和b”,这就相当于猜数字游戏里面的一个问句,显然这个问句的答案只能是“是”或“否”,一个只有两种输出的问题最多只能将可能性空间切成两半,根据上面的思路,最佳切法就是切成1/2和1/2(将数组切成一半)。也就是说,我们希望在比较了a和b的大小关系之后,如果发现a < b的话剩下的排列可能性就变成N!/2,如果发现a>b也是剩下N!/2种可能性。

由于假设每种排列的概率是均等的,所以这也就意味着支持a < b的排列一共有N!/2个,支持a > b的也是N!/2个,换言之,a < b的概率等于a > b的概率。

我们希望每次在比较a和b的时候,a < b和a > b的概率是均等的,这样我们就能保证无论如何都能将可能性缩小为原来的一半的最优下界。

一个直接的推论是,如果每次都像上面这样的完美比较,那么N个元素的N!种可能排列只需要log2N!就排查完了,而log2N!近似于NlogN。这正是快排的复杂度。

快速排序的实现

我们先理解一下快速排序的工作机制吧,下面是《算法导论》里的快排:

QUICKSORT(A, p, r)

if p < r

then q ← PARTITION(A, p, r) //关键

QUICKSORT(A, p, q - 1)

QUICKSORT(A, q + 1, r)

快速排序算法的关键是PARTITION过程,它对A[p..r]进行就地重排:

PARTITION(A, p, r)

x ← A[r]

i ← p - 1

for j ← p to r - 1

do if A[j] ≤ x

then i ← i + 1

exchange A[i] A[j]

exchange A[i + 1] A[r]

return i + 1

我们将上面的过程用C语言描述一下:

#include "stdio.h"

#include "math.h"

#include "stdlib.h"

void PrintArray(int *arr);

void swap(int *a,int *b);

int num = 10;

void QuickSort(int *arr, int beg, int end)

{

if(beg < end)

{

int pivot = Partition(arr, beg, end);

QuickSort(arr, beg, pivot-1);

QuickSort(arr, pivot+1, end);

}

}

void swap(int *a,int *b)

{

int tmp;

tmp = *a;

*a = *b;

*b = tmp;

}

int Partition(int *arr, int beg, int end)

{

int j;

int sentinel = arr[end];

printf("\n sentinel = arr[%d] = %d", end, sentinel);

int i = beg-1;

for(j=beg; j<=end-1; ++j)

{

if(arr[j] <= sentinel)

{

printf("\n arr[%d](%d) <= sentinel(%d)", j, arr[j], sentinel);

i++;

swap(&arr[i], &arr[j]);

}

}

swap(&arr[i+1], &arr[end]);

printf("\n排序过程:");

PrintArray(arr);

return i+1;

}

void PrintArray(int arr[])

{

int i;

for(i=0; i < num; ++i)

{

printf("%d ", arr[i]);

}

}

int main()

{

int i;

int arr[10];

srand(time(0));

for(i=0; i < 10; i++)

{

arr[i] = rand()%100+1;

//printf("%d ", rand()%100+1);

}

printf("初始数组:");

PrintArray(arr);

QuickSort(arr, 0, num-1);

printf("\n最后结果:");

PrintArray(arr);

return 0;

}

初始数组:59 40 55 92 73 69 27 79 3 30

sentinel = arr[9] = 30

arr[6](27) <= sentinel(30)

arr[8](3) <= sentinel(30)

排序过程:27 3 30 92 73 69 59 79 40 55

sentinel = arr[1] = 3

排序过程:3 27 30 92 73 69 59 79 40 55

sentinel = arr[9] = 55

arr[8](40) <= sentinel(55)

排序过程:3 27 30 40 55 69 59 79 92 73

sentinel = arr[9] = 73

arr[5](69) <= sentinel(73)

arr[6](59) <= sentinel(73)

排序过程:3 27 30 40 55 69 59 73 92 79

sentinel = arr[6] = 59

排序过程:3 27 30 40 55 59 69 73 92 79

sentinel = arr[9] = 79

排序过程:3 27 30 40 55 59 69 73 79 92

最后结果:3 27 30 40 55 59 69 73 79 92

Process returned 0 (0x0) execution time : 0.564 s

Press any key to continue.

从程序的运行结果我们就可以很清晰地看出快速排序的工作工程:

定点sentinel设为数组最后一个元素30

把比30小的划成一个小组(27,3,30),并把它们放在数组的前面。

定点sentinel设为小组的最后一个,不包含30,即(27,3)中的3。

对小组原地排序,即(3,27)。这样就完成这个小组的排序了(3,27,30)。

定点sentinel再次设为数组最后一个元素55。

把小于55的元素找出来划分为另外的小组(40)。

那个小组的排序也已经完成(40,55)。

定点再设为73,同样分组(69,59,73),排序为(59,69,73)。

定点再设为79,分组(79)

完成排序:3 27 30 40 55 59 69 73 79 92

总结下快排的过程:随机选择一个元素做“轴元素”(上面的定点sentinel),将所有小于轴元素的移到左边,其余移到右边。根据这个过程,快排的第一次比较就是将一个元素和轴元素比较,这个时候显而易见的是,“大于”和“小于”的可能性各占一半。这是一次漂亮的比较。

延伸阅读

此文章所在专题列表如下:

java编程思想快速排序_快速排序里的学问:快速排序的过程相关推荐

  1. java编程思想怎么样_读完java编程思想后的思考?

    谢邀,这本书真的给我带来很多思考. 我的java入门不是java编程思想,学校的教材是一本紫色的书,已经忘了叫什么名字了,里面内容倒挺新还讲了些javafx.但那本书实在是太浅并且结构混乱,以至于我和 ...

  2. java编程思想 初始化_《java编程思想》_第五章_初始化与清理

    初始化和清理是涉及安全的两个问题,java中采用了构造器,并额外提供了"垃圾回收器",对于不再使用的内存资源,垃圾回收器能自动将其释放. 一.用构造器确保初始化 java中,通过提 ...

  3. java 四舍五入_《JAVA编程思想》5分钟速成:1-4章:概述

    前言: 1.面向对象的特征有哪些方面? 2.Math.round(11.5) 等于多少? Math.round(-11.5)等于多少? 3.float f=3.4;是否正确? 4.short s1 = ...

  4. java 析构函数_《JAVA编程思想》5分钟速成:第5章(初始化和清理)

    第五章.初始化和清理 前言 1.初始化顺序(静态成员.非静态成员,构造器.父类构造器)的排序: 2.构造器(constructor)是否可被重写(override)? 3.final, finally ...

  5. 【java】《java编程思想》 读书笔记

    之前主要用的C++的比较多,之前花了快2个月的实际认真系统全面的学习了以下java的基础语法,<java编程思想>这本书翻译水平确实不是很好,很多话读着会比较拗口.推荐读之前,先去网上搜索 ...

  6. java编程思想--概述

    之前主要用的C++的比较多,之前花了快2个月的实际认真系统全面的学习了以下java的基础语法,<java编程思想>这本书翻译水平确实不是很好,很多话读着会比较拗口.推荐读之前,先去网上搜索 ...

  7. Java编程思想第四版学习总结

    Java编程思想第四版学习总结 文章目录 Java编程思想第四版学习总结 第 1 章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 实现方案的隐藏 1.4 方案的重复使用 1.5 继承:重 ...

  8. Java中的泛型 --- Java 编程思想

    前言 ​ 我一直都认为泛型是程序语言设计中一个非常基础,重要的概念,Java 中的泛型到底是怎么样的,为什么会有泛型,泛型怎么发展出来的.通透理解泛型是学好基础里面中非常重要的.于是,我对<Ja ...

  9. 关于《Java编程思想》的简单纠正

    今天在看<Java编程思想>(我买的第四版,中文版)这本书的时候,在书第93页部分开头,有这么一段描述: "5.6.1    指定初始化 如果想为某个变量赋初值,该怎么做呢?有一 ...

  10. Java编程思想第四版——第十五天

    2012-04-23 121-131/913 Java编程思想第四版--第十五天 5.5.3 终结条件 通常不能指望finalize(),必须创建其他的"清理"方法,并明确的调用它 ...

最新文章

  1. 【leetcode】二叉树与经典问题
  2. 连接函数vc++笔记---CDatabase类
  3. (0004) H5开发之导入JQuery库以及使用。
  4. Web三大组件的注册——Servlet、Filter、Listener(监听三大作用域: ServletContext、HttpSession、ServletRequest )
  5. SecureCRT录制的安卓电视切换台脚本
  6. 【pyqt5学习】——最新版:配置external tools(designer、pyuic、pqrcc)
  7. python如何运行一个python程序_在python中,如何运行一个命令行程序,它在发送Ctrl+D之前不会返回...
  8. Linux常用命令4(grep、df、du、awk、su、ll)
  9. 关于对DataTable进行操作的几个例子总结
  10. 游戏开发之C++指针的妙用(C++)
  11. SQL SERVER 卸载清除步骤
  12. 计算机ascii码表
  13. 自动控制系统中的典型环节
  14. 七牛云视频转码 php,学习猿地-我的扩展包分享 - 七牛云视频转码
  15. 组合数学 排列 容斥 卡特兰数
  16. 深度学习--综述前言
  17. 组合博弈游戏 - SG函数和SG定理
  18. css去除图片或元素的背景颜色【透明】
  19. 个性化习题推荐-Exercise recommendation based on knowledge concept prediction
  20. warning no match for this type name:xxx.xxx.xxx [Xlint:invalidAbsoluteTypeName]

热门文章

  1. filter动态参数 maven_多环境下Maven项目的管理
  2. join为什么每个字符都分割了 js_js如何截取以逗号隔开的字符串
  3. 多元统计分析最短距离法_多元统计分析习题及解答.doc
  4. linux拨号日志,Linux系统日志管理:(1)连接时间日志
  5. python输入长和宽计算面_python案例1
  6. opencv获取人脸眼镜位置_用opencv检测人眼并定位瞳孔位置
  7. rust大油井频率怎么用_90%的人都不会用电吹风!用不好危害大!1分钟告诉你到底怎么用...
  8. Node.js:express设置全局变量
  9. python_numpy_矩阵乘法multiply()、dot()、 matmul()、 * 、 @ 辨析
  10. VS2013中为C++程序生成lib和dll文件