不准访问
- 积分
- 12296
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2012-2-17
- 最后登录
- 1970-1-1
|
注册一亩三分地论坛,查看更多干货!
您需要 登录 才可以下载或查看附件。没有帐号?注册账号 
x
本帖最后由 北美农民 于 2012-12-4 00:25 编辑
惰性又发作, 有点不想写下去了, 坚持一步算一步吧。。终于进入了n*log n的时代, 到了快速排序, 虽然可以不理n^2了, 但是思想千万别忘。
注:若无特殊说明, 排序以递增为目的。
快速排序(Quick Sort)是由C.R.A Hoare在60年代提出的。 其策略是 交换划分与分治法。 尤其是后者---分治法是很常用很重要的算法策略, 许多公司面试经常考察。 快排的重要性, 我觉得要了解其原理,并且3分钟内写出快排的核心代码部分是有必要的, 2分钟内算基本合格。
我们假设n个元素的数组a[0..n-1],快速排序算法每个步骤和策略是:
1:选择一个基准元素a[k]
2:让大于等于a[k]的数组元素在其右边,小于a[k]的在其左边
3:对a[k]左右边区间的元素重复1,2步,直到左右区间小于等于一个1。
步骤3是快速排序分治策略的体现,然而, 单独的分治法又不能诠释步骤2. 步骤2的实现是最难的一步。于是, 我将步骤2称作交换划分。那么,分治可以通过递归实现, 那么该如何实现交换划分? 笔者这里介绍一个自己摸索出来的实现方式。 欢迎大家给出更简洁高校的代码, the easier, the better!
交换划分的算法方法如下:
我们选定基数int standard=a[start]作为划分基数, 然后 用两个指针, p=start,区间起始位置 ,q=end, 区间结束位置。
若p<q 则做如下两步:
一:指针q从右向左扫描,直至扫描到小于standard的元素。
赋值给a[p],p++。
二:指针p从左向右扫描, 直至扫描到大于或等于standard的元素。(为什么是大于或等于? 请看算法步骤2第一句话).
赋值给a[q],q--。
做完以上两步后,判断p是否等于q, 若p<q, 则继续重复”一"和“二”。p与q终有相遇之时, 即最后将使得p=q,则停止。
千万不要忘记了一件事, 那就是,standard要放回数组, 即a[p](或a[q])=standard, 这样a[p](或a[q])左边都是小于standard的元素, 右边则是大于等于standard的元素
以上,通过交换划分, 我们成功的将元素隔离开来, 接下来是分治。 即用同样的方法处理a[start..p-1]和a[p+1..end], 递归即可。
举例说明交换划分的过程, 设数组a[0..7]一共有8个元素,
第一次交换划分过程如下。
初始化,p=0, q=7, standard=a[p]=27
q=7, 往左边扫描, 发现接下来小于standard的值是a[q]=19, 此时q=6。 将a[q]赋值给a[p],p++,得到数组:
| 0 | p=1 | 2 | 3 | 4 | 5 | q=6 | 7 | | 19 | 13 | 46 | 25 | 88 | 72 | 19 | 56 |
p=1, 继续往右边扫描, 接下来大于等于standard的值是a[p]=46, 此时p=2。将a[p]赋值给a[q],q--, 得到数组:
| 0 | 1 | p=2 | 3 | 4 | q=5 | 6 | 7 | | 19 | 13 | 46 | 25 | 88 | 72 | 46 | 56
|
步骤“一”和“二”走了一遍, 此时p=2小于q=5,两者并未相遇 重复。
q=5, 往左边扫描, 发现接下来小于standard的值是a[q]=25, 此时q=3。 将a[q]赋值给a[p],p++,得到数组:
| 0 | 1 | 2 | p=q=3 | 4 | 5 | 6 | 7 | | 19 | 13 | 25 | 25 | 88 | 72 | 46 | 56
|
突然发现此时p=q, 已经相遇, 于是将standard放回, a[p]=27, 最终得到如下数组:
| 0 | 1 | 2 | p=q=3 | 4 | 5 | 6 | 7 | | 19 | 13 | 25 | 27 (standard放回) | 88 | 72 | 46 | 56
|
此时左边都是小于standard的元素, 右边反之。 交换划分完成。
紧接着只需要如法炮制(a[0]..a[2])以及(a[4]..a[7]), 递归实现即可。
实现代码如下:
void Qsort(int start, int end) {
if (start<end) //想想为什么要判断
{
int standard,p,q;
// swap(a[p], a[(start+end)/2]); 取中间位置的值为基准数, 用不用也不影响正确性。
p=start; q=end; standard=a[p];
while (p<q)
{
//步骤1
while (a[q]>=standard && p<q) q--;
if (p<q)
a[p++]=a[q];
//步骤2
while (a[p]<standard && p<q) p++;
if (p<q)
a[q--]=a[p];
}
a[p]=standard; //把standard放回数组, 划分交换完毕
//分治
Qsort(start,p-1);
Qsort(p+1,end);
}
}
红色部分说明: 笔者描述的快速排序算法, 初始化时选择的基准值standard为a[p], 即该段数组区间的第一个值。 考虑最坏情况,若数组本身有大段的有序, 逆序特征, 划分交换步骤的复杂度将较高, 递归的深度将出现极端情况(仔细思考交换划分和分治的过程, 为什么?)。 因此, 我们可以
1:选取中间的元素作为基准值,standard=a[(start+end)/2]
2:随机选择一个元素做基准值, standard=a[random(start,end)]。
这样将能改善这种情况。
总结:快排的思想, 很多人都知道, 其核心在于实现划分交换。 而根据笔者经验来看,这是最容易出错的地方,徐多人自主实现的时候会出纰漏, 要么就是判断条件出错, 要么就是忘了步骤,大家一定在理解原理的基础上写代码。 同时,该部分的实现方式也很多种多样, 欢迎高人提出更精简, 更方便于理解的形式。 最后,笔者向各位提一个要求,面试的时候,无论怎么写,该排序能在2分钟之内流畅地写完。
|
上一篇: 【七类排序】之第三种:选择排序下一篇: 关于recursive求fibonacci求助
|