【动态规划】将一个包含m个整数的数组分成n个数组,每个数组的和尽量接近,及其变形(Python实现)

背景

实际的问题来源于LQA系统的人员分配工作量,有两种方式,一种是 平均分配,一种是按给定比例分配。不需要AC,能得到符合题意的解就算达成目标。

平均分配

一个order订单包含一个xls表格,内含若干词条,需要把词条分配给若干LQA,每个人负责的词条总字数大致相近就好,不必完全一致。
词条不可拆分,分配工作量的占比是按字数占比。
另外延伸一个应用情况:
已知现在有x个小组,(小组之间人数差距比较大,假设有的100人,有的10个人,),需要分成n个部门。要求,小组不能拆分,各个部门的人数较为均衡。求,每个部门的小组。

抽象

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

input:
# 给定小组及其对应人数
groups = {'a':100, 'b':10, 'c':23, 'd':23, 'e':12, 'f':34, 'g':67, 'h':135, 'i':5, 'j':39, 'k':60, 'l':204}
# 目标部门数
n = 5
output:

groups: [[204], [135], [100, 23], [67, 39, 12, 5], [60, 34, 23, 10]]

思路

这里参照了这个同学的思路
https://cloud.tencent.com/developer/article/1659134
感谢

根据以上思路,核心思想是每一趟遍历一次 数组,每一趟得出一个分组列表,因为需要分为5组,所以进行5趟分组,每一趟从后开始遍历。
遍历的目的是 依次比较每个数value,和剩下的 数的平均值 avg(sum_leftover/(n-i), i 为趟数),
如果 value >=avg,则加入groups,成为一个list,并直接结束循环(因为已经比avg大的话,加上任何数只会偏离avg,增加方差)
如果 value < avg, 则加入groups,成为该趟 list 的第一个成员,继续往后循环,看看是否能加上一个数,使这个分组更接近avg。循环结束后终止这次遍历。

最后一趟,则把剩下所有数字放入最后一个groups 的同一个list。

原文举例:

数组为: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

实现

大致理解之后我就直接动手做了,没有参照原文的go代码,具体细节的问题没有特别考虑,比如经典 的动态规划一般怎么做,比如这里是否可以不考虑循环中不能remove成员的问题(长度动态变化 的迭代对象不能再次被访问)。
(我在下面的第一次实现中 未在循环中remove,严格遵守了他的语法,而实际上,因为这个for循环里,只会在满足 if 条件的时候才会做remove操作(长度变化),并且remove马上break跳出循环了,不会再进行下一次循环,所以不会报错,符合语法规范,可以大胆remove,可以减少很多不必要的变量,这点我在 按比例分配 时做了)

import numpy as np

def split(groups, n):
    # 求总数
    # 均分
    values = [i for i in groups.values()]
    values.sort(reverse=True)
    print (values, "length:", len(values))

    groups =[]
    # values_copy = values.copy()
    count_remove = 0
    cnt_out = 0
    for index in range(n):# 循环组数
        print (index)
        if index == (n-1):
            # 最后一次,直接把剩下的列为一个组
            groups_list = np.sum(groups)
            # 求两个list的差集
            for num in groups_list:
                if num in values:
                    values.remove(num)
            groups.append(values)
            print ("groups:",groups)
            return groups
        else:
            cnt = 0
            for value in values[cnt_out:]:
                # 计算剩下的数的均值
                avg = (sum(values) -np.sum(np.sum(groups)) )/(n - index)
                print ("avg:", avg)
                if value >= avg:
                    print ("value:", value)
                    count_remove += value
                    group = [value]
                    groups.append(group)
                    break#结束本层循环,继续下一层循环
                else:
                    group = [value]
                    count_remove += value
                    delta = avg-value
                    print ("delta:", delta)
                    # 从后面的数列中找到 x =dealta
                    print (cnt)
                    for i in values[cnt:]:
                        print ("searching in :", i)
                        if i <= delta:
                            group.append(i)
                            count_remove += i
                            delta -= i
                    groups.append(group)
                    break
                cnt +=1
        cnt_out +=1

【动态规划】将一个包含m个整数的数组分成n个数组,每个数组的和尽量接近,及其变形(Python实现)_第1张图片
得到结果如上,大致均衡(第一个为204是因为这个204不可再分)

按比例分配

有了前面的经验这个就简单多了,可以说是split方法 变形,在删除了很多无意义变量后,代码如下
前提:在开始之前,先按比例对总人数进行了计算,得到以下标准组:

input:
    groups = {'a':100, 'b':10, 'c':23, 'd':23, 'e':12, 'f':34, 'g':67, 'h':135, 'i':5, 'j':39, 'k':60, 'l':204}
    percents =  [45, 20,20, 15]
    n = 4
    # 计算后
    split_words = [320 , 142, 142, 106]
output:
可以自己先动手笔算一下预期结果

实现

import numpy as np

def split(groups, n, split_words):
    values = [i for i in groups.values()]
    values.sort(reverse=True)
    print (values, "length:", len(values))
    groups = []
    for index in range(n):
        standard = split_words[index]
        if index == (n-1):
            groups.append(values)
            return groups
        else:
            for value in values:
                if value > standard:
                    group = [value]
                    values.remove(value)
                    groups.append(group)
                    break
                else:
                    group = [value]
                    values.remove(value)
                    delta =standard-value
                    for i in values:
                        print ("searching in :", i)
                        if i <= delta:
                            values.remove(i)
                            group.append(i)
                            delta -= i
                    groups.append(group)
                    break

结果:
结果2
与预期结果大致,达成目标!

TODO:由于业务原因,我暂时没有思考数值重复的问题。

这个看起来比前面那个平均分配的舒服多了,也可以按照这个思路改造一下前面的代码,平均分配的代码也可以采用先算出平均数值的方法,节省后面的计算成本, 欢迎讨论。

你可能感兴趣的:(算法,算法,数据结构,python)