为什么算法容易忘记之快速排序

本文用来帮助大家理解记忆快速排序,方法和上篇文章一样,着重理解算法基本思想及其代码中的循环控制变量的意义。

基本思想

快速排序属于拿着元素找位置的算法。思路非常简单明了,首先给第一个元素找到它正确的位置并把它放置其中,此时该元素将原数组分为两半,左半边的元素都小于或等于它,右半边的元素都大于它,对这两个子数组重复刚才的操作,直到子数组中只有一个元素,此时排序完成。

由思想到代码

首先,我们用一个forInsert变量存储数组第一个位置上的元素的值。可以通俗理解为我们将第一个位置上的元素“挖”了出来,以便为它找到合适的位置,第一个位置此时已经是“空”的,位置是空的这一概念很关键,后面会用到。

如何为该元素找到合适的位置呢?答案是先确定该元素所在位置的范围,不断缩小该范围,直到该范围是一个确定的位置,查找结束,把forInsert的值放到该位置上,再对该位置左右两个子数组进行迭代,直到子数组大小为1时结束,排序完成。为表示该元素所在位置的范围,我们需要定义两个变量left,right,代表元素所在位置的范围的左端和右端,显然left的初始值应为0,right的初始值应为N-1。

下面开始缩小这一范围,将right位置上的元素与forInsert进行比较,如果大于forInsert,那么可以断定right这个位置肯定不是forInsert应当在的位置,因为如果将forInsert放置在right位置上,该位置上原来的元素将无处安放。所以我们可以将right减1(right--),这就缩小了位置的范围,然后我们继续将新的right位置上的元素与forInsert比较,如果还是大于forInsert,则可以继续将right减1后继续比较,直到right位置上的值小于forInsert的值时,就是magic发生的地方。

由于right位置上的元素比forInsert小,我们无法判断该位置是否是forInsert应当在的位置,BTW,我们可以判定left这个位置肯定不是forInsert应当在的位置,为什么?请参照上文叙述自行理解。

然后呢?我们可以将right位置上的值放置到left位置上,让left加1(left++),这进一步缩小了位置的范围。

此时right位置我们认为是“空的”了,看到没,刚才left是空的,现在right是空的了。

下步的思路肯定还是想办法继续缩小位置的范围。我们可以将left位置上的元素与forInsert比较,如果小于forInsert的值,我们可以断定,left这个位置肯定不是forInsert应当在的位置,为啥?因为将forInsert放置到该位置上,该位置上的元素只能往左边挪,而左边每个位置上都是比forInsert小的元素导致“无处可挪”,矛盾出现,反证结束。然后我们又可以放心地将left加1了,位置范围又缩小了,哦耶!

我们继续将left位置上的元素与forInsert比较,直到发现left位置上的元素大于forInsert时,又要有magic发生了,我们将left位置上的元素放置到right位置上(还记得right位置此时是空的吗?),现在,left位置变成空的了,由于此时right位置上的元素是大于forInsert的,right位置肯定不是forInsert应当在的位置,所以我们要将right减1,进一步缩小待确定位置的范围。

好了,让我们停一停,看看现在是什么状况,显然left增加了一些值,并且left位置此时是空的,right减少了一些值,整体上[left , right]包含的范围比初始时小了好多。如果left=right,我们知道,要找的位置就是现在left所指示的空位置,直接将forInsert放置到left位置上即可。然后开始左右两个子数组的迭代,如果left还是小于right,那我们只能继续进行缩小位置范围的工作,直到确定位置为止。

这是代码

void quick_sort(int s[], int l, int r)
{
if (l < r)
{
int i = l, j = r, x = s[l];
while (i < j)
{
while(i < j && s[j] >= x)
j--;
if(i < j)      s[i++] = s[j];
while(i < j && s[i] < x)
i++;
if(i < j)    s[j--] = s[i];
}
s[i] = x;
quick_sort(s, l, i - 1); // 递归调用
quick_sort(s, i + 1, r);
}

原文发布于微信公众号 - 人工智能LeadAI(atleadai)

原文发表时间:2018-02-20

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏LIN_ZONE

es6语法需要注意的部分

由于无符号右移运算的结果是一个 32 位的正数,所以负数的无符号右移运算得到的总是一个非常大的数字。例如,如果把 -64 右移 5 位,将得到 13421772...

914
来自专栏Python研发

最全Python内置函数

判断真假,  True:真  ,  False:假,   把一个对象转换成bool值

2622
来自专栏Coding迪斯尼

自制Monkey语言编译器:解释执行if..else判断语句

1165
来自专栏小古哥的博客园

JavaScript 正则表达式入门教程

正则表达式是描述一组字符串特征的模式,用来匹配特定的字符串 主要分三个部分:基本语法、RegExp对象的方法、JS中支持正则表达式的String对象方法 一、基...

3413
来自专栏编程

Python面向对象1:基础介绍+封装特征

目前有三种编程方式: 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封...

2137
来自专栏Android干货园

Kotlin初级(3)- - - 基础函数.md

area函数在printArea外部无效,它只服务于printArea。这在实现一个大函数时隐藏实现的细节是非常有用的。除此之外,本地函数还有一个好处就是可以访...

983
来自专栏java初学

final关键字

39012
来自专栏土豆专栏

Java面试之数据类型(一)

封装类是引用类型,基本类型在传递参数的时候都是按值传递,而封装类型是按引用传递的(其实引用也是按值传递的,但是传递的是对象的地址)

1842
来自专栏我的博客

插入排序

原理: 1. 从第一个元素开始,该元素可以认为已经被排序 2. 取出下一个元素,在已经排序的元素序列中从后向前扫描 3. 如果该元素(已排序)大于新元素,...

2546
来自专栏抠抠空间

函数 (一) 基础

一、函数的作用 函数可以让我们代码结构更清晰,而且避免了代码的重复,冗余,使一段代码或者功能可以反复的被调用,大大提高了开发效率 二、函数的定义 def 函数名...

3006

扫码关注云+社区

领取腾讯云代金券