【问题标题】:splitting list in chunks of balanced weight以平衡重量块分割列表
【发布时间】:2011-07-28 07:24:49
【问题描述】:

我需要一种算法将值列表拆分成这样的块,每个块中的值总和(大约)等于(我想它是Knapsack problem 的一些变体)

所以,例如 [1, 2, 1, 4, 10, 3, 8] => [[8, 2], [10], [1, 3, 1, 4]]

等长的块是首选,但这不是一个约束。

Python 是首选语言,但也欢迎其他语言

编辑:块数已定义

【问题讨论】:

  • 恐怕你的问题没有很好的定义。对块的数量与与完全相等的总和的偏差是否有要求?正如目前所提出的,这个问题有一个简单的解决方案,即只有一个块。
  • 闻起来是 NP-Hard。您应该定义什么是“近似”,因为我相信没有多项式解决方案可以找到最佳分区。
  • @Petar Ivanov:我已经在编辑中精确 - 定义了块的数量
  • @amit:这就是我寻找近似值的原因
  • 这是广义的分区问题:en.wikipedia.org/wiki/Partition_problem,是 NP-complete。

标签: python algorithm optimization language-agnostic combinatorics


【解决方案1】:

贪婪:
1. 可用项目降序排列。
2.创建N个空组
3. 开始将一项一项添加到总和最小的组中。

我认为在大多数现实生活中这应该足够了。

【讨论】:

  • O(NlogN)。排序是瓶颈,该解决方案将确保两组之间的差异最多为 max{S}
  • 在另一个线程中,类似于这个,我已经证明 max{S}-min{S} 是这个算法的最大差异。看看:stackoverflow.com/questions/6455703/…
  • @amit。将[1, 1, 1] 分成两块怎么样?我认为 max(S) 听起来更像是正确的答案。
【解决方案2】:

根据@Alin Purcaru 的回答和@amit 的评论,我编写了代码(Python 3.1)。据我测试,它具有线性性能(项目数和块数,所以最后是 O(N * M))。我避免每次都对列表​​进行排序,保留字典中每个块的当前值总和(对于更多的块可能不太实用)

import time, random

def split_chunks(l, n):
    """ 
       Splits list l into n chunks with approximately equals sum of values
       see  http://stackoverflow.com/questions/6855394/splitting-list-in-chunks-of-balanced-weight
    """
    result = [[] for i in range(n)]
    sums   = {i:0 for i in range(n)}
    c = 0
    for e in l:
        for i in sums:
            if c == sums[i]:
                result[i].append(e)
                break
        sums[i] += e
        c = min(sums.values())    
    return result


if __name__ == '__main__':

    MIN_VALUE = 0
    MAX_VALUE = 20000000
    ITEMS     = 50000
    CHUNKS    = 256

    l =[random.randint(MIN_VALUE, MAX_VALUE ) for i in range(ITEMS)]

    t = time.time()

    r = split_chunks(l, CHUNKS)

    print(ITEMS, CHUNKS, time.time() - t)

只是因为,您知道,我们可以在 PHP 5.3 中使用相同的代码(比 Python 3.1 慢 2 - 3 倍):

function split_chunks($l, $n){

    $result = array_fill(0, $n, array());
    $sums   = array_fill(0, $n, 0);
    $c = 0;
    foreach ($l as $e){
        foreach ($sums as $i=>$sum){
            if ($c == $sum){
                $result[$i][] = $e;
                break;  
            } // if
        } // foreach
        $sums[$i] += $e;        
        $c = min($sums);
    } // foreach
    return $result;
}

define('MIN_VALUE',0);
define('MAX_VALUE',20000000);
define('ITEMS',50000);
define('CHUNKS',128);

$l = array();
for ($i=0; $i<ITEMS; $i++){
    $l[] = rand(MIN_VALUE, MAX_VALUE);  
}

$t = microtime(true);

$r = split_chunks($l, CHUNKS);

$t = microtime(true) - $t;

print(ITEMS. ' ' .  CHUNKS .' ' . $t . ' ');

【讨论】:

【解决方案3】:

您可能想使用人工智能工具来解决这个问题。 首先定义你的问题

States={(c1,c2,...,ck) | c1,...,ck are subgroups of your problem , and union(c1,..,ck)=S } 
successors((c1,...,ck)) = {switch one element from one sub list to another } 
utility(c1,...,ck) = max{sum(c1),sum(c2)...} - min{sum(c1),sum(c2),...}

现在,您可以将steepest ascent hill climbing 与随机重启一起使用。

这个算法将是anytime,这意味着你可以开始搜索,当时间到了 - 停止它,你会得到迄今为止最好的结果。随着运行时间的增加,结果会更好。

【讨论】:

    【解决方案4】:

    这会更快,更干净一些(基于上面的想法!)

    def split_chunks2(l, n):
        result = [[] for i in range(n)]
        sums   = [0]*n
        i = 0
        for e in l:
            result[i].append(e)
            sums[i] += e
            i = sums.index(min(sums)) 
        return result
    

    【讨论】:

    • 鉴于没有提供解释,一些标识符可能更能说明问题。
    猜你喜欢
    • 2018-07-18
    • 2018-02-05
    • 2019-09-05
    • 1970-01-01
    • 1970-01-01
    • 2016-07-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多