您的位置:首页 > 其它

从快速排序开始

2017-11-13 22:11 701 查看

起步

打算重新学习下算法记录到博客,只为整理下自己的思路、备忘,所以内容难免存在很多主观看法和不严谨的地方。

如果有幸被检索到,内容好坏还请大家自己甄别,有问题的地方,也可以能够提出来大家共同学习。


快速排序

从快速排序和归并排序开始,因为对于算法学习来讲,这两个排序是理解递归的一个很好的切入点。

概要

快排思想:就是把数组首位数字v放入数组有序时它该处的位置,同时使其左右数据满足如下描述性质:

[|v|..................] ==> [....<v....|v|....>v.....]
这个步骤称之为partition.

可以发现经过一次partition,v的序列位定下来了,同时其他的数据虽然没有排好序,但是至少处在它该处的区间。然后对 <v 和 >v 左右区间再次进行同样的操作(递归),可以预见区间被划分得越来越多也越来越小,数据被分割得越来越接近排序的位置。最后完全停留在它的序列位上,此时整个数组有序。理解了大体流程以,就可以写出快排的伪代码:

int _partition(int a[], int lo, int hi);
void _quicksort(int a[], int lo, int hi){
if (lo >= hi)
return;
int j = _partition(a, lo, hi);
_quicksort(a, lo, j - 1);
_quicksort(a, j + 1, hi);
}

类似于二分法,此数组理想情况下被分割的次数是logN,每次分割后,遍历数据量为N,时间复杂度NlogN。

partition分割函数

快排的核心就是partition + 递归。

partition的过程

先入为主,partition流程,数组将一直处于以下状态:

[(v, lo) | .....<v..... | i......j | .....>v.....hi]
毕竟要用代码来实现算法,所以这里必须加入控制流的两个参数i、j,以下明确参数含义:

i:正在向右遍历的指针;

j:正在向左遍历的指针;

遇到i > v、j < v的时候,用swap(a[i],
a[j])来使数组满足以上规律,之后继续遍历;

所以当刚开始遍历i、j时,这两个参数应该满足:[lo+1, i) < v、(j,
hi] > v;

这两个区间在初始的时候长度都必须为0,由此也可以确定出i、j的初始值应该是i == lo+1,j==hi

partition的终点

[(v, lo) | .....<v..... j |i .....>v.....hi]

正是由于i、j两个指针不断的遍历和交换,将数组规划为左右两段,j刚好是最后一个<v的数字,当然要与lo进行交换,交换后就是:

[j | .....<v..... v|i .....>v.....hi]
其实就是之前概要提到的partition的最终执行结果:

[....<v....|v|....>v.....]

partition的起点

最后忘记一个关键点,partition中i、j的初始值如何确定?(上面好像已经提及过),参考如下的数组

[(v, lo,i) | .............hi] j

这里i==lo,j==hi+1,为什么给这样的初值(不应该是i == lo+1,j==hi吗)其实这里partition循环代码中使用的是前置+,所以还未进入循环的时候,应该把初始值设置为初始位置的上一个位置。当然i
== lo+1,j==hi的初始值也可以,不过partition循环代码中的前置+就要改为后置+,个人觉得在逻辑上会更繁杂一些,不容易调通。

代码

int _partition(int a[], int lo, int hi){
int i = lo;				//各参数初始值
int j = hi + 1;
int v = a[lo];
while(true){
while(a[++i] < v){
if (i >= hi) break;	//退出条件
}
while(a[--j] > v){
if (j <= lo) break;	//退出条件

}
if (i >= j)			// ">=" 还是 ">" ?
break;
swap(a[i], a[j]);
}
swap(a[lo], a[j]);
return j;
}
void quicksort(int a[], int n){
_quicksort(a, 0, n - 1);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: