在制造业和材料加工中,常常需要将固定长度的原材料切割成不同长度以满足客户订单。 
例如,一根长度为 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 工具。 
阅读原文:原文链接