前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【动态规划】将一个包含m个整数的数组分成n个数组,每个数组的和尽量接近

【动态规划】将一个包含m个整数的数组分成n个数组,每个数组的和尽量接近

原创
作者头像
garyhwang
发布2020-07-10 22:02:32
6.3K0
发布2020-07-10 22:02:32
举报
文章被收录于专栏:潇洒哥写字潇洒哥写字

1 背景

ClickHouse集群缩容,为保证数据不丢失,计划将需要缩容的节点上的数据,迁移到其他节点上,保证迁移到每个机器上的数据量尽量均衡。数据的迁移已partition为单位,已知每个partition的数据量。

2 抽象

将一个包含m个整数的数组分成n个数组,每个数组的和尽量接近

3 思路

这个问题是典型的动态规划的问题,理论上是无法找到最优解的,但是本次只是为了解决实际生产中的问题,而不是要AC,所以我们只需要找到一个相对合理的算法,使得partition的分配相对均衡就好了。

输入:int数组,分组数divisionNum

  1. 对数组倒序排序
  2. 计算数组的平均值 avg
  3. 遍历数组。
  • 如果第一个数大于等于avg,将这个数单独作为一组,因为再加下一个数也不会使得求和更接近avg;然后将剩下的数重新求平均,表示需要让剩下的数分配得更加平均,这样可以避免极值的影响,然后重新开始下一轮计算
  • 如果第一个数num小于avg,我们将这个数加入到数组中,然后我们需要找到一(或若干)个数,使得其和更接近delta = avg-num,
    • 继续遍历数组,若发现某个数k==delta,将k加入到数组,结束本轮寻找
    • 若发现a > delta > b;此时要继续判断,如果(delta - b) > (a - delta),将b加入到数组,delta = delta - b,然后继续遍历;如果(delta - b) < (a - delta),保存distance = delta - b,然后将a将入到数组中,继续往下遍历,判断能否找到距离 < distance的,如果有则选择距离更小的这组,否则选择将b加入数组。

我们举一个栗子:

数组为:500, 18, 28, 2, 27, 35, 22, 10, 6, 5, 3, 2, 1;分为4组

排序为:500, 35, 28, 27, 22, 18, 10, 6, 5, 3, 2, 2, 1

计算平均值 avg = 164.75

遍历数组:

  • 第一轮:500 > avg,取出500单独作为一组;剩余数组为 35, 28, 27, 22, 18, 10, 6, 5, 3, 2, 2, 1
    • 计算avg = 53
  • 第二轮:35 < avg,取出35放入到第二组;
    • delta=53-35=18;
    • 接下来为28 > 18,继续遍历,27 > 18,22 > 18,18 == 18,于是取出18加入到第二组,结束第二轮,剩余数组为 28, 27, 22, 10, 6, 5, 3, 2, 2, 1
  • 第三轮:28 < avg, 取出28放入到第三组;
    • delta=53-28=25
    • 27 > delta > 22,27-delta=2,delta-22=3,distance = 2,将22加入临时数组,delta = 3;
    • 18 >3, ... ,5 > 3, 3==3,distance = delta-3 = 0;于是将22和3加入到第三组,结束第三轮,属于数组为 27, 10, 6, 5, 2, 2, 1
  • 第四轮:直接返回剩下数加入到一个组作为第四组

结果:

arr 0 is : 500, sum = 500

arr 1 is : 35 18, sum = 53

arr 2 is : 28 22 3, sum = 53

arr 3 is : 27 10 6 5 2 2 1, sum = 53

4 实现

代码语言:txt
复制
// 将数组分成n个数组,每个数组的和尽量接近
func GetAvgArr(numberList []int64, arrNum int) [][]int64 {
	avgArrays := make([][]int64, 0)
	if len(numberList) == 0 || len(numberList) < arrNum {
		return avgArrays
	}

	// 1. 计算平均值
	var sum, mean float64
	numberListFloat64 := make([]float64, 0)
	for _, num := range numberList {
		numberListFloat64 = append(numberListFloat64, float64(num))
		sum += float64(num)
	}
	mean = sum / float64(arrNum)

	// 2. 倒序排序
	sort.Sort(sort.Reverse(sort.Float64Slice(numberListFloat64)))

	for cnt := 0; cnt < arrNum; cnt++ {
		//fmt.Println("mean = ", mean)
		var arr []float64
		if cnt == arrNum-1 {
			// 最后一组,返回数组剩余所有数
			avgArrays = append(avgArrays, transFloatToIntList(numberListFloat64))
			break
		}
		// 如果最大的数max>=mean,这个数单独一个组
		if len(numberListFloat64) > 0 && numberListFloat64[0] >= mean {
			arr = []float64{float64(numberListFloat64[0])}
			avgArrays = append(avgArrays, transFloatToIntList(arr))
			sum = sum - numberListFloat64[0]

			// 重新计算剩下partition的平均值
			mean = sum / float64(arrNum-len(avgArrays))
		} else {
			// 否则寻找一组数据
			arr, _ = getList(numberListFloat64, mean, math.Pow(mean, 2))
			avgArrays = append(avgArrays, transFloatToIntList(arr))
		}
		// 将已经形成一组的数据从原数组中移除,准备寻找下一组数据
		numberListFloat64 = removeFromFloatList(numberListFloat64, arr)
	}

	return avgArrays
}

// 将[]float64转为[]int64
func transFloatToIntList(floatList []float64) []int64 {
	res := make([]int64, 0)
	for _, item := range floatList {
		res = append(res, int64(item))
	}
	return res
}

// 将在removeNums中出现过的数字从originalList中移除
func removeFromFloatList(originalList []float64, removeNums []float64) []float64 {
	res := make([]float64, 0)
	from := 0
	for _, remove := range removeNums {
		for i := from; i < len(originalList); i++ {
			if originalList[i] == remove {
				res = append(res, originalList[from:i]...)
				from = i + 1
				break
			}
		}
	}
	res = append(res, originalList[from:]...)
	return res
}

func getList(arr []float64, delta, distance float64) ([]float64, float64) {
	res := make([]float64, 0)
	if len(arr) == 0 {
		return res, -1
	}

	for i := 0; i < len(arr)-1; i++ {
		switch true {
		case delta == arr[i]:
			res = append(res, arr[i])
			return res, 0
		case delta < arr[i]:
			continue
		case delta > arr[i]:
			if i == 0 {
				res = append(res, arr[i])
				delta = delta - arr[i]
				distance = math.Pow(delta, 2)
				tmp, d := getList(arr[i+1:], delta, distance)
				res = append(res, tmp...)
				return res, d
			} else {
				dis1 := math.Pow(arr[i-1]-delta, 2)
				dis2 := math.Pow(delta-arr[i], 2)
				if dis1 > dis2 {
					res = append(res, arr[i])
					delta = delta - arr[i]
					tmp, d := getList(arr[i+1:], delta, dis2)
					res = append(res, tmp...)
					return res, d
				} else {
					tmp, d := getList(arr[i:], delta, dis2)
					if dis1 > d {
						res = append(res, tmp...)
						return res, d
					}

					res = append(res, arr[i-1])
					return res, dis1
				}
			}
		}
	}

	dis := math.Pow(delta-arr[len(arr)-1], 2)

	if dis < distance {
		return arr[len(arr)-1:], dis
	}

	return make([]float64, 0), -1
}

测试

代码语言:txt
复制
func main() {
	partitionList := []int64{500, 18, 28, 2, 27, 35, 22, 10, 6, 5, 3, 2, 1}
	fmt.Println("test partitionList is : ", partitionList)
	fmt.Println("result is :")
	arrays := stage.GetAvgArr(partitionList, 4)
	for i, arr := range arrays {
		fmt.Printf("arr %d is : %d, ", i, arr)
		var sumValue int64
		for _, value := range arr {
			sumValue += value
		}
		fmt.Println("sum = ", sumValue)
	}
}

几组测试结果:

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 背景
  • 2 抽象
  • 3 思路
  • 4 实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档