从快速排序开始

来源:互联网 发布:电话号码扫描录入软件 编辑:程序博客网 时间:2024/05/22 10:39

起步

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

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


快速排序

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


概要

快排思想:就是把数组首位数字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);}