算法导论实践——背包问题(完全背包问题和0/1背包问题)

算法导论实践——背包问题(完全背包问题和0/1背包问题)

  • 理论
    • 前言
    • 完全背包问题
    • 0/1背包问题
  • 实践
    • UnboundedKnapsack.cs
    • Zero_OneKnapsack.cs
    • Program.cs

以前感觉很高大上,现在理解了觉得也不是太难。其中0/1背包问题理解起来比较有难度。

理论

前言

代码参考课堂PPT完成,0/1背包问题的思考花费了大量时间。事实上,完全背包问题就是在容量x给定的基础上每次添加物品i,都会将所有物品遍历一遍,寻找当前应该加入的物品i,这个物品有可能重复;而0/1背包问题是在容量x给定的基础上,按照一定的规律,有序添加物品。即对j递增,判断第j个元素能否加入先前的某个已经求出的背包中。因为前面的某个已经求出的背包也是按照能否添加j的方式确定,因此就不会发生元素重复了。
总结一下

  • 完全背包问题:存储代价所需要的空间是一维线性的(不需要记忆之前放入的情况),而物品选择时是二维的。
  • 0/1背包问题:存储代价所需要的空间是二维的,而物品选择时是一维线性的。(只需判断元素j是否加入)

完全背包问题

设K[x]为背包容量为x时的最大装配价值。

则有K[x] = Max{K[x],K[x - Wi] + Vi}, Wi<=x, 其中Wi是物品i的容量,Vi是物品i的价值。具体思路就是:
背包容量为x时的最大装配价值 = Max(之前的某个容量的最大装配价值 + 加入某个物品的装配价值)
为了确定“之前的某个容量的最大装配价值”,我们应该遍历物品序列,并且用x减去加入的第i个物品的容量,也就是说,x = x - Wi。举个例子,如果K[x] = K[x - W2] + V2,这就说明我们应该在当背包容量是x - W2的装配方案中装入新的物品V2,此时容量为x的背包有最大价值。

0/1背包问题

设K[x,j]表示背包容量为x时,装前j个元素(只用前j个元素)的最大装配价值。

在完全背包问题中,我们会考虑所有情况,不会去记忆之前装配的情况,不需要考虑物品的重复性,而在0/1背包问题中,每个物品只能装一次,因此,为了实现0/1背包问题,我们需要知道之前装过什么,之后就不能再装同一个物品了。 0/1背包问题的算法设计思路很清晰。我们把过程拆开来看,按照动态规划的子结构要求,在给定背包容量的情况下,我们要知道K[x,j]和K[x,j-1]的关系。
容易发现如果在K[x,j]的装配方案中,不对第j个物品予以考虑的话,有:

  K[x,j] = K[x,j-1]; 

而如果要在K[x,j]的装配方案中对第j个物品予以考虑的话,有:

    K[x,j] = Max{K[x,j],K[x - Wj,j - 1] + Vj},这里类似于完全背包问题,改改参数即可。

总的来说,每一次的判断即:是否装入第j个物品,如要装入,在之前的哪个位置装入第j个物品。如此一来就永远都不会发生重复添置物品的情况了。

实践

UnboundedKnapsack.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace Knapsack
{
     
    public static class UnboundedKnapsack
    {
     
        /*演算:
         * 设K[x]为背包容量为x时的最大装配价值,则有
         * 
         * 
         * K[x] = Max{K[x],K[x - Wi] + Vi}, Wi<=x, 其中Wi是物品i的容量,Vi是物品i的价值。
         * 具体思路就是:
         * 背包容量为x时的最大装配价值 = Max(之前的某个容量的最大装配价值 + 加入某个物品的装配价值)
         * 为了确定“之前的某个容量的最大装配价值”,我们应该用x减去加入的某个物品的容量,也就是说,
         * x = x - Wi。举个例子,如果K[x] = K[x - W2] + V2,这就说明我们应该在当背包容量是x - W2的装配方案中
         * 装入新的物品V2,此时容量为x的背包有最大价值。
         */
        private static int[] K;
        private static List<Item>[] PackSolutions;
        public static int SolveUnboundedKnapsack(Item[] items, int capacity)
        {
     
            int n = items.Length;
            PackSolutions = new List<Item>[capacity + 1];
            K = new int[capacity + 1];

            for(int i = 0; i < capacity + 1; i++)
            {
     
                K[i] = 0;
                PackSolutions[i] = new List<Item>();
            }
            
            for (int x = 1; x <= capacity; x++)
            {
     
                K[x] = -1;
                for(int i = 0; i < n; i++)
                {
     
                    if(items[i].Weight <= x)
                    {
     
                        int q = K[x - items[i].Weight] + items[i].Value;
                        if(q > K[x])
                        {
     
                            K[x] = q;
                            PackSolutions[x].Clear();
                            PackSolutions[x - items[i].Weight].ForEach(item => PackSolutions[x].Add(item));
                            PackSolutions[x].Add(items[i]);
                        }
                    } 
                }
            }
            return K[capacity];
        }

        public static void PrintPackSolutions()
        {
     
            for(int i = 1; i < PackSolutions.Length; i++)
            {
     
                Console.WriteLine("当受重能力为" + i +"时:");
                for(int j = 0; j < PackSolutions[i].Count; j++)
                {
     
                    Console.WriteLine("选择" + PackSolutions[i][j].Name);
                }
            }
        }
    }
}

Zero_OneKnapsack.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace Knapsack
{
     
    public static class Zero_OneKnapsack
    {
     
        /*演算:
         * 设K[x,j]表示背包容量为x时,装前j个元素(只用前j个元素)的最大配置价值
         * 在完全背包问题中
         * 不需要考虑物品的重复性,而在0/1背包问题中,每个物品只能装一次,因此,
         * 为了实现0/1背包问题,我们需要知道之前装过什么,之后就不能再装同一个物品了。
         * 0/1背包问题的算法设计思路很清晰。我们把过程拆开来看,
         * 按照动态规划的子结构要求,在给定背包容量的情况下,
         * 
         * 我们要知道K[x,j]和K[x,j-1]的关系。容易发现如果不考虑第j个物品在K[x,j]的装配方案中的话,有:
         * K[x,j] = K[x,j-1];
         * 而如果要考虑第j个物品在K[x,j]的装配方案中的话,有:
         * K[x,j] = Max{K[x,j],K[x - Wj,j - 1] + Vj},这里类似于完全背包问题,改改参数即可。
         * 
         * 即判断是否装入第j个物品,在之前的哪里装入第j个物品。如此一来,这样一来永远都不会发生重复添置物品的情况了。
         */

        private static int[,] K;
        private static List<Item>[,] PackSolutions;

        public static int SolveZero_OneKnapsack(Item[] items, int capacity)
        {
     
            int n = items.Length;
            K = new int[capacity + 1, n + 1];
            PackSolutions = new List<Item>[capacity + 1, n + 1];
            //初始化
            for(int i = 0; i < capacity + 1; i++)
            {
     
                for(int j = 0;j < n + 1; j++)
                {
     
                    K[i, j] = 0;
                    PackSolutions[i, j] = new List<Item>();
                }
            }


            for(int x = 1; x <= capacity; x++)
            {
     
                for(int j = 1; j <= n; j++)
                {
     
                    //还未加入第j个item时,为其Solutions赋初值,因为j不一定会加入,此时是没考虑j在K[x,j]中
                    K[x, j] = K[x, j - 1];
                    PackSolutions[x, j - 1].ForEach(item => PackSolutions[x, j].Add(item));
                    
                    
                    if (items[j - 1].Weight <= x)//items[j-1] 就是第j个物品
                    {
     
                        int q = K[x - items[j - 1].Weight, j - 1] + items[j - 1].Value;
                        //开始考虑是否加入第j个物品
                        if (q > K[x, j])
                        {
     
                            K[x, j] = q;

                            //在K[x - items[j - 1].Weight, j - 1]的基础上添加Item(在哪里添加第j个物品)
                            PackSolutions[x, j].Clear();
                            PackSolutions[x - items[j - 1].Weight, j - 1].ForEach(item => PackSolutions[x, j].Add(item));
                            PackSolutions[x, j].Add(items[j - 1]);
                        }
                    }
                }
            }

            return K[capacity, n];
        }

        public static void PrintPackSolutions(Item[] items, int capacity)
        {
     
            for (int i = 1; i <= capacity; i++)
            {
     
                Console.WriteLine("---------------------------------------------------");
                Console.WriteLine("容纳量x为:" + i);
                for (int j = 1; j <= items.Length; j++)
                {
     
                    Console.WriteLine("选择前" + j + "个元素的方案:");
                    for (int k = 0; k < PackSolutions[i, j].Count; k++)
                    {
     
                        Console.Write(PackSolutions[i, j][k].Name);
                        Console.Write("     ");
                    }
                    Console.WriteLine(" ");
                }
            }
        }
    }
}

Program.cs

using System;

namespace Knapsack
{
     
    class Program
    {
     
        static void Main(string[] args)
        {
     
            Item[] items = new Item[3];
            items[0] = new Item {
      Name = "乌龟", Weight = 1, Value = 1 };
            items[1] = new Item {
      Name = "灯泡", Weight = 2, Value = 4 };
            items[2] = new Item {
      Name = "西瓜", Weight = 3, Value = 6 };

            /*int maxValue = UnboundedKnapsack.SolveUnboundedKnapsack(items, 4);
            Console.WriteLine("最大价值:" + maxValue);
            UnboundedKnapsack.PrintPackSolutions();*/

            int maxValue = Zero_OneKnapsack.SolveZero_OneKnapsack(items, 3);
            Console.WriteLine("最大价值:" + maxValue);
            Zero_OneKnapsack.PrintPackSolutions(items, 3);
        }
    }
}

你可能感兴趣的:(算法学习,算法,动态规划,c#,visual,studio)