CSharp: Breadth First Search Algorithm and Depth First Search Algorithm

项目结构:

cs 复制代码
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : AppConfig.cs
 
 */
 
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.BreadthFirst.Config
{
    /// <summary>
    /// 全局应用配置
    /// </summary>
    public static class AppConfig
    {
        public static string LogSavePath = "./logs";
        public static string LogFileName = "jewelry_packer.log";
        public static bool LogConsoleEnable = true;
        public static bool LogFileEnable = true;
 
        public static int MaxWorkerThread = 8;
        public static int MaxSearchState = 25000;
 
        public static void InitLogDir()
        {
            if (!Directory.Exists(LogSavePath))
            {
                Directory.CreateDirectory(LogSavePath);
            }
        }
    }
}
 
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : Enums.cs
 
 */
 
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.BreadthFirst.Core
{
    /// <summary>
    /// 配载算法策略
    /// </summary>
    public enum LoadStrategyType
    {
        BFS,
        DFS,
        ASTAR
    }
 
    /// <summary>
    /// 返回码实体
    /// </summary>
    public record ResultCode(int Code, string Msg)
    {
        public static readonly ResultCode Success = new(0, "成功");
        public static readonly ResultCode ParamError = new(400, "参数非法");
        public static readonly ResultCode NoAvailablePackage = new(401, "无可用包装规格");
        public static readonly ResultCode ConstraintFail = new(402, "装箱约束冲突,无法装载");
        public static readonly ResultCode SearchOverLimit = new(501, "搜索状态超限,未找到方案");
        public static readonly ResultCode NoAnyLoadPlan = new(502, "无法生成配载方案");
    }
}
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : Models.cs
 
 */
 
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.BreadthFirst.Core
{
    /// <summary>
    /// 单类首饰明细
    /// </summary>
    public class JewelryItem
    {
        public string JewelryCode { get; set; } = string.Empty;
        public double SingleVolume { get; set; }
        public int Quantity { get; set; }
 
        public double GetTotalVolume()
        {
            return SingleVolume * Quantity;
        }
    }
 
    /// <summary>
    /// 装箱全局约束
    /// </summary>
    public class PackConstraint
    {
        public double BufferCoeff { get; set; }
        public int SingleBoxMaxQty { get; set; }
        public bool ForbidMixJewelry { get; set; }
        public bool ForbidSplitJewelry { get; set; }
    }
 
    /// <summary>
    /// 包装规格
    /// </summary>
    public class PackageSpec
    {
        public string SpecCode { get; set; } = string.Empty;
        public double Volume { get; set; }
        public int StockQty { get; set; }
        public double UnitCost { get; set; }
        public int PriorityWeight { get; set; }
 
        public double GetValidVolume(double coeff)
        {
            return Volume * coeff;
        }
    }
 
    /// <summary>
    /// 配载查询入参
    /// </summary>
    public class PackQueryCondition
    {
        public List<JewelryItem>? JewelryList { get; set; }
        public PackConstraint? Constraint { get; set; }
        public double TotalMaterialVol { get; set; }
        public List<PackageSpec>? PackageSpecList { get; set; }
        public LoadStrategyType StrategyType { get; set; }
        public bool AllowOverLoad { get; set; }
    }
 
    /// <summary>
    /// 使用的包装记录
    /// </summary>
    public class SingleUsePackage
    {
        public string SpecCode { get; set; } = string.Empty;
        public double Volume { get; set; }
        public double ValidVolume { get; set; }
        public int UseCount { get; set; }
        public string BelongJewelryCode { get; set; } = string.Empty;
    }
 
    /// <summary>
    /// 配载结果
    /// </summary>
    public class PackResult
    {
        public int Code { get; set; }
        public string Msg { get; set; } = string.Empty;
        public List<SingleUsePackage> UsePackageList { get; set; } = new();
        public double RemainMaterialVol { get; set; }
        public Dictionary<string, int> RemainPackageStock { get; set; } = new();
        public int TotalUsePackageCnt { get; set; }
        public int SearchStateCount { get; set; }
        public bool FullLoadFlag { get; set; }
        public double JewelryTotalCalcVol { get; set; }
        public double TotalCost { get; set; }
    }
}
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : Logger.cs
 
 */
 
using CSharpAlgorithms.BreadthFirst.Config;
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.BreadthFirst.Core
{
    /// <summary>
    /// 全局日志单例,同时输出控制台+日志文件
    /// </summary>
    public class AppLogger
    {
        private static AppLogger? _instance;
        private static readonly object _lock = new();
        private readonly string _logPath;
 
        private AppLogger()
        {
            AppConfig.InitLogDir();
            _logPath = Path.Combine(AppConfig.LogSavePath, AppConfig.LogFileName);
        }
 
        public static AppLogger GetInstance()
        {
            if (_instance == null)
            {
                lock (_lock)
                {
                    _instance ??= new AppLogger();
                }
            }
            return _instance;
        }
 
        private void Write(string level, string message)
        {
            var time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            var line = $"[{time}] [{level}] {message}";
 
            if (AppConfig.LogConsoleEnable)
            {
                Console.WriteLine(line);
            }
 
            if (AppConfig.LogFileEnable)
            {
                File.AppendAllText(_logPath, line + Environment.NewLine, Encoding.UTF8);
            }
        }
 
        public void Info(string msg) => Write("INFO", msg);
        public void Warn(string msg) => Write("WARN", msg);
        public void Error(string msg) => Write("ERROR", msg);
    }
}
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : IStrategy.cs
 
 */
 
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.BreadthFirst.Core
{
    /// <summary>
    /// 配载策略统一接口
    /// </summary>
    public interface IPackStrategy
    {
        PackResult Execute(PackQueryCondition condition);
    }
}
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : BfsStrategy.cs
 
 */
using CSharpAlgorithms.BreadthFirst.Config;
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.BreadthFirst.Core
{
    public class BfsStrategy : IPackStrategy
    {
        private readonly AppLogger _logger = AppLogger.GetInstance();
 
        private record BfsNode(
            double LoadVol,
            Dictionary<string, int> UsedMap,
            double TotalCost,
            int WeightSum,
            int PackCount);
 
        public PackResult Execute(PackQueryCondition condition)
        {
            double totalMatVol = condition.TotalMaterialVol;
            var specList = condition.PackageSpecList!;
            bool allowOver = condition.AllowOverLoad;
            int maxState = AppConfig.MaxSearchState;
            double bufferCoeff = condition.Constraint?.BufferCoeff ?? 1.0;
 
            if (totalMatVol <= 0)
            {
                return new PackResult
                {
                    Code = ResultCode.ParamError.Code,
                    Msg = ResultCode.ParamError.Msg,
                    RemainMaterialVol = totalMatVol
                };
            }
            if (specList.Count == 0)
            {
                return new PackResult
                {
                    Code = ResultCode.NoAvailablePackage.Code,
                    Msg = ResultCode.NoAvailablePackage.Msg,
                    RemainMaterialVol = totalMatVol
                };
            }
 
            var initStock = new Dictionary<string, int>();
            var specVolMap = new Dictionary<string, double>();
            var specNominalVolMap = new Dictionary<string, double>();
            var specCostMap = new Dictionary<string, double>();
            var specWeightMap = new Dictionary<string, int>();
 
            foreach (var item in specList)
            {
                initStock[item.SpecCode] = item.StockQty;
                specVolMap[item.SpecCode] = item.Volume * bufferCoeff;
                specNominalVolMap[item.SpecCode] = item.Volume;
                specCostMap[item.SpecCode] = item.UnitCost;
                specWeightMap[item.SpecCode] = item.PriorityWeight;
            }
 
            var startUsed = new Dictionary<string, int>();
            foreach (var k in initStock.Keys) startUsed[k] = 0;
 
            var visited = new HashSet<string>();
            int visitedCount = 1;
            var queue = new Queue<BfsNode>();
            var startNode = new BfsNode(0, CloneDict(startUsed), 0, 0, 0);
            queue.Enqueue(startNode);
            visited.Add(GetStateKey(startUsed));
 
            BfsNode? bestNode = null;
            int? minPackCnt = null;
 
            while (queue.Count > 0)
            {
                var node = queue.Dequeue();
 
                if (node.LoadVol >= totalMatVol)
                {
                    if (bestNode == null)
                    {
                        bestNode = node;
                        minPackCnt = node.PackCount;
                    }
                    else
                    {
                        if (node.PackCount == minPackCnt)
                        {
                            bool better = node.WeightSum > bestNode.WeightSum
                                          || (node.WeightSum == bestNode.WeightSum && node.TotalCost < bestNode.TotalCost);
                            if (better)
                                bestNode = node;
                        }
                    }
                    continue;
                }
 
                if (visitedCount >= maxState)
                {
                    _logger.Warn($"搜索达到最大状态限制 {maxState},终止搜索");
                    break;
                }
 
                foreach (var kv in initStock)
                {
                    string specCode = kv.Key;
                    int stockNum = kv.Value;
                    int usedNum = node.UsedMap[specCode];
                    if (usedNum >= stockNum) continue;
 
                    double packVol = specVolMap[specCode];
                    if (packVol <= 0) continue;
                    double newLoad = node.LoadVol + packVol;
                    if (!allowOver && newLoad > totalMatVol * 1.5) continue;
 
                    var newUsed = CloneDict(node.UsedMap);
                    newUsed[specCode]++;
                    string key = GetStateKey(newUsed);
                    if (visited.Contains(key)) continue;
 
                    visited.Add(key);
                    visitedCount++;
                    var newNode = new BfsNode(
                        newLoad,
                        newUsed,
                        node.TotalCost + specCostMap[specCode],
                        node.WeightSum + specWeightMap[specCode],
                        node.PackCount + 1);
                    queue.Enqueue(newNode);
                }
            }
 
            if (bestNode == null)
            {
                return new PackResult
                {
                    Code = ResultCode.NoAnyLoadPlan.Code,
                    Msg = ResultCode.NoAnyLoadPlan.Msg,
                    RemainMaterialVol = totalMatVol,
                    RemainPackageStock = CloneDict(initStock),
                    SearchStateCount = visitedCount
                };
            }
 
            var best = bestNode;
            double remainMat = totalMatVol - best.LoadVol;
            bool fullFlag = remainMat <= 0.001;
            var useList = new List<SingleUsePackage>();
            var remainStock = new Dictionary<string, int>();
 
            foreach (var kv in initStock)
            {
                string code = kv.Key;
                int totalStock = kv.Value;
                int useCnt = best.UsedMap[code];
                remainStock[code] = totalStock - useCnt;
                if (useCnt > 0)
                {
                    useList.Add(new SingleUsePackage
                        {
                            SpecCode = code,
                            Volume = specNominalVolMap[code],
                            ValidVolume = specVolMap[code],
                            UseCount = useCnt
                        });
                }
            }
 
            return new PackResult
            {
                Code = ResultCode.Success.Code,
                Msg = ResultCode.Success.Msg,
                UsePackageList = useList,
                RemainMaterialVol = remainMat,
                RemainPackageStock = remainStock,
                TotalUsePackageCnt = best.PackCount,
                SearchStateCount = visitedCount,
                FullLoadFlag = fullFlag,
                TotalCost = best.TotalCost
            };
        }
 
        private static Dictionary<string, int> CloneDict(Dictionary<string, int> src)
        {
            return new Dictionary<string, int>(src);
        }
 
        private static string GetStateKey(Dictionary<string, int> dict)
        {
            return string.Join(",", dict.OrderBy(x => x.Key).Select(x => $"{x.Key}:{x.Value}"));
        }
    }
}
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : DfsStrategy.cs
 
 */
 
using CSharpAlgorithms.BreadthFirst.Config;
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.BreadthFirst.Core
{
    public class DfsStrategy : IPackStrategy
    {
        private readonly AppLogger _logger = AppLogger.GetInstance();
        private double _totalMatVol;
        private bool _allowOver;
        private int _maxState;
        private Dictionary<string, double> _specVolMap = new();
        private Dictionary<string, double> _specNominalVolMap = new();
        private Dictionary<string, double> _specCostMap = new();
        private Dictionary<string, int> _initStock = new();
        private DfsNode? _bestNode;
        private HashSet<string> _visited = new();
        private int _visitedCnt;
 
        private record DfsNode(double LoadVol, Dictionary<string, int> UsedMap, double TotalCost, int PackCnt);
 
        public PackResult Execute(PackQueryCondition condition)
        {
            _totalMatVol = condition.TotalMaterialVol;
            _allowOver = condition.AllowOverLoad;
            _maxState = AppConfig.MaxSearchState;
            var specList = condition.PackageSpecList!;
            double bufferCoeff = condition.Constraint?.BufferCoeff ?? 1.0;
 
            if (_totalMatVol <= 0)
            {
                return new PackResult { Code = ResultCode.ParamError.Code, Msg = ResultCode.ParamError.Msg };
            }
            if (specList.Count == 0)
            {
                return new PackResult { Code = ResultCode.NoAvailablePackage.Code, Msg = ResultCode.NoAvailablePackage.Msg };
            }
 
            _initStock.Clear();
            _specVolMap.Clear();
            _specNominalVolMap.Clear();
            _specCostMap.Clear();
            foreach (var item in specList)
            {
                _initStock[item.SpecCode] = item.StockQty;
                _specVolMap[item.SpecCode] = item.Volume * bufferCoeff;
                _specNominalVolMap[item.SpecCode] = item.Volume;
                _specCostMap[item.SpecCode] = item.UnitCost;
            }
 
            var startUsed = new Dictionary<string, int>();
            foreach (var k in _initStock.Keys) startUsed[k] = 0;
            _visited.Clear();
            _visitedCnt = 0;
            _bestNode = null;
 
            Dfs(0, startUsed, 0, 0);
 
            if (_bestNode == null)
            {
                return new PackResult
                {
                    Code = ResultCode.NoAnyLoadPlan.Code,
                    Msg = ResultCode.NoAnyLoadPlan.Msg,
                    RemainMaterialVol = _totalMatVol,
                    RemainPackageStock = new Dictionary<string, int>(_initStock),
                    SearchStateCount = _visitedCnt
                };
            }
 
            var best = _bestNode;
            double remainMat = _totalMatVol - best.LoadVol;
            bool fullFlag = remainMat <= 0.001;
            var useList = new List<SingleUsePackage>();
            var remainStock = new Dictionary<string, int>();
 
            foreach (var kv in _initStock)
            {
                string code = kv.Key;
                int totalStock = kv.Value;
                int useCnt = best.UsedMap[code];
                remainStock[code] = totalStock - useCnt;
                if (useCnt > 0)
                {
                    useList.Add(new SingleUsePackage
                    {
                        SpecCode = code,
                        Volume = _specNominalVolMap[code],
                        ValidVolume = _specVolMap[code],
                        UseCount = useCnt
                    });
                }
            }
 
            return new PackResult
            {
                Code = ResultCode.Success.Code,
                Msg = ResultCode.Success.Msg,
                UsePackageList = useList,
                RemainMaterialVol = remainMat,
                RemainPackageStock = remainStock,
                TotalUsePackageCnt = best.PackCnt,
                SearchStateCount = _visitedCnt,
                FullLoadFlag = fullFlag,
                TotalCost = best.TotalCost
            };
        }
 
        private void Dfs(double loadVol, Dictionary<string, int> usedMap, double cost, int packCnt)
        {
            if (_bestNode != null) return;
            if (loadVol >= _totalMatVol)
            {
                _bestNode = new DfsNode(loadVol, new Dictionary<string, int>(usedMap), cost, packCnt);
                return;
            }
 
            string key = GetStateKey(usedMap);
            if (_visited.Contains(key)) return;
            if (_visitedCnt >= _maxState) return;
 
            _visited.Add(key);
            _visitedCnt++;
 
            foreach (var kv in _initStock)
            {
                string specCode = kv.Key;
                int stockNum = kv.Value;
                int usedNum = usedMap[specCode];
                if (usedNum >= stockNum) continue;
 
                double pv = _specVolMap[specCode];
                if (pv <= 0) continue;
                double newLoad = loadVol + pv;
                if (!_allowOver && newLoad > _totalMatVol * 1.5) continue;
 
                usedMap[specCode]++;
                Dfs(newLoad, usedMap, cost + _specCostMap[specCode], packCnt + 1);
                usedMap[specCode]--;
            }
        }
 
        private static string GetStateKey(Dictionary<string, int> dict)
        {
            return string.Join(",", dict.OrderBy(x => x.Key).Select(x => $"{x.Key}:{x.Value}"));
        }
    }
}
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : AStarStrategy.cs
 
 */
 
using CSharpAlgorithms.BreadthFirst.Config;
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.BreadthFirst.Core
{
    public class AStarStrategy : IPackStrategy
    {
        private readonly AppLogger _logger = AppLogger.GetInstance();
 
        private record AStarItem(double F, int G, double LoadVol, double Cost, Dictionary<string, int> UsedMap);
 
        public PackResult Execute(PackQueryCondition condition)
        {
            double totalMatVol = condition.TotalMaterialVol;
            var specList = condition.PackageSpecList!;
            bool allowOver = condition.AllowOverLoad;
            int maxState = AppConfig.MaxSearchState;
            double bufferCoeff = condition.Constraint?.BufferCoeff ?? 1.0;
 
            if (totalMatVol <= 0)
            {
                return new PackResult { Code = ResultCode.ParamError.Code, Msg = ResultCode.ParamError.Msg };
            }
            if (specList.Count == 0)
            {
                return new PackResult { Code = ResultCode.NoAvailablePackage.Code, Msg = ResultCode.NoAvailablePackage.Msg };
            }
 
            var initStock = new Dictionary<string, int>();
            var specVolMap = new Dictionary<string, double>();
            var specNominalVolMap = new Dictionary<string, double>();
            var specCostMap = new Dictionary<string, double>();
            var specWeightMap = new Dictionary<string, int>();
 
            foreach (var item in specList)
            {
                initStock[item.SpecCode] = item.StockQty;
                specVolMap[item.SpecCode] = item.Volume * bufferCoeff;
                specNominalVolMap[item.SpecCode] = item.Volume;
                specCostMap[item.SpecCode] = item.UnitCost;
                specWeightMap[item.SpecCode] = item.PriorityWeight;
            }
 
            var startUsed = new Dictionary<string, int>();
            foreach (var k in initStock.Keys) startUsed[k] = 0;
 
            var visited = new HashSet<string>();
            int visitedCount = 1;
            var heap = new PriorityQueue<AStarItem, double>();
            heap.Enqueue(new AStarItem(0, 0, 0, 0, new Dictionary<string, int>(startUsed)), 0);
 
            AStarItem? best = null;
 
            while (heap.Count > 0)
            {
                heap.TryDequeue(out var top, out _);
                string key = GetStateKey(top.UsedMap);
                if (visited.Contains(key)) continue;
                visited.Add(key);
                visitedCount++;
 
                if (top.LoadVol >= totalMatVol)
                {
                    best = top;
                    break;
                }
                if (visitedCount >= maxState)
                {
                    _logger.Warn("A*搜索达到最大状态限制");
                    break;
                }
 
                foreach (var kv in initStock)
                {
                    string specCode = kv.Key;
                    int stockNum = kv.Value;
                    int usedNum = top.UsedMap[specCode];
                    if (usedNum >= stockNum) continue;
 
                    double pv = specVolMap[specCode];
                    if (pv <= 0) continue;
                    double newLoad = top.LoadVol + pv;
                    if (!allowOver && newLoad > totalMatVol * 1.5) continue;
 
                    int newG = top.G + 1;
                    double newCost = top.Cost + specCostMap[specCode];
                    double h = Math.Max(totalMatVol - newLoad, 0) * 0.01 + newCost * 0.15 - specWeightMap[specCode] * 0.005;
                    double newF = newG + h;
 
                    var newUsed = new Dictionary<string, int>(top.UsedMap);
                    newUsed[specCode]++;
                    heap.Enqueue(new AStarItem(newF, newG, newLoad, newCost, newUsed), newF);
                }
            }
 
            if (best == null)
            {
                return new PackResult
                {
                    Code = ResultCode.NoAnyLoadPlan.Code,
                    Msg = ResultCode.NoAnyLoadPlan.Msg,
                    RemainMaterialVol = totalMatVol,
                    RemainPackageStock = new Dictionary<string, int>(initStock),
                    SearchStateCount = visitedCount
                };
            }
 
            double remainMat = totalMatVol - best.LoadVol;
            bool fullFlag = remainMat <= 0.001;
            var useList = new List<SingleUsePackage>();
            var remainStock = new Dictionary<string, int>();
 
            foreach (var kv in initStock)
            {
                string code = kv.Key;
                int totalStock = kv.Value;
                int useCnt = best.UsedMap[code];
                remainStock[code] = totalStock - useCnt;
                if (useCnt > 0)
                {
                    useList.Add(new SingleUsePackage
                    {
                        SpecCode = code,
                        Volume = specNominalVolMap[code],
                        ValidVolume = specVolMap[code],
                        UseCount = useCnt
                    });
                }
            }
 
            return new PackResult
            {
                Code = ResultCode.Success.Code,
                Msg = ResultCode.Success.Msg,
                UsePackageList = useList,
                RemainMaterialVol = remainMat,
                RemainPackageStock = remainStock,
                TotalUsePackageCnt = best.G,
                SearchStateCount = visitedCount,
                FullLoadFlag = fullFlag,
                TotalCost = best.Cost
            };
        }
 
        private static string GetStateKey(Dictionary<string, int> dict)
        {
            return string.Join(",", dict.OrderBy(x => x.Key).Select(x => $"{x.Key}:{x.Value}"));
        }
    }
}
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : SafeTaskPool.cs
 
 */
 
using CSharpAlgorithms.BreadthFirst.Config;
using CSharpAlgorithms.BreadthFirst.Core;
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.BreadthFirst.Concurrency
{
    /// <summary>
    /// 线程安全任务池
    /// </summary>
    public class SafeTaskPool
    {
        private static SafeTaskPool? _instance;
        private static readonly object _lockObj = new();
        private readonly SemaphoreSlim _sem;
 
        private SafeTaskPool(int maxWorker)
        {
            _sem = new SemaphoreSlim(maxWorker, maxWorker);
        }
 
        public static SafeTaskPool GetInstance()
        {
            if (_instance == null)
            {
                lock (_lockObj)
                {
                    _instance ??= new SafeTaskPool(AppConfig.MaxWorkerThread);
                }
            }
            return _instance;
        }
 
        public async Task<PackResult> SubmitAsync(IPackStrategy strategy, PackQueryCondition condition)
        {
            await _sem.WaitAsync();
            try
            {
                return await Task.Run(() => strategy.Execute(condition));
            }
            finally
            {
                _sem.Release();
            }
        }
 
        public PackResult Submit(IPackStrategy strategy, PackQueryCondition condition)
        {
            return SubmitAsync(strategy, condition).Result;
        }
    }
}
 
 
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : PackageService.cs
 
 */
 
using CSharpAlgorithms.BreadthFirst.Concurrency;
using CSharpAlgorithms.BreadthFirst.Core;
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.BreadthFirst.Service
{
 
    /// <summary>
    ///
    /// </summary>
    public class JewelryPackageService
    {
        private readonly AppLogger _logger = AppLogger.GetInstance();
 
        /// <summary>
        /// 汇总首饰总体积
        /// </summary>
        public double CalcJewelryTotalVolume(List<JewelryItem> list)
        {
            double total = 0;
            _logger.Info("=====开始计算各类首饰物料体积明细=====");
            foreach (var item in list)
            {
                double vol = item.GetTotalVolume();
                _logger.Info($"货号:{item.JewelryCode} | 单件体积:{item.SingleVolume} cm³ | 数量:{item.Quantity} | 品类合计:{vol:F2} cm³");
                total += vol;
            }
            _logger.Info($"【汇总完成】所有首饰物料理论总体积 = {total:F2} cm³");
            return total;
        }
 
        /// <summary>
        /// 禁止拆盒校验:检查是否存在一种包装规格,可用多个同规格盒子完整容纳整个品类
        /// </summary>
        public bool CheckForbidSplitConstraint(JewelryItem item, List<PackageSpec> packages, double coeff)
        {
            double totalV = item.GetTotalVolume();
            double singleV = item.SingleVolume;
            foreach (var pkg in packages)
            {
                double validVol = pkg.GetValidVolume(coeff);
                if (singleV <= validVol && pkg.StockQty * validVol >= totalV)
                    return true;
            }
            _logger.Error($"【约束冲突】首饰{item.JewelryCode}总体积{totalV:F2},无盒子规格可完整容纳(单件体积{singleV:F2}),禁止拆盒模式无解");
            return false;
        }
 
        /// <summary>
        /// 禁止混装:拆分为多个独立配载任务
        /// </summary>
        public List<PackQueryCondition> SplitByJewelry(PackQueryCondition origin)
        {
            var cons = origin.Constraint;
            if (cons == null || !cons.ForbidMixJewelry)
            {
                return new List<PackQueryCondition> { origin };
            }
 
            _logger.Info("【启用禁止混装规则】各首饰品类独立计算包装方案");
            var subList = new List<PackQueryCondition>();
            foreach (var j in origin.JewelryList!)
            {
                if (cons.ForbidSplitJewelry)
                {
                    if (!CheckForbidSplitConstraint(j, origin.PackageSpecList!, cons.BufferCoeff))
                        continue;
                }
 
                if (cons.SingleBoxMaxQty == 1)
                {
                    for (int i = 0; i < j.Quantity; i++)
                    {
                        var singleItem = new JewelryItem
                        {
                            JewelryCode = j.JewelryCode,
                            SingleVolume = j.SingleVolume,
                            Quantity = 1
                        };
                        subList.Add(new PackQueryCondition
                        {
                            JewelryList = new List<JewelryItem> { singleItem },
                            Constraint = cons,
                            TotalMaterialVol = j.SingleVolume,
                            PackageSpecList = origin.PackageSpecList,
                            StrategyType = origin.StrategyType,
                            AllowOverLoad = origin.AllowOverLoad
                        });
                    }
                }
                else
                {
                    subList.Add(new PackQueryCondition
                    {
                        JewelryList = new List<JewelryItem> { j },
                        Constraint = cons,
                        TotalMaterialVol = j.GetTotalVolume(),
                        PackageSpecList = origin.PackageSpecList,
                        StrategyType = origin.StrategyType,
                        AllowOverLoad = origin.AllowOverLoad
                    });
                }
            }
            return subList;
        }
 
        public IPackStrategy CreateStrategy(LoadStrategyType type)
        {
            return type switch
            {
                LoadStrategyType.BFS => new BfsStrategy(),
                LoadStrategyType.DFS => new DfsStrategy(),
                LoadStrategyType.ASTAR => new AStarStrategy(),
                _ => throw new NotSupportedException($"不支持策略 {type}")
            };
        }
 
        public PackResult CalcSinglePlan(PackQueryCondition condition)
        {
            if (condition.Constraint != null)
            {
                var c = condition.Constraint;
                _logger.Info("=======装箱约束参数=======");
                _logger.Info($"缓冲空隙系数:{c.BufferCoeff}");
                _logger.Info($"单盒最大首饰件数:{c.SingleBoxMaxQty}");
                _logger.Info($"禁止不同品类混装:{c.ForbidMixJewelry}");
                _logger.Info($"禁止同品类拆盒:{c.ForbidSplitJewelry}");
            }
 
            double calcVol = 0;
            if (condition.JewelryList != null && condition.JewelryList.Count > 0)
            {
                calcVol = CalcJewelryTotalVolume(condition.JewelryList);
                condition.TotalMaterialVol = calcVol;
            }
 
            var subConditions = SplitByJewelry(condition);
            var strategy = CreateStrategy(condition.StrategyType);
            var pool = SafeTaskPool.GetInstance();
            var subResults = new List<PackResult>();
 
            foreach (var subCond in subConditions)
            {
                subResults.Add(pool.Submit(strategy, subCond));
            }
 
            var mergeResult = MergeSubResult(subResults, condition);
            mergeResult.JewelryTotalCalcVol = calcVol;
            PrintResultLog(mergeResult);
            return mergeResult;
        }
 
        /// <summary>
        /// 合并多个品类独立配载结果
        /// </summary>
        public PackResult MergeSubResult(List<PackResult> subList, PackQueryCondition origin)
        {
            var usePackages = new List<SingleUsePackage>();
            var remainStock = new Dictionary<string, int>();
            double remainMatSum = 0;
            int totalSearch = 0;
            double totalCostSum = 0;
            bool allSuccess = true;
 
            foreach (var pkg in origin.PackageSpecList!)
                remainStock[pkg.SpecCode] = pkg.StockQty;
 
            foreach (var sub in subList)
            {
                if (sub.Code != ResultCode.Success.Code) allSuccess = false;
                remainMatSum += sub.RemainMaterialVol;
                totalSearch += sub.SearchStateCount;
                totalCostSum += sub.TotalCost;
                usePackages.AddRange(sub.UsePackageList);
 
                foreach (var pu in sub.UsePackageList)
                    remainStock[pu.SpecCode] -= pu.UseCount;
            }
 
            int totalPackCnt = usePackages.Sum(x => x.UseCount);
            bool fullFlag = remainMatSum <= 0.01;
 
            if (!allSuccess)
            {
                return new PackResult
                {
                    Code = ResultCode.NoAnyLoadPlan.Code,
                    Msg = "部分品类无法找到可行包装方案",
                    UsePackageList = usePackages,
                    RemainMaterialVol = Math.Round(remainMatSum, 2),
                    RemainPackageStock = remainStock,
                    TotalUsePackageCnt = totalPackCnt,
                    SearchStateCount = totalSearch,
                    FullLoadFlag = fullFlag,
                    TotalCost = totalCostSum
                };
            }
 
            return new PackResult
            {
                Code = ResultCode.Success.Code,
                Msg = ResultCode.Success.Msg,
                UsePackageList = usePackages,
                RemainMaterialVol = Math.Round(remainMatSum, 2),
                RemainPackageStock = remainStock,
                TotalUsePackageCnt = totalPackCnt,
                SearchStateCount = totalSearch,
                FullLoadFlag = fullFlag,
                TotalCost = totalCostSum
            };
        }
 
        public void PrintResultLog(PackResult res)
        {
            _logger.Info("=====================配载结果=====================");
            _logger.Info($"首饰明细汇总理论总体积:{res.JewelryTotalCalcVol:F2} cm³");
            _logger.Info($"返回码:{res.Code} 消息:{res.Msg}");
            _logger.Info($"搜索遍历状态总数:{res.SearchStateCount}");
            _logger.Info($"总共使用包装数量:{res.TotalUsePackageCnt}");
            _logger.Info($"方案预估总成本:{res.TotalCost:F2}");
            _logger.Info($"物料是否完全装满:{res.FullLoadFlag}");
            _logger.Info($"剩余未装入物料体积:{res.RemainMaterialVol:F2} cm³");
            _logger.Info("本次选用包装清单:");
            foreach (var item in res.UsePackageList)
            {
                _logger.Info($"    编码:{item.SpecCode} 标称容积:{item.Volume:F2}cm³ 有效容积:{item.ValidVolume:F2}cm³ 使用数量:{item.UseCount} 装载品类:{item.BelongJewelryCode}");
            }
            _logger.Info("包装库存剩余情况:");
            foreach (var kv in res.RemainPackageStock)
            {
                _logger.Info($"    {kv.Key} 剩余库存:{kv.Value}");
            }
            _logger.Info("==================================================");
        }
    }
}

调用;

cs 复制代码
/*
 # encoding: utf-8
# 版权所有  2026 ©涂聚文有限公司™ ®
# 许可信息查看:言語成了邀功盡責的功臣,還需要行爲每日來值班嗎
# 描述:广度优先搜索 Breadth First Search Algorithm 深度优先遍历 Depth First Search Algorithm
# Author    : geovindu,Geovin Du 涂聚文.
# IDE       : vs2026 c# .net 10
# os        : windows 10
# database  : mysql 9.0 sql server 2019, postgreSQL 17.0  Oracle 21c Neo4j
# Datetime  : 2026/07/18 23:16
# User      :  geovindu
# Product   : Visual Studio 2026
# Project   : CSharpAlgorithms
# File      : BreadthFirstBll.cs
 
 */
using CSharpAlgorithms.BreadthFirst.Core;
using CSharpAlgorithms.BreadthFirst.Service;
using System;
using System.Collections.Generic;
using System.Text;
 
namespace CSharpAlgorithms.Bll
{
    public class BreadthFirstBll
    {
 
        static readonly AppLogger Log = AppLogger.GetInstance();
        static readonly JewelryPackageService Service = new();
        static double ReadDouble(string prompt)
        {
            while (true)
            {
                Console.Write(prompt);
                var line = Console.ReadLine();
                if (double.TryParse(line, out var v) && v > 0)
                    return v;
                Console.WriteLine("输入错误,请输入大于0的数字!");
            }
        }
 
        static int ReadInt(string prompt)
        {
            while (true)
            {
                Console.Write(prompt);
                var line = Console.ReadLine();
                if (int.TryParse(line, out var v) && v >= 0)
                    return v;
                Console.WriteLine("输入错误,请输入非负整数!");
            }
        }
 
        static bool ReadBool(string prompt)
        {
            while (true)
            {
                Console.Write(prompt + " y/n:");
                var line = Console.ReadLine()?.Trim().ToLower();
                if (line == "y") return true;
                if (line == "n") return false;
                Console.WriteLine("只能输入 y 或 n");
            }
        }
        /// <summary>
        ///
        /// </summary>
        public void Demo()
       {
 
            List<JewelryItem> jewelryList = new();
            List<PackageSpec> pkgList = new();
            PackConstraint? constraint = null;
 
            while (true)
            {
                Console.WriteLine("\n================珠宝首饰智能包装配载系统 C#版================");
                Console.WriteLine("1. 录入首饰品类清单");
                Console.WriteLine("2. 录入包装规格(容积、成本、优先级权重、库存)");
                Console.WriteLine("3. 设置装箱约束规则");
                Console.WriteLine("4. BFS配载(推荐:最少数量+权重择优+成本择优)");
                Console.WriteLine("5. DFS配载");
                Console.WriteLine("6. A*启发式配载");
                Console.WriteLine("0. 退出程序");
                Console.Write("请输入选项:");
                var opt = Console.ReadLine()?.Trim() ?? "";
 
                switch (opt)
                {
                    case "1":
                        jewelryList.Clear();
                        while (true)
                        {
                            Console.Write("首饰货号:");
                            string code = Console.ReadLine()!.Trim();
                            double sv = ReadDouble("单件首饰(含配件)体积 cm³:");
                            int qty = ReadInt("该品类首饰数量:");
                            jewelryList.Add(new JewelryItem
                            {
                                JewelryCode = code,
                                SingleVolume = sv,
                                Quantity = qty
                            });
                            if (!ReadBool("继续录入下一类首饰?")) break;
                        }
                        break;
                    case "2":
                        pkgList.Clear();
                        while (true)
                        {
                            Console.Write("包装编码(BOX-500):");
                            string code = Console.ReadLine()!.Trim();
                            double vol = ReadDouble("包装标称容积 cm³:");
                            double cost = ReadDouble("单个包装单位成本:");
                            int weight = ReadInt("优先级权重(数值越大优先选用,建议1~100):");
                            int stock = ReadInt("当前可用库存数量:");
                            pkgList.Add(new PackageSpec
                            {
                                SpecCode = code,
                                Volume = vol,
                                UnitCost = cost,
                                PriorityWeight = weight,
                                StockQty = stock
                            });
                            if (!ReadBool("继续新增包装?")) break;
                        }
                        break;
                    case "3":
                        double bufferCoeff = ReadDouble("缓冲空隙系数(0.7~0.95,例0.85):");
                        int maxBoxQty = ReadInt("单个礼盒最多放置首饰件数:");
                        bool forbidMix = ReadBool("是否禁止不同品类首饰混装同一礼盒");
                        bool forbidSplit = ReadBool("是否禁止同一品类首饰拆分多个盒子");
                        constraint = new PackConstraint
                        {
                            BufferCoeff = bufferCoeff,
                            SingleBoxMaxQty = maxBoxQty,
                            ForbidMixJewelry = forbidMix,
                            ForbidSplitJewelry = forbidSplit
                        };
                        Log.Info("装箱约束参数设置完成!");
                        break;
                    case "4":
                    case "5":
                    case "6":
                        if (jewelryList.Count == 0 || pkgList.Count == 0 || constraint == null)
                        {
                            Console.WriteLine("请先录入首饰、包装、装箱约束!");
                            break;
                        }
                        LoadStrategyType st = opt switch
                        {
                            "4" => LoadStrategyType.BFS,
                            "5" => LoadStrategyType.DFS,
                            "6" => LoadStrategyType.ASTAR,
                            _ => LoadStrategyType.BFS
                        };
                        var cond = new PackQueryCondition
                        {
                            JewelryList = jewelryList,
                            Constraint = constraint,
                            PackageSpecList = pkgList,
                            StrategyType = st,
                            AllowOverLoad = false
                        };
                        Service.CalcSinglePlan(cond);
                        break;
                    case "0":
                        Log.Info("程序正常退出");
                        return;
                    default:
                        Console.WriteLine("无效选项!");
                        break;
                }
            }
 
        }
    }
}

输出:

相关推荐
妙码生花1 天前
从 PHP 到 AI + Golang,程序员自救转型手记(四十一):增加管理员账号管理接口
后端·go·gin
用户0678260743271 天前
APP版本管理全链路(后端设计)
后端
樱桃读报僵尸1 天前
说说 LLMRouter,Agent 执行过程中怎么动态的选择 LLM
算法
suconnect1 天前
Spring Boot接入企业RAG:文档切分、向量检索、权限过滤和答案溯源
java·spring boot·后端
神奇小汤圆1 天前
别再用 nohup java -jar 了:Spring Boot 生产环境该怎么守护?
后端
JavaGuide1 天前
GitHub 9.8 万 Star!把整个代码仓库变成知识图谱,这个 AI Coding 工具太适合 Claude Code / Codex 了
前端·后端·ai编程
旺仔学长 哈哈1 天前
Spring Boot 智能停车场管理系统---附源码+数据库文档
数据库·spring boot·后端·智能停车场
用户8356290780511 天前
使用 Python 在 PDF 中绘制线条、矩形和自定义图形
后端·python
霸道流氓气质1 天前
Java中集成Smile 技术教程:从入门到工程实践
java·开发语言
gis开发之家1 天前
《Vue3 从入门到大神40篇》Vue3 源码详解(十):diff 算法全解析 —— 为什么 Vue3 比 Vue2 更快?
javascript·算法·typescript·前端框架·vue3·vue3源码