受到 从一道笔试题谈算法优化(上) 和 从一道笔试题谈算法优化(下) 这个系列文章的启发,并结合C++ STL
中partial_sort
的处理逻辑,本文用golang
实现了7种PartialSort
部分排序解决方案,对它们进行性能比较。
1. 定义和判断
部分排序: 排列指定集合data中的所有元素,使得前m(0<m<data.len)个最小元素从小到大的顺序放置在[0, m)中。
依照上面的定义,有如下方法来判断一个集合是否是部分有序的(源码见github):
// 判断data中的元素是否是关于m部分有序的
func IsPartialSorted(data sort.Interface, m int) bool {
// 1. 判断[0, m)中的元素是否全排序
mid := m-1
for i := mid; i > 0; i-- {
if data.Less(i, i-1) {
return false
}
}
// 2. 判断[m, data.Len)中的元素是否都不小于[0, m)中的元素
n := data.Len()
for i := m; i < n; i++ {
if data.Less(i, mid) {
return false
}
}
return true
}
2. 方案1:选择排序
依次从data中选出m个最小的元素(源码见github):
func solution1(data sort.Interface, m int) {
len := data.Len()
for i := 0; i < m; i++ {
idx := i
// 选出[i, data.Len)中最小的元素
for j := i+1; j < len; j++ {
if data.Less(j, idx) {
idx = j
}
}
data.Swap(i, idx)
}
}
3. 方案2:快速排序
直接使用golang
SDK
中的sort
方法进行全排序,然后选出前m个元素(源码见github):
func solution2(data sort.Interface, m int) {
sort.Sort(data)
}
4. 算法概述
当然,前面两种解决方法都是在打酱油。solution1的时间复杂度为O(n*m),solution2的时间复杂度为 O(Nlog(N));
后面几种解决方法都是思路都一致,概括如下:
- 将 data中的前面m个元素作为算法的初始数据;
- 找出这 m 个数的最大值 idx;
- 遍历所有剩下的元素 i ,如果 i 小于 idx,将 idx 替换为 i ,然后再找出新的 m 个数中的最大值 idx ;
- 排序最后得到的m个元素;
注: 本文所使用的源码见 github。
5. 方案3
func solution3(data sort.Interface, m int) {
//标记是否发生过交换
bExchanged := true
var idx int // 后续所有元素中最大元素的索引
//遍历后续的元素
len := data.Len()
for i := m; i < len; i++ {
//如果上一轮发生过交换
if bExchanged {
//找出ResArr中最大的元素
idx = 0
for j := idx + 1; j < m; j++ {
if data.Less(idx, j) {
idx = j
}
}
}
bExchanged = false
//这个后续元素比ResArr中最大的元素小,则替换
if data.Less(i, idx) {
bExchanged = true
data.Swap(i, idx)
}
}
introspectiveSort(data, 0, m)
}
6. 方案4
func solution4(data sort.Interface, m int) {
//排序
introspectiveSort(data, 0, m)
//最大元素索引
maxElemIdx := m - 1
//可能产生交换的区域的最小索引
zoneBeginIdx := maxElemIdx
//遍历后续的元素
len := data.Len()
for i := m; i < len; i++ {
//这个后续元素比ResArr中最大的元素小,则替换
if data.Less(i, maxElemIdx) {
data.Swap(maxElemIdx, i)
if zoneBeginIdx == maxElemIdx && zoneBeginIdx > 0 {
zoneBeginIdx--
}
//查找最大元素
idx := zoneBeginIdx
for j := idx + 1; j < m; j++ {
if data.Less(idx, j) {
idx = j
}
}
maxElemIdx = idx
}
}
introspectiveSort(data, 0, m)
}
7. 方案5
func solution5(data sort.Interface, m int) {
//排序
introspectiveSort(data, 0, m)
//最大元素索引
maxElemIdx := m - 1
//可能产生交换的区域的最小索引
zoneBeginIdx := maxElemIdx
//遍历后续的元素
len := data.Len()
for i := m; i < len; i++ {
//这个后续元素比ResArr中最大的元素小,则替换
if data.Less(i, maxElemIdx) {
data.Swap(maxElemIdx, i)
if zoneBeginIdx == maxElemIdx && zoneBeginIdx > 0 {
zoneBeginIdx--
}
//太多杂乱元素的时候排序
if zoneBeginIdx < SplitPoint {
introspectiveSort(data, 0, m)
maxElemIdx = m - 1
zoneBeginIdx = maxElemIdx
continue
}
//查找最大元素
idx := zoneBeginIdx
for j := idx + 1; j < m; j++ {
if data.Less(idx, j) {
idx = j
}
}
maxElemIdx = idx
}
}
introspectiveSort(data, 0, m)
}
const (
SplitPoint = 400
)
8. 方案6
func solution6(data sort.Interface, m int) {
//排序
introspectiveSort(data, 0, m)
//最大元素索引
maxElemIdx := m - 1
//可能产生交换的区域的最小索引
zoneBeginIdx := maxElemIdx
//遍历后续的元素
len := data.Len()
for i := m; i < len; i++ {
//这个后续元素比ResArr中最大的元素小,则替换
if data.Less(i, maxElemIdx) {
data.Swap(maxElemIdx, i)
if zoneBeginIdx == maxElemIdx && zoneBeginIdx > 0 {
zoneBeginIdx--
}
//太多杂乱元素的时候排序
if zoneBeginIdx < SplitPoint {
messyBeginIdx := zoneBeginIdx+1
introspectiveSort(data, messyBeginIdx, m)
symMerge(data,0, messyBeginIdx, m)
maxElemIdx = m - 1
zoneBeginIdx = maxElemIdx
continue
}
//查找最大元素
idx := zoneBeginIdx
for j := idx + 1; j < m; j++ {
if data.Less(idx, j) {
idx = j
}
}
maxElemIdx = idx
}
}
introspectiveSort(data, 0, m)
}
9. 方案7:堆排序
本方案参考C++ STL的partial_sort的思路,采用堆排序来解决:
func solution7(data sort.Interface, m int) {
PartialSort(data, m)
}
// PartialSort, Rearranges elements such that the range [0, m)
// contains the sorted m smallest elements in the range [first, data.Len).
// The order of equal elements is not guaranteed to be preserved.
// The order of the remaining elements in the range [m, data.Len) is unspecified.
func PartialSort(data sort.Interface, m int) {
//建max-heap
makeHeap(data, 0, m)
minElemIdx := 0
//遍历后续的元素
len := data.Len()
for i := m; i < len; i++ {
if data.Less(i, minElemIdx) {
//当这个后续元素比ResArr中最大的元素小,则替换
data.Swap(i, minElemIdx)
// 重新调整max-heap
siftDown(data, minElemIdx, m, minElemIdx)
}
}
// 对max-heap进行对排序
heapsort(data, 0, m)
}
10. 性能比较
笔记本电脑配置如下:
CPU: i5-3230M 2.60GHz
RAM: 8G
对100万个int数据,部分排序前1000个最大数,各个解决方案的用时如下:
13.2267565s
425.0243ms
98.0056ms
85.0048ms
44.0025ms
42.0024ms
15.0008ms
综述,采用堆排序的解决方案是这几个中的最优。
参考文章:
- 从一道笔试题谈算法优化(上): https://blog.youkuaiyun.com/gzlaiyonghao/article/details/3547776
- 从一道笔试题谈算法优化(下): https://blog.youkuaiyun.com/gzlaiyonghao/article/details/3547899
- STL之partial_sort算法源码讲解: https://blog.youkuaiyun.com/ggq89/article/details/88817085