[ 排序算法实践 | 青训营笔记 ]

113 阅读4分钟

一、详解经典排序算法

01、为什么要学习数据结构和算法

  • 数据结构和算法几乎存在于程序开发中的所有地方

  • Go的排序算法有提升空间

02、经典排序算法

  • Insertion Sort 插入排序

    • 原理:

      1、假设待排序的数据存储在数组中,将数组的第一个元素视为已排序部分,将剩余的元素视为未排序部分。

      2、从未排序部分选择第一个元素,将其插入到已排序部分的适当位置。如果已排序部分中的元素比待插入元素大,则将已排序部分的元素向右移动一个位置,为待插入元素腾出空间。重复这个过程,直到找到待插入元素的正确位置。

      3、将未排序部分的第一个元素取出,重复步骤2,将它插入到已排序部分的适当位置。

      4、重复步骤3,直到未排序部分中的所有元素都被插入到已排序部分,完成排序。

    • 最好时的时间复杂度为O(n),最坏和平均时间复杂度为O(n^2)

  • Quick Sort 快速排序

    • 原理

      1. 选择一个基准元素(Pivot):从待排序序列中选择一个元素作为基准元素,可以选择第一个元素、最后一个元素或随机选择一个元素作为基准。
      2. 分割操作:通过一趟扫描,将待排序序列分成两个部分,左边部分的元素小于等于基准元素,右边部分的元素大于基准元素。可以使用两个指针(low和high)分别指向待排序序列的起始和结束位置。
        • 从右向左扫描,找到第一个小于等于基准元素的元素,将其放到基准元素的左边(low位置)。
        • 从左向右扫描,找到第一个大于基准元素的元素,将其放到基准元素的右边(high位置)。
        • 重复上述两个步骤,直到low和high相遇。
      3. 递归排序:对基准元素左边的子序列和右边的子序列进行递归排序。递归的终止条件是子序列的长度为1或0,因为一个元素或空序列都可以看作是已排序的。
      4. 合并结果:将左边子序列、基准元素和右边子序列合并起来,得到最终的有序序列。
    • 最好和平均时间复杂度O(n*logn),最坏时间复杂度为O(n^2)

  • Heap Sort 堆排序

    • 原理
      1. 建立最大堆(或最小堆):将待排序的序列构建成一个最大堆。最大堆是指每个节点的值都大于或等于其子节点的值。建立最大堆的过程可以通过从最后一个非叶子节点开始,从右至左进行下沉操作(sink)来实现。下沉操作是指将一个节点与其子节点中较大的节点进行交换,并继续向下比较和交换,直到节点满足堆的性质或到达叶子节点。
      2. 排序:将最大堆中的根节点(最大值)与最后一个节点交换,然后对剩余的节点进行下沉操作,重新构建最大堆。重复这个过程,每次都将最大值交换到待排序序列的末尾,并缩小待排序序列的范围,直到所有元素都被排序完成。
    • 最好、平均、最坏时间复杂度均为O(n*logn)
  • 结论:

    • 所有短序列和元素有序情况下,插入排序性能好
    • 在大部分的情况下,快速排序有较好的综合性能
    • 几乎在任情况下,堆排序的表现都比较稳定

03、 从零开始打造 pdqsort

pdqsort(pattern-defeating-quicksort),是一种不稳定的混合排序算法,它对常见的序列类型做了特殊的优化,使得在不同条件下都拥有不错的性能

version1

结合三种排序方法的优点

  • 对于短序列(小于一定长度) 我们使用插入排序

  • 其他情况,使用快速排序来保证整体性能

  • 当快速排序表现不佳时,使用堆排序来保证最坏情况下的时间复杂度仍为O(n*logn)

  • Q&A

    • 短序列的具体长度为多少呢?

      12~32,泛型版本选24

    • 如何知道快排表现不佳?

      当最终Pivot的位置离序列两端最接近时(小于length/8)为不佳

23.png

version2

  • 升级pivot选择策略(近似中位数)

24.png

final version

25.png