用算法求多个不同尺寸长方体箱子最小占用空间

已知多个长方体箱子的长宽高,如何用程序计算出它们占用空间最小的排列方式?

  1. 每个箱子的大小可能不相同。
  2. 占用空间是指占用的长方体空间,就是说将它们排列好后放进一个长方体的大箱子里。
  3. 所有箱子必须横平竖直的摆放,不允许斜着摆。
  4. 容纳这些长方体箱子的箱子尺寸是固定的。

基于ChatGPT的回答:
可以使用深度优先搜索(DFS)和回溯算法来解决这个问题。具体步骤如下:

1、对每个箱子进行排序,使得它们的长、宽、高分别按照从大到小的顺序排列。

2、从一个空的大箱子开始,依次尝试将每个箱子放入其中。

3、对于每个箱子,有六种摆放方式,即长、宽、高分别对应大箱子的长、宽、高。依次尝试这六种摆放方式,如果能够放下,则将该箱子放入大箱子中,并递归地处理剩下的箱子。

4、如果所有箱子都已经放入大箱子中,则计算大箱子的体积,并更新最小体积。

5、回溯到上一步,尝试其他的摆放方式,直到所有的摆放方式都尝试过。

返回最小体积。

下面是具体的python代码实现:


import itertools

def dfs(box_list, big_box, used, min_volume):
    if all(used):
        # 所有箱子都已经放入大箱子中,计算大箱子的体积
        volume = big_box[0] * big_box[1] * big_box[2]
        if volume < min_volume:
            min_volume = volume
        return min_volume

    # 对未使用的箱子进行排序
    unused_boxes = [box for i, box in enumerate(box_list) if not used[i]]
    unused_boxes.sort(key=lambda x: x[0] * x[1] * x[2], reverse=True)

    for box in unused_boxes:
        # 尝试将箱子放入大箱子中的六种摆放方式
        for i, j, k in itertools.permutations([0, 1, 2]):
            if box[i] <= big_box[i] and box[j] <= big_box[j] and box[k] <= big_box[k]:
                new_box = list(big_box)
                new_box[i] -= box[i]
                new_box[j] -= box[j]
                new_box[k] -= box[k]
                new_used = list(used)
                new_used[box_list.index(box)] = True
                min_volume = dfs(box_list, new_box, new_used, min_volume)

    return min_volume

def min_box_volume(box_list, big_box):
    used = [False] * len(box_list)
    min_volume = dfs(box_list, big_box, used, float('inf'))
    return min_volume

其中,box_list是一个二维列表,包含每个箱子的长、宽、高;big_box是一个三元组,表示大箱子的长、宽、高。函数min_box_volume返回最小体积。

先贪心求出浪费最小的情况,再dfs枚举装箱。dfs可能消耗时间有点多,用IDA*吧

这个问题是一个经典的三维装箱问题,可以使用启发式算法来解决,例如模拟退火算法、遗传算法、禁忌搜索等。这里提供一个贪心算法的示例代码,该算法可以在较短的时间内得到一个近似最优解。

import itertools

def compute_box_size(boxes):
    """计算给定箱子列表的最小占用空间"""
    min_vol = float('inf')
    for perm in itertools.permutations(boxes):
        # 枚举所有排列方式
        x, y, z = 0, 0, 0
        for box in perm:
            # 将当前箱子放入空间中
            x = max(x, box[0])
            y = max(y, box[1])
            z = max(z, box[2])
        vol = x * y * z
        if vol < min_vol:
            min_vol = vol
    return min_vol

# 示例
boxes = [(3, 4, 5), (1, 2, 3), (4, 5, 6)]
min_vol = compute_box_size(boxes)
print(min_vol)

输出结果为:

img


这里假设给定的箱子列表为 boxes,每个箱子的长宽高分别存储在一个三元组中,例如 (3, 4, 5) 表示一个长为 3、宽为 4、高为 5 的箱子。compute_box_size 函数使用了一个嵌套的迭代器来枚举所有可能的箱子排列方式,然后对于每种排列方式,计算占用的空间大小,并保留最小值。


class Box:
    def __init__(self, length, width, height):
        self.length = length
        self.width = width
        self.height = height
def pack_boxes(boxes, container_length, container_width, container_height):
    # 按照宽度从大到小排序
    boxes = sorted(boxes, key=lambda box: box.width, reverse=True)
    # 创建一个空的二维矩阵
    matrix = [[0 for _ in range(container_width)] for _ in range(container_length)]
    # 保存每个箱子的放置位置
    positions = [None] * len(boxes)
    # 依次将每个箱子放入当前占用空间最小的列中
    for box in boxes:
        # 找到当前占用空间最小的列
        min_col = None
        min_height = container_height
        for j in range(container_width - box.width + 1):
            col_height = max(matrix[i][j:j+box.width]) for i in range(container_length)
            if col_height < min_height:
                min_col = j
                min_height = col_height
        # 如果当前列放不下该箱子,则将该箱子放入下一列的顶部
        if min_height + box.height > container_height:
            min_col = 0
        # 将箱子放入当前列中
        for i in range(min_col, min_col + box.width):
            for j in range(min_height, min_height + box.height):
                matrix[j][i] = 1
        # 保存箱子的放置位置
        positions[boxes.index(box)] = (min_col, min_height)
    # 计算占用空间大小
    total_height = sum(box.height + position[1] for box, position in zip(boxes, positions))
    # 输出占用空间大小和箱子的放置位置
    print(f'Total height: {total_height}')
    for box, position in zip(boxes, positions):
        print(f'Box {boxes.index(box)}: ({position[0]}, {position[1]})')

以上代码中,Box类表示一个长方体箱子,包含length、width和height三个属性。pack_boxes函数接受一个箱子列表boxes和一个容器的长、宽、高container_length、container_width和container_height,返回所有箱子的放置位置和占用空间大小。

引用chatGPT作答,以下是其中的一种算法:

1.初始化一个大小为大箱子尺寸的空间。
2.对所有箱子进行排序,从大到小依次取出一个箱子。
3.将当前箱子放入空间中,从空间的左下角开始找到第一个能够放下当前箱子的位置。
4.将当前箱子放入该位置,更新空间的占用情况。
5.重复步骤2-4,直到所有箱子都被放置在空间中。
6.输出空间的占用情况,即为最小占用空间的排列方式。
该算法的时间复杂度为 $O(n^2)$,其中 $n$ 是箱子的数量。在实际应用中,可能需要通过加入一些启发式规则来优化算法的性能,例如将箱子按长宽高排序,优先放置长宽高相似的箱子,等等。

以下答案由GPT-3.5大模型与博主波罗歌共同编写:
这是一个NP难问题,目前还没有确定的多项式时间解法。但是可以采用启发式算法或近似算法进行求解。

一种常见的启发式算法是贪心算法。具体来说,可以按照一定的规则将所有的箱子放入一个大箱子中,使得它们的占用空间最小化。一种常见的规则是将箱子按照体积从大到小进行排序,然后依次将它们放入大箱子中,每次选择一个未被摆放的箱子,将其放在当前可以放置的位置中,使得它的底部尽可能靠近大箱子的底部,并且旋转箱子使得它的最大尺寸与大箱子的对应尺寸对齐。这个过程直到所有箱子都被放置完成。

另一种常见的算法是离线最优算法,它可以在指数时间内找到全局最优解,但是只适用于数量较少的箱子。具体来说,它可以把所有的箱子组成一个二进制字符串,其中每一个位表示一个箱子是否被放置,然后枚举所有可能的字符串并计算它们对应的总体积,最终选择体积最小的一个字符串对应的放置方式。这个算法的问题在于它的时间复杂度是指数级别的。

下面是贪心算法的Python实现:

import itertools

def pack_boxes(boxes, container):
    sorted_boxes = sorted(boxes, key=lambda box: box[0]*box[1]*box[2], reverse=True)
    positions = [(x, y, z)
                 for x in range(container[0])
                 for y in range(container[1])
                 for z in range(container[2])]
    rotations = list(itertools.permutations(range(3)))
    best_volume, best_positions, best_rotations = float('inf'), None, None
    for perm in itertools.permutations(sorted_boxes):
        used_positions = []
        for box in perm:
            best_candidate = None
            best_candidate_volume = float('inf')
            for pos in positions:
                for rotate in rotations:
                    candidate_width, candidate_height, candidate_depth = (
                        box[rotate[0]],
                        box[rotate[1]],
                        box[rotate[2]])
                    candidate_pos = (
                        pos[0],
                        pos[1],
                        pos[2])
                    if (candidate_pos, rotate) in used_positions:
                        continue
                    if (candidate_pos[0] + candidate_width > container[0] or
                        candidate_pos[1] + candidate_height > container[1] or
                        candidate_pos[2] + candidate_depth > container[2]):
                        continue
                    candidate_volume = candidate_width * candidate_height * candidate_depth
                    if candidate_volume < best_candidate_volume:
                        best_candidate_volume = candidate_volume
                        best_candidate = (candidate_pos, rotate)
            if best_candidate is None:
                break
            used_positions.append(best_candidate)
        if len(used_positions) == len(perm):
            permutation_volume = sum(box[0]*box[1]*box[2] for box in perm)
            if permutation_volume < best_volume:
                best_volume = permutation_volume
                best_positions = used_positions
                best_rotations = [rotate for pos, rotate in used_positions]
    return best_volume, best_positions, best_rotations

这个函数接收两个参数,其中boxes是一个二元组列表,每个二元组表示一个长方体箱子的长、宽、高,例如[(1, 2, 3), (2, 3, 4)]container是一个三元组,表示大箱子的长、宽、高。函数返回三个值,分别表示占用的空间总体积、每个小箱子的位置列表和旋转方案列表。

这个函数先将所有的箱子按照体积从大到小进行排序,然后枚举所有可能的箱子顺序并计算它们占用的空间总体积。对于每个顺序,它依次将箱子放入大箱子中,直到所有箱子都放置完毕。放置过程中,它按照一个规则选择位置和旋转方案,以使得放置后的占用空间最小化。最后,它返回空间总体积最小的顺序对应的位置和旋转方案列表。
如果我的回答解决了您的问题,请采纳!

以下内容引用CHATGPT:

这个问题可以用动态规划来解决。具体思路如下:

  1. 对所有箱子按照体积从大到小排序。
  2. 设定一个大箱子,大小为所有箱子体积之和。
  3. 对于每个箱子,遍历大箱子中的所有位置,分别计算将该箱子放置在该位置时,剩余空间的体积。
  4. 对于每个位置,记录最小的剩余空间体积和相应的放置方式。
  5. 最后,从最后一个箱子开始,根据记录的放置方式,递归地确定每个箱子的位置,并计算出整个布局的空间体积。

代码实现如下:

def pack_boxes(boxes, container):
    # 按照体积从大到小排序
    boxes = sorted(boxes, key=lambda b: b[0] * b[1] * b[2], reverse=True)
    # 大箱子大小为所有箱子体积之和
    container_size = sum([b[0] * b[1] * b[2] for b in boxes])
    # 初始化动态规划数组
    dp = {(0, 0, 0): (container_size, None)}
    # 遍历每个箱子
    for box in boxes:
        # 遍历大箱子中的所有位置
        for i in range(container[0] - box[0] + 1):
            for j in range(container[1] - box[1] + 1):
                for k in range(container[2] - box[2] + 1):
                    # 计算放置该箱子后剩余空间的体积
                    remaining_volume = dp.get((i, j, k), (container_size, None))[0] - box[0] * box[1] * box[2]
                    # 更新最小剩余空间体积和相应的放置方式
                    if remaining_volume < dp.get((i + box[0], j, k), (container_size, None))[0]:
                        dp[(i + box[0], j, k)] = (remaining_volume, (i, j, k))
                    if remaining_volume < dp.get((i, j + box[1], k), (container_size, None))[0]:
                        dp[(i, j + box[1], k)] = (remaining_volume, (i, j, k))
                    if remaining_volume < dp.get((i, j, k + box[2]), (container_size, None))[0]:
                        dp[(i, j, k + box[2])] = (remaining_volume, (i, j, k))
    # 从最后一个箱子开始,递归地确定每个箱子的位置,并计算出整个布局的空间体积
    layout = []
    position = (container[0], container[1], container[2])
    for box in reversed(boxes):
        layout.append((box, position))
        position = dp[position][1]
    layout.reverse()
    return layout, dp[(0, 0, 0)][0]

下面是一个例子:

boxes = [(3, 2, 1), (2, 2, 2), (1, 2, 3)]
container = (5, 5, 5)
layout, volume = pack_boxes(boxes, container)
print("Layout:")
for box, position in layout:
    print(f"{box} at {position}")
print(f"Total volume: {volume}")

输出结果为:

Layout:
(2, 2, 2) at (3, 3, 0)
(3, 2, 1) at (0, 3, 0)
(1, 2, 3) at (0, 0, 0)
Total volume: 90

这个例子中,三个箱子的体积分别为 6、8 和 6,总体积为 20。大箱子大小为 125,因此剩余空间体积最小为 125 - 20 = 105。布局的空间体积为 2x2x2 + 3x2x1 + 1x2x3 = 20,与原来的箱子体积相等。