快速排序
(一) 简介 :
快速排序是由Hoare提出的,采用了分治法(Divide-and-ConquerMethod)。
分治法一般分成三个组成部分:分解,解决,合并。
快速排序不需要合并,只有分解 ,以及解决(递归)两个步骤。
(二) 基本思想 :(挖坑填数+分治法)
1.先从数列中取出一个数作为基准数。
2.将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边,得到基准数的 下标 I 。
3.再对左右区间(左区间[left , i-1], 右区间[ i+1, right ] )重复第二步,直到各区间只有一个数。
(三) 步骤:
(挖坑填数+分治法):
(1)挖坑填数图文解析 :
以一个数组作为示例,取区间第一个数为基准数。
0 1 2 3 4 5 6 7 8 9
72 6 57 88 60 42 83 73 48 85
初始时,i = 0; j = 9; X = a[i] = 72,基准数为左边元素,双向扫描。
由于已经将a[0]中的数保存到X中,可以理解成在数组a[0]上挖了个坑,可以将其它数据填充到这来。
从j开始向前找一个比X小或等于X的数。当j=8,符合条件,将a[8]挖出再填到上一个坑a[0]中。a[0]=a[8]; ; 这样一个坑a[0]就被搞定了,但又形成了一个新坑a[8],这怎么办了?简单,再找数字来填a[8]这个坑。这次从i开始向后找一个大于X的数,当i=3,符合条件,将a[3]挖出再填到上一个坑中a[8]=a[3];
数组变为:
0 1 2 3 4 5 6 7 8 9
48 6 57 88 60 42 83 73 88 85
i = 3; j = 7; X=72
再重复上面的步骤,先从后向前找,再从前向后找。
从j开始向前找,当j=5,符合条件,将a[5]挖出填到上一个坑中,a[3] = a[5]; i++;
从i开始向后找,当i=5时,由于i==j退出。
此时,i = j = 5,而a[5]刚好又是上次挖的坑,因此将X填入a[5]。
数组变为:
0 1 2 3 4 5 6 7 8 9
48 6 57 42 60 72 83 73 88 85
可以看出a[5]前面的数字都小于它,a[5]后面的数字都大于它。因此再对a[0…4]和a[6…9]这二个子区间重复上述步骤就可以了。
(2) 挖坑填数总结 :
1.i =L; j = R; 将基准数挖出形成第一个坑a[i] , 即将a[i] 的值保存到temp 中。
(注意保存的是基准值,而并不是基准值的下标。)
2.由后向前找比基准数小的数,找到后挖出此数填前一个坑a[ i ]中,即a[ j] 的值赋给a[ i ]
3.由前向后找大于等于基准数的数,找到后也挖出此数填到前一个坑a[j]中,即将a[ i] 的值赋值给 a[ j ]。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i] 这个坑中。
5. 结果为返回基准数的下标 i .
(刚开始时是将array[left]赋值给temp ,所以要从右边找数填坑。
即左边挖坑,右边找数填坑;
右边挖坑,左边找数 填坑。)
(3) 实现代码 :
再写分治法的代码:
(四) 快速排序扩展 :
(1) 算法描述 :
(2) 时间复杂度分析 :
(3) 具体实现细节 :
3.1)划分 :
3.1.1 选取基准数(枢纽点)
1. 左边元素
2. 随机元素
3. 三数取中
3.1.2 分割
1. 单向扫描
2. 双向扫描
3. Hoare的双向扫描
4. 改进的双向扫描
5. 双向扫描的其他问题
3.2)分治 :
3.2 尾递归
(4) 时间复杂度分析:
快速排序最佳运行时间O(nlogn),最坏运行时间O(N^2)。
两点很重要:
1. 选取枢纽元的不同, 决定了快排算法时间复杂度的数量级;
2. 划分方法的划分方法总是O(n), 所以其具体实现的不同只影响算法时间复杂度的系数。
(5) 选取基准值 :select_pivot( ElementType A[], int left, int right)(枢纽元)
1. 左边元素
2. 随机元素
3. 三数取中
1) 左边元素:
对于完全随机的数据,枢纽元的选取不是很重要,往往直接取左端的元素作为枢纽元。
问题:但是实际应用中,数据往往是部分有序的,如果仍用两端的元素最为枢纽元,则会产生很不好的划分,使算法退化成O(n^2)。
所以要采用一些手段避免这种情况,我知道的有“随机选取法”和“三数取中法”。
2)随机元素 :
分析:此中获取的基准值始终是A[ left ] ,这样 选择不同 基准值,后面的代码不用改变 。
(3) 三数取中 :
(5) 改进快速排序 :
按照上面的方法,递归会持续到分区只有一个元素。而事实上,当分割到一定大小后,继续分割的效率比插入排序要差。
所以 改进为 :
分析: 本来快速排序的递归条件是if (left < right) ,
此中将其改为 if( right-left>cutoff) ,说明在前面一部分是用快速排序,
到后面一部分直接是用插入排序 来处理更加小的数组。
(6) 总结 :
1. 快速排序 (分治法中的划分,解决):
1. 选取基准值(左边元素,随机元素,三数取中)
2. 划分函数 partition 之 挖坑填数,返回枢纽点下标(中心点下标)。
3. 递归