• 背包分组问题的解法


    背包分组问题的解法

    作者:eaglet

         今天在博问中看到这样一个问题 按记录总值比例分组记录 ,这个问题本质上是一个背包分组的问题。eaglet 花了2小时时间写了一个C#的实现,时间仓促,感觉还有很多值得改进的地方,不管怎么样,功能是实现了,贴出来给大家讨论吧。

         我先把原题的意思按照我的理解再描述一遍:

         有数组A 假设为 int[] goods = {25,15,10,3,1, 5, 14, 16, 5, 6};

         我们希望将这些goods 按下面给出的分组规则来分组。

         我们有数组B 假设为 int[] sizes = {50, 30, 20}; 我们希望把数组A分成三组,并且使每组的和与数组B对应的值最匹配。

         本题的答案是

         Group1 : {10,3,1,14,16,6}

         Group2 : {25,5}

         Group3 : {15,5}   

         数组长度小的时候,用手算就可以分组,但如果长度大,分组数量多,则手算就很难了,需要寻求计算机的帮助。

         我的解决思路是:

         第一步用整个的goods 数组分别按 50, 30 ,20 计算最优组合,得到三组最优组合(注意这时这三组组合很可能有重复的记录)

         第二步从这三组组合中取出最优的一组,也就是总和和对应的大小之差最小的一组,保留这组记录。

         第三步从goods数组中将刚刚选中的那组数据剔除掉,然后用新的goods 数组重复第一步,运算时不再运算已选出的组合,直到全部匹配或者只剩下最后一组。

         第四步如果还剩下最后一组,则把剩余的goods 全部给这一组,并输出。

        

         下面给出代码

        /// <summary>
        /// 背包分组
        /// </summary>
        public class BackpackGroup
        {
            /// <summary>
            /// 找到最匹配的那个组别
            /// </summary>
            /// <param name="sizes"></param>
            /// <param name="result"></param>
            /// <returns></returns>
            private int GetMostMatchedIndex(int[] sizes, List<int>[] result)
            {
                int min = int.MaxValue;
                int index = -1;
     
                for (int i = 0; i < sizes.Length; i++)
                {
                    if (result[i] != null)
                    {
                        int sum = 0;
                        foreach (int value in result[i])
                        {
                            sum += value;
                        }
     
                        if (min >= sizes[i] - sum)
                        {
                            index = i;
                            min = sizes[i] - sum;
                        }
                    }
                }
     
                return index;
            }
     
            /// <summary>
            /// 得到剩余的goods
            /// </summary>
            /// <param name="select"></param>
            /// <param name="goods"></param>
            /// <returns></returns>
            private int[] GetLeftGoods(List<int> select, int[] goods)
            {
                List<int> result = new List<int>();
     
                int?[] tempSelect = new int?[select.Count];
     
                for (int i = 0; i < select.Count; i++)
                {
                    tempSelect[i] = select[i];
                }
     
                foreach (int value in goods)
                {
                    bool throwaway = false;
     
                    for (int i = 0; i < select.Count; i++)
                    {
                        if (tempSelect[i] == null)
                        {
                            continue;
                        }
     
                        if (tempSelect[i] == value)
                        {
                            throwaway = true;
                            tempSelect[i] = null;
                            break;
                        }
                    }
     
                    if (!throwaway)
                    {
                        result.Add(value);
                    }
                }
     
                return result.ToArray();
            }
     
            /// <summary>
            /// 递归方式内部分组
            /// </summary>
            /// <param name="goods"></param>
            /// <param name="sizes"></param>
            /// <param name="result"></param>
            private void InnerGroup(int[] goods, int[] sizes, List<int>[] result)
            {
                List<int>[] temp = new List<int>[result.Length];
                result.CopyTo(temp, 0);
     
                for (int i = 0; i < sizes.Length; i++)
                {
                    if (temp[i] == null)
                    {
                        Backpack backpack = new Backpack();
                        temp[i] = backpack.Match(goods, sizes[i]);
                    }
                    else
                    {
                        temp[i] = null;
                    }
                }
     
                int index = GetMostMatchedIndex(sizes, temp);
     
                if (index < 0)
                {
                    return;
                }
     
                result[index] = temp[index];
     
                goods = GetLeftGoods(temp[index], goods);
     
                int left = 0;
                int lastIndex = -1;
                
                for(int i = 0; i < result.Length; i++)
                {
                    if (result[i] == null)
                    {
                        lastIndex = i;
                        left++;
                    }
                }
     
                if (left == 1)
                {
                    result[lastIndex] = new List<int>(goods);
                    return;
                }
     
     
                InnerGroup(goods, sizes, result);
            }
     
            public List<int>[] Group(int[] goods, int[] sizes)
            {
                List<int>[] result = new List<int>[sizes.Length];
                InnerGroup(goods, sizes, result);
                return result;
            }
        }
     
     
        /// <summary>
        /// 背包算法
        /// </summary>
        public class Backpack
        {
            private List<int> _MatchGoods = new List<int>();
            private List<int> _TmpMatchGoods = new List<int>();
            private int[] _Goods;
            private int _Size;
            private int _Max = 0;
            private bool findMatch = false;
            private int _PreSum = 0;
            private bool _CatchLast = false;
     
            private void Init(int[] goods, int size)
            {
                _MatchGoods = new List<int>();
                _TmpMatchGoods = new List<int>();
                _Goods = goods;
                _Size = size;
                _Max = 0;
                findMatch = false;
                _PreSum = 0;
                _CatchLast = false;
            }
     
            /// <summary>
            /// 递归计算从第start个元素开始的之后的最匹配结果
            /// </summary>
            /// <param name="start"></param>
            /// <param name="floor"></param>
            private void Match(int start, int floor)
            {
                if (start >= _Goods.Length)
                {
                    _CatchLast = true;
                    return;
                }
     
                if (_PreSum + _Goods[start] > _Size)
                {
                    return;
                }
     
                _PreSum += _Goods[start];
     
                _TmpMatchGoods.Add(_Goods[start]);
     
                if (start + 1 >= _Goods.Length)
                {
                    _CatchLast = true;
                    return;
                }
     
                for (int i = start + 1; i < _Goods.Length; i++)
                {
                    Match(i, floor + 1);
     
                    if (floor == 0)
                    {
                        if (_PreSum == _Size)
                        {
                            findMatch = true;
                            _MatchGoods = _TmpMatchGoods;
                        }
                        else
                        {
                            if (_Max < _PreSum)
                            {
                                _Max = _PreSum;
                                _MatchGoods = _TmpMatchGoods;
                            }
                        }
     
                        if (_CatchLast)
                        {
                            return;
                        }
     
                        _TmpMatchGoods = new List<int>(_Goods.Length);
                        _PreSum = 0;
     
                        _PreSum += _Goods[start];
                        _TmpMatchGoods.Add(_Goods[start]);
                    }
                }
            }
     
            public List<int> Match(int[] goods, int size)
            {
                Init(goods, size);
     
                //以此计算各个元素的组合,找到第一个最匹配的结果
                for (int i = 0; i < goods.Length; i++)
                {
                    _PreSum = 0;
                    _CatchLast = false;
                    _TmpMatchGoods = new List<int>(_Goods.Length);
     
                    Match(i, 0);
     
                    if (findMatch)
                    {
                        return _MatchGoods;
                    }
                }
     
                return _MatchGoods;
            }
        }

    这个算法有个问题,就是背包算法只给出了一组最匹配的记录,如果最匹配的记录有多个(并列的),则情况会更复杂一些,不过这个相对简单的算法最后分组的效果已经不错。

    另外背包算法感觉写的并不简洁,应该还有更好的写法。

    测试代码

                Backpack backpack = new Backpack();
     
                int[] goods = {25,15,10,3,1, 5, 14, 16, 5, 6};
                int[] sizes = {35, 45, 20};
                BackpackGroup backGroup = new BackpackGroup();
                List<int>[] groups = backGroup.Group(goods, sizes);
     
                foreach (List<int> matchGoods in groups)
                {
                    foreach (int mg in matchGoods)
                    {
                        Console.Write(mg);
                        Console.Write(",");
                    }
                    Console.WriteLine();
                }
                Console.ReadKey();

     

    下面给出几个不同的分组的结果

    int[] sizes = {50, 30, 20};

    10,3,1,14,16,6,
    25,5,
    15,5,

     

    int[] sizes = {70, 10, 20};

    25,3,1,14,16,5,6,
    10,
    15,5,

    int[] sizes = {35, 45, 20};

    10,3,1,16,6,
    25,14,5,
    15,5,

    前两组完全匹配,最后一组近似匹配。

  • 相关阅读:
    分位数(quantiles)、Z-score 与 F-score
    学术研究中的 NLP
    学术研究中的 NLP
    国内外免费电子书(数学、算法、图像、深度学习、机器学习)
    国内外免费电子书(数学、算法、图像、深度学习、机器学习)
    中英文对照 —— 缩略词
    内核编译及模块相关命令使用
    UVA 156 (13.08.04)
    hdu3117之矩阵快速幂
    系统运维技巧(三)——利用dd命令临时增加交换分区
  • 原文地址:https://www.cnblogs.com/eaglet/p/1555645.html
Copyright © 2020-2023  润新知