Python 给定互斥整数的键,找到多个字典值的最小和的有效方法是什么?

Python 给定互斥整数的键,找到多个字典值的最小和的有效方法是什么?,python,performance,combinations,Python,Performance,Combinations,我有一个字典,它的键由0到n的整数的所有len(4)组合组成,所有值都是浮点数(表示由另一个函数计算的成本) e、 g: 我希望有效地找到m个互斥键(即,键不共享任何整数),它们的值总和为最小的数(因此,找到最低的总体成本)。也就是说,我不仅仅想要字典中的m个最小值,我想要m个相互排斥的值,求和为最小值。(或者,如果没有达到绝对最小值,我不会介意一些效率非常接近的东西) 在上面的例子中,对于m=3,可能: cost_dict[(0,3,5,11)] >1.1 cost_dict[(2,6

我有一个字典,它的键由0到n的整数的所有len(4)组合组成,所有值都是浮点数(表示由另一个函数计算的成本)

e、 g:

我希望有效地找到m个互斥键(即,键不共享任何整数),它们的值总和为最小的数(因此,找到最低的总体成本)。也就是说,我不仅仅想要字典中的m个最小值,我想要m个相互排斥的值,求和为最小值。(或者,如果没有达到绝对最小值,我不会介意一些效率非常接近的东西)

在上面的例子中,对于m=3,可能:

cost_dict[(0,3,5,11)]
>1.1 
cost_dict[(2,6,7,13)]
>0.24
cost_dict[(4,10,14,15)]
>3.91
。。。可以是其值总和为该字典中所有互斥键中可能的最小值的键

dict中最小的三个值可能类似于:

cost_dict[(0,3,7,13)]
>0.5
cost_dict[(2,6,7,13)]
>0.24
cost_dict[(4,6,14,15)]
>0.8
但考虑到这些键中的整数不是互斥的,这是不正确的


有可能比O(n**m)时间做得更好吗?也就是说,对于m个级别,我可以将每个项与键与第一个不相交的其他项相加(这需要键是冻结集而不是元组)。这是相当缓慢的,因为字典的长度可以达到10000


在这个问题的早期版本中,似乎对我有所帮助的是创建一个所有可能的键组合的列表,这是一个时间密集型的列表,但由于我需要多次找到最小成本,因此可能更有效

我尝试了三种不同的方法来解决这个问题——优化蛮力、动态规划方法和贪婪算法。前两种方法无法处理
n>17
的输入,但生成了最优解,因此我可以使用它们来验证贪婪方法的平均性能。我将首先从动态规划方法开始,然后描述贪婪方法

动态规划 首先,请注意,如果我们确定
(1,2,3,4)
(5,6,7,8)
之和小于
(3,4,5,6)
(1,2,7,8)
,那么您的最优解绝对不能同时包含
(3,4,5,6)
(1,2,7,8)
-因为你可以把它们换成前者,得到一个较小的金额。扩展这个逻辑,将有一个
(a,b,c,d)
(e,f,g,h)
的最佳组合,这将导致所有
x0,x1,x2,x3,x4,x5,x6,x7的组合的最小和,因此我们可以排除所有其他组合

利用这些知识,我们可以从集合
[0,n)
,通过强制执行
x0,x1,x2,x3
的所有组合的总和,将其最小化。然后,我们可以使用这些映射对
x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11
x0,x1,x2,x3,x4,x5,x6,x7和
x0,x1,x1,x2,x2,x3对重复此过程直到我们得到
x0,x1…x_4*m-1)
的所有最小和,然后我们对其进行迭代以找到最小和

def dp_solve(const_dict, n, m):

    lookup = {comb:(comb,) for comb in const_dict.keys()}

    keys = set(range(n))
    for size in range(8, 4 * m + 1, 4):
        for key_total in combinations(keys, size):
            key_set = set(key_total)
            min_keys = (key_total[:4], key_total[4:])
            min_val = const_dict[min_keys[0]] + const_dict[min_keys[1]]

            key1, key2 = min(zip(combinations(key_total, 4), reversed(list(combinations(key_total, size - 4)))), key=lambda x:const_dict[x[0]]+const_dict[x[1]])

            k = tuple(sorted(x for x in key1 + key2))
            const_dict[k] = const_dict[key1] + const_dict[key2]
            lookup[k] = lookup[key1] + lookup[key2]

    key, val = min(((key, val) for key, val in const_dict.items() if len(key) == 4 * m), key=lambda x: x[1])
    return lookup[key], val
诚然,这个实现非常粗糙,因为我一直在一块接一块地进行微优化,希望在不必切换到贪婪方法的情况下使其足够快

贪心的 这可能是您关心的问题,因为它可以快速处理相当大的输入,并且非常准确

首先为部分和构造一个列表,然后通过增加值开始迭代字典中的元素。对于每个元素,找到所有不会与其键产生任何冲突的部分和,然后“合并”将它们添加到一个新的部分和中,并附加到列表中。在这样做时,您可以构建一个最小部分和列表,这些最小部分和可以从字典中最小的
k
值创建。为了加快这一切,我使用哈希集快速检查哪些部分和包含相同密钥的对

在“快速”贪婪方法中,当您找到键长为
4*m
(或者相当于
m
4元组)的部分和时,您会中止。根据我的经验,这通常会获得相当好的结果,但我想添加一些逻辑,以便在需要时使其更精确。为此,我添加了两个因素-

  • extra_运行
    ——它指示在中断前搜索更好解决方案的额外迭代次数
  • check\u factor
    -指定当前搜索“深度”的倍数,以向前扫描单个新整数,从而在当前状态下创建更好的解决方案。这与上述不同,因为它不“保留”检查每个新整数-它只进行快速求和,以查看是否创建了新的最小值。这使它的速度大大加快,但代价是另一个
    m-1
    4元组必须已经存在于其中一个部分和中
综合起来,这些检查似乎总能找到真正的最小和,但运行时间要长5倍左右(尽管仍然很快)。要禁用它们,只需对这两个因素都通过
0

def greedy_solve(const_dict, n, m, extra_runs=10, check_factor=2):
    pairs = sorted(const_dict.items(), key=lambda x: x[1])

    lookup = [set([]) for _ in range(n)]
    nset = set([])

    min_sums = []
    min_key, min_val = None, None
    for i, (pkey, pval) in enumerate(pairs):
        valid = set(nset)
        for x in pkey:
            valid -= lookup[x]
            lookup[x].add(len(min_sums))
        
        nset.add(len(min_sums))
        min_sums.append(((pkey,), pval))

        for x in pkey:
            lookup[x].update(range(len(min_sums), len(min_sums) + len(valid)))
        for idx in valid:
            comb, val = min_sums[idx]
            for key in comb:
                for x in key:
                    lookup[x].add(len(min_sums))
            nset.add(len(min_sums))
            min_sums.append((comb + (pkey,), val + pval))
            if len(comb) == m - 1 and (not min_key or min_val > val + pval):
                min_key, min_val = min_sums[-1]
        
        if min_key:
            if not extra_runs: break
            extra_runs -= 1

    for pkey, pval in pairs[:int(check_factor*i)]:
        valid = set(nset)
        for x in pkey:
            valid -= lookup[x]
        
        for idx in valid:
            comb, val = min_sums[idx]
            if len(comb) < m - 1:
                nset.remove(idx)
            elif min_val > val + pval:
                min_key, min_val = comb + (pkey,), val + pval
    return min_key, min_val
def贪婪求解(常量,n,m,额外运行=10,检查因数=2):
pairs=已排序(const_dict.items(),key=lambda x:x[1])
查找=[为范围(n)中的uu设置([])]
nset=设置([])
最小和=[]
最小值键,最小值=无,无
对于枚举(成对)中的i(pkey,pval):
有效=设置(nset)
对于pkey中的x:
有效-=查找[x]
查找[x]。添加(长度(最小和))
nset.添加(长度(最小和))
最小和追加((pkey,),pval))
对于pkey中的x:
查找[x]。更新(范围(len(最小和)、len(最小和)+len(有效)))
对于有效的idx:
科姆,瓦尔=
def greedy_solve(const_dict, n, m, extra_runs=10, check_factor=2):
    pairs = sorted(const_dict.items(), key=lambda x: x[1])

    lookup = [set([]) for _ in range(n)]
    nset = set([])

    min_sums = []
    min_key, min_val = None, None
    for i, (pkey, pval) in enumerate(pairs):
        valid = set(nset)
        for x in pkey:
            valid -= lookup[x]
            lookup[x].add(len(min_sums))
        
        nset.add(len(min_sums))
        min_sums.append(((pkey,), pval))

        for x in pkey:
            lookup[x].update(range(len(min_sums), len(min_sums) + len(valid)))
        for idx in valid:
            comb, val = min_sums[idx]
            for key in comb:
                for x in key:
                    lookup[x].add(len(min_sums))
            nset.add(len(min_sums))
            min_sums.append((comb + (pkey,), val + pval))
            if len(comb) == m - 1 and (not min_key or min_val > val + pval):
                min_key, min_val = min_sums[-1]
        
        if min_key:
            if not extra_runs: break
            extra_runs -= 1

    for pkey, pval in pairs[:int(check_factor*i)]:
        valid = set(nset)
        for x in pkey:
            valid -= lookup[x]
        
        for idx in valid:
            comb, val = min_sums[idx]
            if len(comb) < m - 1:
                nset.remove(idx)
            elif min_val > val + pval:
                min_key, min_val = comb + (pkey,), val + pval
    return min_key, min_val