一、数据结构前置知识
时间复杂度和空间复杂度
时间复杂度和空间复杂度一般是针对算法而言,是衡量一个算法是否高效的重要标准。先纠正一个误区,时间复杂度并不是算法执行的时间,再纠正一个误区,算法不单单指冒泡排序之类的,一个循环甚至是一个判断都可以称之为算法。其实理解起来并不冲突,八大排序甚至更多的算法本质上也是通过各种循环判断来实现的。
时间复杂度: 指算法语句的执行次数。O(1),O(n),O(logn),O(n2)
空间复杂度: 就是一个算法在运行过程中临时占用的存储空间大小,换句话说就是被创建次数最多的变量,它被创建了多少次,那么这个算法的空间复杂度就是多少。有个规律,如果算法语句中就有创建对象,那么这个算法的时间复杂度和空间复杂度一般一致,很好理解,算法语句被执行了多少次就创建了多少对象。
二、经典排序算法
冒泡排序(Bubble Sort)
算法描述:
- 比较相邻的元素。如果第一个比第二个大,就交换它们两个;
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
- 针对所有的元素重复以上的步骤,除了最后一个;
- 重复步骤1~3,直到排序完成。
如果两个元素相等,不会再交换位置,所以冒泡排序是一种稳定排序算法。
快速排序(Quick Sort)
算法描述:
使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
- 从数列中挑出一个元素,称为 “基准”(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
key值的选取可以有多种形式,例如中间数或者随机数,分别会对算法的复杂度产生不同的影响。
归并排序(Merge Sort)
算法描述:
- 把长度为n的输入序列分成两个长度为n/2的子序列;
- 对这两个子序列分别采用归并排序;
- 将两个排序好的子序列合并成一个最终的排序序列。
三、pdqsort
简介
pdqsort(pattern-defeating-quicksort)
是一种不稳定的混合排序算法,它的不同版本被应用在C++ BOOST、Rust以及Go1.19中。它对常见的序列类型做了特殊的优化,使得在不同条件下都拥有不错的性能。
顾名思义,它结合了三种排序方法的优点:
- 对于短序列(小于一定长度)我们使用插入排序
- 其他情况,使用快速排序保证整体性能
- 当快速排序表现不佳时,使用堆排序保证在最坏情况下时间复杂度仍然为O(n*logn)
最终版本
时间复杂度对比
四、总结
- 理论算法注重理论性能,例如时间、空间复杂度等。生产环境中的算法需要面对不用的实践场景,更加注重实践性能
- Go语言的排序算法实际一直是混合排序算法,主体是快速排序。Go<1.18时的算法也是基于快速排序。和pdqsort的区别在于fallback时机、pivot选择策略、是否有针对pattern优化等。
- 在设计高性能的排序算法时,要根据不同情况选择不同策略,取长补短,也就是具体问题具体分析。