快速排序算法 - go实现

在分析redis集群中大Key的时候,通常都采用分析rdb文件的方式;但是这种方式需要在每一台redis服务器上部署分析程序及分析脚本,而像salt之类的工具运维没有开放给我们使用,一台一台部署不好管理。正好我们的总redis规模不大,大概在200个集群左右,考虑到cluster集群,需要分析的redis实例数在300左右,所以就想着能不能通过scan的方式来进行。

通过scan命令扫描从库,将集群中key深度大于指定值的key扫描出来,将这些 key 放在一个slice中,取 top N,这就需要对 slice中的 key按照深度进行排序。300个实例说多不多,说少不少,排序算法的性能还是很重要的。快速排序针对小数据量排序性能很好,正好mysql在使用sort buffer进行排序时采用的是快速排序,这里就用go实现来复习一下快速排序算法。

func sortBigKeySlice(bigKeySlice []*bigKeyInstance) {
   if len(bigKeySlice) < 2 {
      return
   }
   sortBigKey(bigKeySlice, 0, len(bigKeySlice))
}

//使用递归实现
func sortBigKey(bigKeySlice []*bigKeyInstance, low, high int) {
   if low >= high {
      return
   }
   p := partition(bigKeySlice, low, high)
   sortBigKey(bigKeySlice, low, p)
   sortBigKey(bigKeySlice, p + 1, high)
}
//普通快速排序,对于普通快速排序,将第一个元素作为基准,小于该元素的放在左边,大于等于该元素的放在右边
func partition(bigKeySlice []*bigKeyInstance, low, high int) int{
   //直接将第一个元素作为分隔值
   pivotIns := bigKeySlice[low]
   //当前第一个元素点位作为标记点
   pivotPos := low

   //去除第一个分割值,遍历元素,如果元素比分割值小,将标记点右移一位,交换元素的值,大于等于则继续比较下一个元素
   for i := low + 1; i < high; i++ {
      if bigKeySlice[i].size < pivotIns.size {
         pivotPos += 1
         bigKeySlice[pivotPos], bigKeySlice[i] = bigKeySlice[i], bigKeySlice[pivotPos]
      } else {
         continue
      }
   }
   //不要忘记最后的互换,将分隔值与标记点元素互换
   bigKeySlice[low], bigKeySlice[pivotPos] = bigKeySlice[pivotPos], bigKeySlice[low]
   return pivotPos
}

  

普通快速排序默认左边的第一个元素作为基准数,对于渐进有序的数组来说,这就导致小于基准的数会相当少,而大于等于基准的数相当多,造成分区不平衡的问题,普通排序就会退化,严重的将退化成O(n^2)。所以对其改进:不再默认选择第一个数,而是随机选一个数作为基准,这样的快排称为随机普通快排。

//随机普通快速排序,不使用第一个元素作为基准,而是使用一个随机元素作为基准
func partition(bigKeySlice []*bigKeyInstance, low, high int) int {
	//取slice中的一个随机元素作为分割点,而不是第一个元素开始分割
	rand_low := low + rand.Intn(high - low)
	bigKeySlice[low], bigKeySlice[rand_low] = bigKeySlice[rand_low], bigKeySlice[low]
	pivotPos := low
	for i := low + 1; i < high; i++ {
		if bigKeySlice[i].size < bigKeySlice[low].size {
			pivotPos += 1
			bigKeySlice[pivotPos], bigKeySlice[i] = bigKeySlice[i], bigKeySlice[pivotPos]
		} else {
			continue
		}
	}
	bigKeySlice[low], bigKeySlice[pivotPos] = bigKeySlice[pivotPos], bigKeySlice[low]
	return pivotPos
}

  

对于含有大量重复元素的数组,则对于与基准数相同的数,要么分到了左边,要么分到了右边,同样会造成分治不平衡的问题,造成性能退化。这时,采用双路排序或三路排序进行改进。

双路排序 & 三路排序待续....

 

posted @ 2020-08-04 19:16  卷毛狒狒  阅读(28)  评论(0编辑  收藏