You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FFD+模拟退火优化一维装箱问题无效,求改进方案

一维装箱问题:FFD+模拟退火优化失效的改进思路

我正在开发一维装箱问题的项目,目标是优化FirstFitDecreasing(FFD)算法的解,减少箱子使用数量。但当前结合Simulated Annealing(模拟退火)的实现无法提升解的质量,怀疑问题出在退火器或generate_neighbour函数的实现上,求改进思路。

测试实例说明

测试采用BPPlib库中的Falkenauer_u120_00.txt文件,每行存储一个物品重量,实例内容示例:

120
150
98
98
98
96
96
94
...

该实例的最优解可参考对应表格数据。

完整实现代码

import random
import math

def Le_Instancia(filename):
    try:
        with open(filename, 'r') as file:
            weight = [int(line.strip()) for line in file]
            return weight
    except FileNotFoundError:
        print("Arquivo não encontrado. (Verifique se o arquivo está na pasta correta)")
        return []

def FirstFitDecreasing(weight, n, bin_capacity):
    # Inicializa objetivo (número de bins)
    # Pior caso: n = objetivo
    objetivo = 0

    # Cria uma lista para armazenar o espaço restante nos bins
    bin_rem = [0]*n

    # Lista de listas para armazenar as bins utilizadas com os itens dentro
    bins_used = [[] for _ in range(n)]

    # Ordena os itens em ordem decrescente de peso
    weight.sort(reverse=True)

    # Adiciona os itens um por um
    for i in range(n):
        # Acha a primeira bin que pode acomodar weight[i]
        j = 0
        while j < objetivo:
            if bin_rem[j] >= weight[i]:
                bin_rem[j] = bin_rem[j] - weight[i]
                bins_used[j].append(weight[i])  # Adiciona o item na bin
                break
            j += 1

        # Caso não exista bin que possa acomodar weight[i], cria um novo bin
        if j == objetivo:
            bin_rem[objetivo] = bin_capacity - weight[i]
            bins_used[objetivo].append(weight[i])  # Adiciona o item na nova bin
            objetivo = objetivo + 1

    bins_used = bins_used[:objetivo]

    return objetivo, bins_used


def cost_function(bins_used):
    return len(bins_used)

def simulated_annealing(weight, n, bin_capacity, initial_temperature, cooling_rate, max_iterations):
    # First Fit Decreasing initial solution
    objective, bins_used = FirstFitDecreasing(weight, n, bin_capacity)
    best_objective = objective
    best_solution = bins_used

    # Initialize temperature
    temperature = initial_temperature

    for i in range(max_iterations):
        # Generate a neighbor solution by perturbing the current solution
        neighbor_bins_used = generate_neighbour(bins_used, bin_capacity)
        print(neighbor_bins_used)
        # Calculate the cost (number of bins) of the neighbor solution
        neighbor_objective = cost_function(neighbor_bins_used)
        #print(neighbor_objective)

        # Calculate the cost of the current solution
        current_objective = cost_function(bins_used)

        # Determine if the neighbor solution is accepted
        if neighbor_objective < current_objective:
            # Accept the neighbor solution if it's better
            bins_used = neighbor_bins_used
            objective = neighbor_objective

            # Update the best solution if necessary
            if neighbor_objective < best_objective:
                best_objective = neighbor_objective
                best_solution = neighbor_bins_used
        else:
            # Calculate the probability of accepting a worse solution
            probability = math.exp((current_objective - neighbor_objective) / temperature)

            # Accept the neighbor solution with a probability
            if random.random() < probability:
                bins_used = neighbor_bins_used
                objective = neighbor_objective
        
        # Cool down the temperature
        temperature *= cooling_rate
    #print(len(neighbor_bins_used))
    return best_objective, best_solution


def generate_neighbour(bins_used, bin_capacity):
    num_bins = len(bins_used)
    if num_bins < 2:
        return bins_used
    
    new_bins_used = [bin_content.copy() for bin_content in bins_used]

    # Choose two bins randomly
    bin1, bin2 = random.sample(range(num_bins), 2)

    # Choose a random number of items to swap
    num_items_to_swap = random.randint(1, min(len(new_bins_used[bin1]), len(new_bins_used[bin2])))

    # Swap items between bins
    for _ in range(num_items_to_swap):
        item1 = random.choice(new_bins_used[bin1])
        item2 = random.choice(new_bins_used[bin2])
        if sum(new_bins_used[bin1]) - item1 + item2 <= bin_capacity and sum(new_bins_used[bin2]) - item2 + item1 <= bin_capacity:
            new_bins_used[bin1].remove(item1)
            new_bins_used[bin2].remove(item2)
            new_bins_used[bin1].append(item2)
            new_bins_used[bin2].append(item1)

    # Remove empty bins
    new_bins_used = [bin_content for bin_content in new_bins_used if bin_content]

    return new_bins_used


filename = "/home/TEO/main/Falkenauer/Falkenauer_U/Falkenauer_u120_00.txt"  
weight = Le_Instancia(filename)
n = len(weight)
bin_capacity = 150 # Replace with the bin capacity
initial_temperature = 1000
cooling_rate = 0.95
max_iterations = 100000

best_objective, best_solution = simulated_annealing(weight, n, bin_capacity, initial_temperature, cooling_rate, max_iterations)

print("Best number of bins:", best_objective)
print("Best solution (bins used):", best_solution)

改进思路

一、generate_neighbour函数的核心问题与优化

现有交换策略的局限性:当前随机交换两个箱子里的物品,且仅保留满足容量约束的组合,这种方式很难产生能减少箱子数量的邻居解。FFD初始解本身已较优,单纯交换物品几乎无法腾出空箱子,多数操作属于无效迭代。

推荐三种改进的邻居生成策略:

  1. 单物品移动策略:随机选择一个物品,尝试将其移到其他能容纳它的箱子,而非局限于两两交换,更易产生可优化的解:
    def generate_neighbour(bins_used, bin_capacity):
        num_bins = len(bins_used)
        if num_bins < 1:
            return bins_used
        
        new_bins = [b.copy() for b in bins_used]
        # 随机选一个有物品的箱子和里面的物品
        src_bin_idx = random.randint(0, num_bins-1)
        while not new_bins[src_bin_idx]:
            src_bin_idx = random.randint(0, num_bins-1)
        item = random.choice(new_bins[src_bin_idx])
        
        # 尝试5次找合适的目标箱子
        for _ in range(5):
            dest_bin_idx = random.randint(0, num_bins-1)
            if dest_bin_idx != src_bin_idx and sum(new_bins[dest_bin_idx]) + item <= bin_capacity:
                new_bins[src_bin_idx].remove(item)
                new_bins[dest_bin_idx].append(item)
                break
        
        # 移除空箱子
        new_bins = [b for b in new_bins if b]
        return new_bins
    
  2. 多物品重组策略:随机选择两个箱子,尝试重新分配它们的物品以最大化空间利用率,或直接将一个箱子的物品合并到另一个(若容量允许)。
  3. 移除重插策略:随机选择若干物品移除,用BestFit规则重新插入到解中,更有可能产生更优布局。

二、模拟退火核心逻辑优化

  1. 参数调整:当前初始温度1000、冷却率0.95、10万次迭代的组合易导致过早收敛或探索不足。建议:
    • 初始温度调整为100-200,冷却率改为0.99(放慢冷却速度,增加探索时间)。
    • 改为温度降到阈值(如1e-3)时停止迭代,而非固定次数。
  2. 成本函数优化:仅用箱子数作为成本指标过于单一,可加入剩余空间惩罚项,优先选择空间利用率更高的解,为后续优化铺垫:
    def cost_function(bins_used, bin_capacity):
        num_bins = len(bins_used)
        total_remaining = sum(bin_capacity - sum(b) for b in bins_used)
        # 箱子数为核心指标,剩余空间为次要惩罚,系数可按需调整
        return num_bins + total_remaining / (bin_capacity * num_bins)
    
  3. 最优解跟踪优化:当邻居解的箱子数与当前最优相同时,若其空间利用率更高,也更新最优解,提升后续迭代的优化潜力。

三、其他辅助优化

  1. 初始解多样化:不要仅依赖FFD初始解,可同时生成FFD、BestFitDecreasing、随机布局等多个初始解,分别用模拟退火优化后取最优结果。
  2. 加入局部搜索:在模拟退火每一步后,对当前解做局部微调(如将每个物品移到最优容纳箱子),进一步提升解的质量。
  3. 避免无效操作:在邻居生成时,跳过不会改变解的操作(如交换同重量物品),减少无效迭代。

内容的提问来源于stack exchange,提问作者Matheus Soares

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 05:38:09