有效地将盒子堆叠到最少数量的堆栈中?

时间:2016-10-24 16:31:15

标签: python algorithm

我在网上编码挑战中得到了这个问题。

给出一个包含长度和宽度(l,h)的框的列表,输出包含所有框所需的最小堆栈数,假设如果它的长度和宽度小于一个框,则可以将一个框堆叠在另一个框的顶部。其他盒子。

我无法弄清楚如何提出多项式时间解决方案。我已经构建了一个蛮力解决方案,以递归方式创建所有可能的堆栈布局(从N个堆栈开始。然后对于每个堆栈,尝试将它放在每个堆栈的顶部。然后在给定新堆栈布置的情况下递归生成所有堆栈可能性。),然后返回所需的最小堆栈数。

我加快了一些优化:

  • 如果您可以在方框B和方框C的顶部堆放方框A,并且您可以将方框B堆叠在方框C的顶部,则不要考虑在方框C上堆叠方框A.
  • 记住堆栈安排,只考虑堆栈的底层和顶层

以下是此解决方案的python代码:

from time import time

def all_stacks(bottom, top, d={}):

    memo_key = (tuple(bottom), tuple(top))
    if memo_key in d:
        # print "Using MEMO"
        return d[memo_key]

    res = len(bottom)
    found = False
    stack_possibilities = {}
    # try to stack the smallest boxes in all the other boxes
    for j, box1 in enumerate(bottom):
        stack_possibilities[j] = []
        for i, box2 in enumerate(top[j:]):
            # box1 fits in box2
            if fits(box2, box1):
                stack_possibilities[j].append(i + j)
                found = True

    for fr, to_list in stack_possibilities.iteritems():
        indices_to_keep = []
        for box_ind in to_list:
            keep = True
            for box_ind_2 in to_list:
                if fits(top[box_ind], top[box_ind_2]):
                    keep = False
            if keep:
                indices_to_keep.append(box_ind)
        stack_possibilities[fr] = indices_to_keep


    if found:
        lens = []
        for fr, to_list in stack_possibilities.iteritems():
            # print fr
            for to in to_list:
                b = list(bottom)
                t = list(top)
                t[to] = t[fr]
                del b[fr]
                del t[fr]
                lens.append(all_stacks(b, t, d))
        res = min(lens)

    d[memo_key] = res

    return res

def stack_boxes_recursive(boxes):
    boxes = sorted(boxes, key=lambda x: x[0] * x[1], reverse=False)
    stacks = all_stacks(boxes, boxes)
    return stacks

def fits(bigbox, smallbox):
    b1 = smallbox[0] < bigbox[0]
    b2 = smallbox[1] < bigbox[1]
    return b1 and b2


def main():

    n = int(raw_input())
    boxes = []
    for i in range(0,n):
        l, w = raw_input().split()
        boxes.append((long(l), long(w)))
    start = time()
    stacks = stack_boxes_recursive(boxes)
    print time() - start

    print stacks


if __name__ == '__main__':
    main()

输入

17
9 15
64 20
26 46
30 51
74 76
53 20
66 92
90 71
31 93
75 59
24 39
99 40
13 56
95 50
3 82
43 68
2 50

输出:

33.7288980484
6

该算法在几秒钟(1-3)内解决了16个问题,在30秒内解决了17个问题。我很确定这是指数时间。 (因为有一个指数的堆栈安排)。

我很确定有一个多项式时间解决方案,但我不知道它是什么。其中一个问题是memoization依赖于当前的堆栈安排,这意味着你必须做更多的计算。

3 个答案:

答案 0 :(得分:4)

让我们构建一个图形,其中每个框都有顶点,如果A可以堆叠在B的顶部,则从框A到框B的边缘。这个图是非循环的(因为“可以堆叠在顶部”是一个传递关系和盒装不能堆叠在自身之上)。

现在我们需要找到该图的最小路径覆盖。这是一个标准问题,它以这种方式解决:

  1. 让我们构建一个二分图(原始图中的每个顶点在左侧和右侧都有两个“副本”)。对于原始图表中的每个A边缘,B的左侧副本与O(n)的右侧副本之间存在边缘。

  2. 答案是方框数减去二分图中最大匹配的大小。

  3. 二分图为O(n^2)个顶点和O(n^3)个边。例如,如果我们使用Kuhn的算法来查找匹配,则总时间复杂度为n,其中{{1}}是框的数量。

答案 1 :(得分:1)

我最近也遇到过这个问题。我提出以下O(NlogN)解决方案:

1)维护正在使用的所有当前盒子堆栈的最顶层框的列表 AllStkTops 。它将被初始化为空列表。

2)按照长度的递减顺序对框进行排序。 (如果长度相等,则按增加(是,不减少)广度顺序对它们进行排序)。

3)开始挑选框(从最长的开始)并将它们放入当前堆栈中的一个。对于第一个框,将没有堆栈,因此它将成为第一个框堆栈的基础。第二个框将转到第一个框的顶部或将成为第二个框的基础。当我们继续这个过程时,我们会意识到,对于任何手中的盒子,它的长度将小于或等于所有堆叠的最顶层盒子。因此,我们只需要担心广度。从第一个堆栈开始检查所有当前堆栈顶部的宽度,并将其放在堆栈顶部,其中i)长度和宽度严格大于当前堆栈的宽度和ii)最小可能宽度(为了最优化)。如果没有任何堆栈可以容纳此框,请创建一个以此框为基础的新堆栈。

请注意,所有堆栈顶部的宽度将按递增顺序排列,因为当框的宽度大于该时间点的所有堆栈顶部时,我们只创建一个新堆栈。 (对于等长盒子的情况,我们已经按照排序的顺序增加了它们)。因此,我们可以使用二进制搜索过程来找到具有足够大宽度的最窄堆栈顶部。但我们还需要确保其长度严格大于(而不仅仅等于)给定框的长度。但是,我声称顶部的长度永远不会成为问题因为
i)盒子按长度的递减顺序挑选,因此顶部的长度肯定不会严格减少,并且 ii)它也可能不相等,因为我们已经按照其宽度的递增顺序对相同长度的方框进行了排序,因此获得前一个方框的堆栈必须位于此堆栈顶部的左侧。

因此,我们可以使用二进制搜索程序找到当前框的最佳堆栈顶部。

我们可以重复上述步骤,直到我们将所有方框放入堆栈中。最后,计算 AllStkTops 中的堆栈数。

这是O(NlogN)的复杂性,因为排序需要O(NlogN)时间,而每个盒子的二进制搜索需要O(logN)时间。

我很乐意接受有人在我的算法中发现的任何问题和/或缺陷。

干杯!

答案 2 :(得分:0)

一开始看起来很简单,但考虑到可能性,很快就变得艰难。然而,我已经设法在JS中实现了我的算法,我感到非常自信,而且JS具有诸如对象作为引用类型的特性,在这个特殊情况下对我有很大的帮助。

我首先假设我们可以将方框转动为x轴上的长边。然后给出的17个盒子在Chrome中4到10毫秒之间完成,在FF中15到25毫秒,结果至少5个盒子可以包含所有17个盒子。

此外,我尝试了50 boxes case(每个随机尺寸在1-100之间)。因此,根据它们的适用方式,50个盒子的盒子可以在Chrome和FF中解析出令人难以置信的250~15000毫秒。我猜70是这项工作的限制,然后才变得很无聊。

代码仍然可以在速度方面得到提升,但到目前为止这是如此。一旦我有空闲时间,我会尝试在一两天内详细描述代码。

function insertBoxes(data){
  if (data.length <= 1) return data[0] ? [data] : data;

  function flatMap(o){
    return o.inside.length ? o.inside.reduce((p,b) => p.concat(flatMap(b).map(f => f.concat(o.id))),[])
                           : [[o.id]];
  }

  function getBest(arr, r = []){
    var i = arr.findIndex(a => a.every(i => !used[i])),len,tgt,bests,best;
    if (i === -1) return r;
      len = arr[i].length;
      tgt = arr.slice(i);
    bests = tgt.filter(a => a.length === len && a.every(x => !used[x]));
     best = bests.map((a,i) => [a.reduceRight((p,c) => p + boxes[c].x + boxes[c].y, 0), i])
                 .reduce((p,c) => c[0] < p[0] ? c : p,[Infinity,-1]);
    bests[best[1]].forEach(i => used[i] = true);
    r.push(bests[best[1]]);
    return getBest(tgt,r);
  }

  var boxes = data.map((e,i) => ({id:i, x:Math.max(e[0],e[1]), y:Math.min(e[0],e[1]), inside:[]})),
       used = Array(data.length).fill(false),
    interim = boxes.map((b,_,a) => { a.forEach(box => (b.x > box.x && b.y > box.y) && b.inside.push(box));
                                     return b;
                                   })
                   .map(b => flatMap(b))
                   .reduce((p,c) => p.concat(c))
                   .sort((a,b) => b.length-a.length);
  return getBest(interim).map(b => b.map(i => data[i]))
                         .concat(insertBoxes(data.reduce((p,c,i) => used[i] ? p : (p.push(c),p) ,[])));
}

var input = [[9, 15], [64, 20], [26, 46], [30, 51], [74, 76], [53, 20], [66, 92], [90, 71], [31, 93], [75, 59], [24, 39], [99, 40], [13, 56], [95, 50], [3, 82], [43, 68], [2, 50]];
   result = [],
       ps = 0,
       pe = 0,
ps = performance.now();
result = insertBoxes(input);
pe = performance.now();
console.log("boxing", input.length, "boxes done in:", pe-ps,"msecs and all boxes fit in just", result.length,"boxes");
console.log(JSON.stringify(result));

注意:上面的代码使用递归的自上而下的方法,但我已经开始认为从底部到顶部的动态编程方法将是解决这个问题的真正方法。

好的,正如我认为动态编程方法带来了更快的解决方案。我不是删除上述内容,但请忽略它。

以下代码将在不到1毫秒的时间内解析17个项目框数组,在不到100毫秒内解析1000个项目框数组。

function boxBoxes(d){
  return d.sort((a,b) => b[0]*b[1] - a[0]*a[1])
          .map(b => b[0] < b[1] ? b : [b[1],b[0]])
          .map(function(b,i,a){
                 b.c = i ? a.slice(0,i)
                            .filter(f => f[0] > b[0] && f[1] > b[1]).length || Infinity
                         : Infinity;
                 return [b];
               })
          .sort((a,b) => b[0].c - a[0].c)
          .reduceRight(function(p,c,i,a){
                         var r = a.filter(f => f[f.length-1][0] > c[0][0] &&
                                               f[f.length-1][1] > c[0][1]);
                         r.length && r[0].push(...a.splice(i,1)[0]);
                         return a;
                       },void 0);
}

var data = [[9, 15], [64, 20], [26, 46], [30, 51], [74, 76], [53, 20], [66, 92], [90, 71], [31, 93], [75, 59], [24, 39], [99, 40], [13, 56], [95, 50], [3, 82], [43, 68], [2, 50]];
  result = boxBoxes(data);
console.log(result.length,"boxes:",JSON.stringify(result));