一、详解经典排序算法
01、为什么要学习数据结构和算法
-
数据结构和算法几乎存在于程序开发中的所有地方
-
Go的排序算法有提升空间
02、经典排序算法
-
Insertion Sort 插入排序
-
原理:
1、假设待排序的数据存储在数组中,将数组的第一个元素视为已排序部分,将剩余的元素视为未排序部分。
2、从未排序部分选择第一个元素,将其插入到已排序部分的适当位置。如果已排序部分中的元素比待插入元素大,则将已排序部分的元素向右移动一个位置,为待插入元素腾出空间。重复这个过程,直到找到待插入元素的正确位置。
3、将未排序部分的第一个元素取出,重复步骤2,将它插入到已排序部分的适当位置。
4、重复步骤3,直到未排序部分中的所有元素都被插入到已排序部分,完成排序。
-
最好时的时间复杂度为O(n),最坏和平均时间复杂度为O(n^2)
-
-
Quick Sort 快速排序
-
原理
- 选择一个基准元素(Pivot):从待排序序列中选择一个元素作为基准元素,可以选择第一个元素、最后一个元素或随机选择一个元素作为基准。
- 分割操作:通过一趟扫描,将待排序序列分成两个部分,左边部分的元素小于等于基准元素,右边部分的元素大于基准元素。可以使用两个指针(low和high)分别指向待排序序列的起始和结束位置。
- 从右向左扫描,找到第一个小于等于基准元素的元素,将其放到基准元素的左边(low位置)。
- 从左向右扫描,找到第一个大于基准元素的元素,将其放到基准元素的右边(high位置)。
- 重复上述两个步骤,直到low和high相遇。
- 递归排序:对基准元素左边的子序列和右边的子序列进行递归排序。递归的终止条件是子序列的长度为1或0,因为一个元素或空序列都可以看作是已排序的。
- 合并结果:将左边子序列、基准元素和右边子序列合并起来,得到最终的有序序列。
-
最好和平均时间复杂度O(n*logn),最坏时间复杂度为O(n^2)
-
-
Heap Sort 堆排序
- 原理
- 建立最大堆(或最小堆):将待排序的序列构建成一个最大堆。最大堆是指每个节点的值都大于或等于其子节点的值。建立最大堆的过程可以通过从最后一个非叶子节点开始,从右至左进行下沉操作(sink)来实现。下沉操作是指将一个节点与其子节点中较大的节点进行交换,并继续向下比较和交换,直到节点满足堆的性质或到达叶子节点。
- 排序:将最大堆中的根节点(最大值)与最后一个节点交换,然后对剩余的节点进行下沉操作,重新构建最大堆。重复这个过程,每次都将最大值交换到待排序序列的末尾,并缩小待排序序列的范围,直到所有元素都被排序完成。
- 最好、平均、最坏时间复杂度均为O(n*logn)
- 原理
-
结论:
- 所有短序列和元素有序情况下,插入排序性能好
- 在大部分的情况下,快速排序有较好的综合性能
- 几乎在任情况下,堆排序的表现都比较稳定
03、 从零开始打造 pdqsort
pdqsort(pattern-defeating-quicksort),是一种不稳定的混合排序算法,它对常见的序列类型做了特殊的优化,使得在不同条件下都拥有不错的性能
version1
结合三种排序方法的优点
-
对于短序列(小于一定长度) 我们使用插入排序
-
其他情况,使用快速排序来保证整体性能
-
当快速排序表现不佳时,使用堆排序来保证最坏情况下的时间复杂度仍为O(n*logn)
-
Q&A
-
短序列的具体长度为多少呢?
12~32,泛型版本选24
-
如何知道快排表现不佳?
当最终Pivot的位置离序列两端最接近时(小于length/8)为不佳
-
version2
- 升级pivot选择策略(近似中位数)