在制造业和材料加工中,常常需要将固定长度的原材料切割成不同长度以满足客户订单。
例如,一根长度为 1500mm 的金属棒,需要切成 200mm、500mm 和 700mm 的若干段,如何切割才能满足需求并尽量减少浪费?
这就是经典的 下料问题(Cutting Stock Problem) 。
本文将深入介绍下料问题的数学建模、两种主流启发式算法(FFD、BFD),并提供一个 C# 测试文件驱动实现方案 ,方便在实际生产中直接应用。
一、问题定义与数学模型 问题描述 • 目标: 换句话说,我们希望最小化剩余长度之和:
其中 为使用的原材料数量, 为第 根原材料的切割长度集合。
• 为每根原材料设计切割方案,使得所有需求都得到满足,并最小化浪费。 数学模型(整数线性规划) 设二进制变量:
约束条件:
1、每个零件必须放置:
2、每根原料长度不超过限制:
目标函数:
该问题属于 NP-hard,因此对于大规模数据,启发式算法通常比精确求解更实用。
二、主流启发式算法 1. FFD(First-Fit Decreasing) 算法步骤:
复杂度分析:
排序: ,放置操作最坏情况 ,总复杂度 。
2. BFD(Best-Fit Decreasing) 算法步骤:
2. 每次放置零件时,选择剩余长度最小但足够容纳的原料。 复杂度分析:
同样为 ,通常比 FFD 更节省原料浪费。
三、测试驱动实现 数据模型 public class JobItem { public string Name { get ; set ; } public int Length { get ; set ; } public int Quantity { get ; set ; } } public class Job { public int MaxLength { get ; set ; } public int CutWidth { get ; set ; } public List<JobItem> TargetSizes { get ; set ; } } public class Stock { public List<JobItem> Cuts { get ; set ; } = new List<JobItem>(); public int MaxLength { get ; set ; } public int CutWidth { get ; set ; } public int UsedLength => Cuts.Sum(c => c.Length) + (Cuts.Count - 1 ) * CutWidth; public int RemainingLength => MaxLength - UsedLength; }
FFD 算法实现 public static List<Stock> SolveFFD ( Job job ) { var sizes = job.TargetSizes .SelectMany(s => Enumerable.Repeat(s, s.Quantity)) .OrderByDescending(s => s.Length) .ToList(); List<Stock> stocks = new List<Stock>(); foreach ( var item in sizes) { bool placed = false ; foreach ( var stock in stocks) { if (stock.RemainingLength >= item.Length) { stock.Cuts.Add(item); placed = true ; break ; } } if (!placed) { stocks.Add( new Stock { MaxLength = job.MaxLength, CutWidth = job.CutWidth, Cuts = new List<JobItem> { item } }); } } return stocks; }
BFD 算法实现 public static List<Stock> SolveBFD ( Job job ) { var sizes = job.TargetSizes .SelectMany(s => Enumerable.Repeat(s, s.Quantity)) .OrderByDescending(s => s.Length) .ToList(); List<Stock> stocks = new List<Stock>(); foreach ( var item in sizes) { Stock bestStock = null ; int minRem = int .MaxValue; foreach ( var stock in stocks) { if (stock.RemainingLength >= item.Length && stock.RemainingLength - item.Length < minRem) { minRem = stock.RemainingLength - item.Length; bestStock = stock; } } if (bestStock != null ) { bestStock.Cuts.Add(item); } else { stocks.Add( new Stock { MaxLength = job.MaxLength, CutWidth = job.CutWidth, Cuts = new List<JobItem> { item } }); } } return stocks; }
文件驱动主程序 using System.Text.Json; class Program { static void Main () { string inputPath = "job.json" ; string outputFFD = "output_ffd.json" ; string outputBFD = "output_bfd.json" ; Job job = JsonSerializer.Deserialize<Job>(File.ReadAllText(inputPath)); var ffdStocks = SolveFFD(job); var bfdStocks = SolveBFD(job); File.WriteAllText(outputFFD, JsonSerializer.Serialize(ffdStocks, new JsonSerializerOptions { WriteIndented = true })); File.WriteAllText(outputBFD, JsonSerializer.Serialize(bfdStocks, new JsonSerializerOptions { WriteIndented = true })); Console.WriteLine( "FFD 和 BFD 求解完成,输出已生成。" ); } }
输入示例 ( job.json
) { "MaxLength" : 1500 , "CutWidth" : 50 , "TargetSizes" : [ { "Name" : "片段1" , "Length" : 200 , "Quantity" : 8 } , { "Name" : "片段2" , "Length" : 500 , "Quantity" : 4 } , { "Name" : "片段3" , "Length" : 700 , "Quantity" : 2 } ] }
输出 四、总结与扩展 1. 核心思想 :通过启发式算法快速生成合理的切割方案,节省原料。 3. 适用范围 :中小规模问题完全可用;大规模或特殊约束可以引入列生成、整数规划优化。 4. C# 测试文件驱动实现 :便于生产现场直接读取订单数据文件生成切割方案,同时可扩展为 Web API 或 GUI 工具。
阅读原文:原文链接
该文章在 2025/8/25 13:22:20 编辑过