算法学习笔记 快速排序
Posted HarmoniaLeo
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法学习笔记 快速排序相关的知识,希望对你有一定的参考价值。
1.快速排序的由来
快速排序是一种二分的排序算法,这种算法的诞生来自于对有序数组的观察。我们假设有以下数组:
1,2,3,4,5,6,7,8,9
这是一个已经按照从小到大排序完毕的有序数组。观察以上数组,取其中间数5,我们可以发现5以左的数1,2,3,4均比5小,5以右的数6,7,8,9均比5大。我们以5为分界线将数组分为两部分:
1,2,3,4
6,7,8,9
分别取中间数3和8,我们可以得到同样的结论:左边的数总小于中间数,右边的数总大于中间数。由此可见,一个有序数列的每一个二分子数列总满足中间数以左的数小于中间数,中间数以右的数大于中间数。
快速排序的基本思想就是:
使完整数组满足A段所有数小于B段所有数,但A段与B段内部不一定有序。
A | B |
---|---|
3,2,1,4 | 7,9,6,8 |
使A段内A1段所有数小于A2段所有数,B段内B1段所有数小于B2段所有数,但每一小段内不一定有序。
A1 | A2 | B1 | B2 |
---|---|---|---|
1,2 | 3,4 | 7,6 | 9,8 |
以此类推二分数组,直到每个数组内都只有一个元素时,整个数组便变得有序了。
可见快速排序是一个典型的二分算法,且可以使用递归的方式实现。
2.快速排序的基本方法
我们以刚才举例的数组的逆序列为例:
9,8,7,6,5,4,3,2,1
假如我们采用单向迭代的方法来完成快速排序,取中间数为9,之后扫描后面8个数字。由于8个数字均小于9,我们只需要将9插到队伍的最后。
8,7,6,5,4,3,2,1,9
再取中间数为8,扫描后面8个数字,发现只需要把8插到9的前面就行。以此类推,在数据规模为n的情况下,需要n-1次插入。
但是,这是在我们事先已经知道有成块的子串中所有的数均小于中间数的情况下得出的结论。实际情况下,在我们考虑单独的数时,这个数小于中间数并不意味着下一个数小于中间数。所以我们每次选取中间数后需要维护两个序列,一个储存比中间数小的数,一个储存比中间数大的数。每次扫描数,都要与中间数比较后,加入两个序列中的一个当中。每一次选取中间数,复制数都需要n-1次操作,总共选取n个中间数的话,这趟快速排序的时间复杂度就是。因此,我们必须保证所有数据位置的变换都在原数组内部进行。
我们又知道,假如有一排积木紧密排在一起,要将其中的一块移到另一块积木处,就必须得把原来在该位置的积木拿开。
令积木大小不同,如果我们要实现积木从左到右由小到大的排序,可以想象我们一开始将最左边积木拿开,再从右往左找,如果见到了比拿开的积木更小的积木,就拿到最左边的空位。这时右边就空出来了一个积木的空位,我们再从左往右找,找到一块比拿开的积木大的积木,放到右边的空位……
直到空位出现在队伍中间时,左边的积木,尽管还不是从小到大排列的,但至少都拿开的积木,也小于右边的积木。我们再把拿开的积木放回空位,就完成了第一次排序。接着再对右边的积木和左边的积木重复同样的操作,最终整个数组就会变成有序的。
拿开的积木就是我们的基准数,积木的大小就是数组中数字的大小,上面积木排序的过程其实就是数组快速排序的过程。这样的操作时间复杂度是多少呢?由于我们二分地选择基准数,基准数的选择是logn次,考虑最坏的情况,每一段数列中除了基准数外的数中,有一半的数需要移动,对于完整数列即(n-1)/2,随着数列二分,数据规模以公比1/2缩小,根据等比数列求和公式,得:
时,单边总共移动次数为n-1,加上我们需要选择次基准数,总的时间复杂度就是。
3.快速排序的实现
static void quickSort(int s, int e)
{
if (s >= e)
return;
int mid = numbers[s],start = s,end=e;
while (s < e)
{
while (s < e)
{
if (numbers[e] > mid)
{
numbers[s++] = numbers[e];
break;
}
e--;
}
while (s < e)
{
if (numbers[s] < mid)
{
numbers[e--] = numbers[s];
break;
}
s++;
}
}
numbers[s] = mid;
quickSort(start, s - 1);
quickSort(s + 1, end);
}
以上是关于算法学习笔记 快速排序的主要内容,如果未能解决你的问题,请参考以下文章
python 学习笔记 -- 数据结构与算法 快速排序 Quick Sort