你所忽视的排序算法(下)
你所不知道的排序算法下篇更新啦,相比于上篇介绍的,下篇的几种排序算法相对来说比较少见。最近也花了一点时间来理解。话不多说,我们进入正文。
今天出场的是:快速排序,堆排序,计数排序,桶排序。
快速排序
快速排序(Quicksort),又称划分交换排序(partition-exchange sort),简称快排。最早是由东尼·霍尔提出来。快速排序的基本思想是使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。其中一个序列关键字均比另一个序列关键字小,则可分别对这两个子序列继续进行排序,已达到整个序列有序。
逻辑描述
- 从数列中挑出一个元素,称为“基准”(pivot)
- 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分割结束之后,该基准就处于数列的中间位置。这个称为分割(partition)操作。
- 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序
递归到最底部时,数列的大小是零或一,也就是已经排序好了。这个算法一定会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。
代码实现
1 | public static void quickSort(int[] arr, int head, int tail) { |
算法分析
快速排序是二叉查找树(二叉搜索树)的一个空间最优化版本。不是循序地把数据项插入到一个明确的树中,而是由快速排序组织这些数据项到一个由递归调用所隐含的树中。快速排序的最直接竞争者是堆排序(Heapsort)。堆排序比快速排序稍微慢,但是最坏情况的运行时间总是
堆排序
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆是一个近似完全二叉树的结构,并同时满足堆积的性质:即子节点的键值或索引总是小于(或者大于)它的父节点。
这里多一句嘴来帮助理解一下堆有关的操作。
堆节点的访问:通常堆是通过一维数组来实现的,在数组起始位置为0的情况下:
- 父节点i的左子节点在位置2i+1;
- 父节点i的右子节点在位置2i+2;
- 子节点i的父节点在位置floor((i-1)/2);
堆的操作:在堆的数据结构中,堆中的最大值总是位于根节点(在优先队列中使用堆的话,队中的最小值位于根节点)。堆中定义一下几种操作:
- 最大堆调整(Max Heapify):将堆的末端子节点作调整,使得子节点永远小于父节点
- 创建最大堆(Build Max Heap):将堆中的所有数据重新排序
- 堆排序(Heap Sort):移除位在第一个数据的根节点,并做最大堆调整的递归运算
逻辑描述
- 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区
- 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=r[n]
- 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成
代码实现
1 | package org.pross.sort; |
计数排序
计数排序(CountingSort)是一种稳定的线性时间排序算法。其核心在于将输入的数据值转化为键存储在额外开辟的数组空间中,作为一种线性时间复杂度的排序,计数排序要求输入的数据必须是有确定范围的整数。
逻辑描述
- 找出待排序的数组中最大和最小的元素
- 统计数组中每个值为i的元素出现的次数,存入数组C的第i项
- 对所有的计数累加(从数组C中的第一个元素开始,每一项和前一项相加)
- 反向填充目标数组:将每个元素i在新数组的第C(i)项,每放一个元素就将C(i)减去1
代码实现
1 | package org.pross.sort; |
算法分析
计数排序是一个稳定的排序算法。当输入的元素是n个0到k之间的整数时,时间复杂度是O(n+k),空间复杂度也是O(n+k),其排序速度快于任何比较排序算法。
桶排序
桶排序(Bucket sort)或所谓的箱排序,是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。桶排序的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。
逻辑描述
- 设置一个定量的数组当作空桶
- 遍历输入数据,并且把数据一个一个放到对应的桶里去
- 对每个不是空的桶进行排序
- 从不是空的桶里把排好序的数据拼接起来。
代码实现
1 | package org.pross.sort; |
算法分析
桶排序最好情况下使用线性时间O(n),桶排序的时间复杂度,取决与对各个桶之间数据进行排序的时间复杂度,因为其它部分的时间复杂度都为O(n)。很显然,桶划分的越小,各个桶之间的数据越少,排序所用的时间也会越少。但相应的空间消耗就会增大。