golang之各种PartialSort算法性能比较

受到 从一道笔试题谈算法优化(上)从一道笔试题谈算法优化(下) 这个系列文章的启发,并结合C++ STLpartial_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));

后面几种解决方法都是思路都一致,概括如下:

  1. 将 data中的前面m个元素作为算法的初始数据;
  2. 找出这 m 个数的最大值 idx;
  3. 遍历所有剩下的元素 i ,如果 i 小于 idx,将 idx 替换为 i ,然后再找出新的 m 个数中的最大值 idx ;
  4. 排序最后得到的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

综述,采用堆排序的解决方案是这几个中的最优。


参考文章:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值