01揹包問題理解動態規劃演算法

movin2333發表於2021-02-16

一.動態規劃演算法

簡單理解:在一些分治演算法解決的問題中,需要將較大規模的問題轉化為較小規模的問題,往往會用到遞迴。但是在一些問題中,遞迴的小問題被多次重複運算,浪費了效能,因此可以使用陣列或者其他合適的方式將運算過的小規模問題的結果記錄下來,再運算小規模的問題時先看是不是已經運算過了,沒有運算過再去運算並將結果儲存,所以一般分治演算法都是從大規模問題開始遞迴運算,不斷將問題規模變小,而動態規劃演算法則一般從小規模問題開始運算,每次運算分解出的小規模問題都是已經運算過的。如此便使用了儲存空間換取了運算時間,減小了時間複雜度。

二.01揹包問題

 

 1.窮舉法

可以先不考慮揹包,考慮被拿走的物品的所有組合情況,n個物品有2n種拿法(每個物品都可以選擇拿或者不拿),然後再判斷每一種拿法是否能放入揹包,能放入揹包的情況下計算總價值並比較出最大價值。

        static void Main(string[] args)
        {
            //記錄重量的陣列,物品有3種重量,分別是3、4、5
            int[] w = { 0, 3, 4, 5 };
            //記錄物品價值的陣列,和重量陣列對應,物品價值分別是4、5、6
            int[] p = { 0, 4, 5, 6 };
            //呼叫Exhaustivity函式得到7kg的揹包放置物品的最大價值
            Console.WriteLine(Exhaustivity(9 , w, p));
            Console.ReadKey();
        }
    
        /// <summary>
        /// 計算給定的mkg揹包放置物品的最大價值
        /// </summary>
        /// <param name="m">給定的物品重量</param>
        /// <param name="w">記錄所有物品重量的陣列</param>
        /// <param name="p">記錄所有物品對應價格的陣列</param>
        /// <returns>揹包中放置物品的最大價值</returns>
        public static int Exhaustivity(int m,int[] w,int[] p)
        {
            //記錄放入物品的最大的價格
            int maxPrice = 0;
            //有3個物品,外層就迴圈2的3次方,即i值從0取到7,i的二進位制數字最多3位,每一個i的取值的二進位制數字都對應3個物品是否放入揹包
            //如i為6,二進位制為110,代表取前兩個物品,不取第3個物品
            //又如i為0,二進位制為000,代表3個物品均不取;i為1,二進位制001,代表只取第3個物品
            //通過這種方式窮舉所有物品放入揹包的情況,然後判斷每一種情況重量是否滿足要求,再比較價格
            for(int i = 0;i < Math.Pow(2,w.Length - 1); i++)
            {
                //記錄所有被放入揹包物品的總重量
                int weightTotal = 0;
                //記錄所有被放入揹包物品的總價格
                int priceTotal = 0;
                //內層迴圈負責計算並比較所有物品的總重量和總價格
                //內層迴圈的次數就是物品個數,然後分別判斷當前情況下每個物品是否放入揹包,j代表第幾個物品
                for(int j = 1;j <= w.Length - 1; j++)
                {
                    //計算當前物品是否放入揹包
                    int result = Get2(i, j);
                    //在放入揹包的情況下,將重量和價格累加到總重量和總價格上
                    if(result == 1)
                    {
                        weightTotal += w[j];
                        priceTotal += p[j];
                    }
                }
                //判斷是否超重,不超重的情況下判斷價格是不是大於最大價格,如果是更新最大價格
                if (weightTotal <= m && priceTotal > maxPrice)
                    maxPrice = priceTotal;
            }
            //返回最大價格
            return maxPrice;
        }
        /// <summary>
        /// 通過按位與運算得到給定物品number在給定放入情況i中是否放入揹包
        /// </summary>
        /// <param name="i">給定的物品放入情況</param>
        /// <param name="number">給定的物品編號</param>
        /// <returns></returns>
        public static int Get2(int i,int number)
        {
            int A = i;
            int B = (int)Math.Pow(2,number - 1);
            int result = A & B;
            if (result == 0)
                return 0;
            return 1;
        }

 

 2.分治演算法(自上而下分解問題)

 這個問題的分治演算法的核心是將物品逐個放入揹包,有以下幾種情況:

1)揹包沒有剩餘重量了

2)當前物品的編號為0,即所有的物品都試過了

3)當前物品太重,無法放入揹包

4)當前物品可以放入揹包

在第4)種情況下,有兩種情況,一種放入物品得到的價格最大,一種不放入物品得到的價格最大(因為放入物品後背包的剩餘可放入重量也變小了,所以放入當前物品對後續物品能否放入的情況有影響,因此不一定是將當前物品放入得到的總價格最高,需要實際求解並判斷),因此需要進行比較得到最大價格

        static void Main(string[] args)
        {
            //記錄重量的陣列,物品有3種重量,分別是3、4、5
            int[] w = { 0, 3, 4, 5 };
            //記錄物品價值的陣列,和重量陣列對應,物品價值分別是4、5、6
            int[] p = { 0, 4, 5, 6 };
            Console.WriteLine(UpDown(9, w.Length - 1, w, p));
            Console.ReadKey();
        }
    
        /// <summary>
        /// 根據揹包剩餘重量和物品編號計算放入物品能獲得最大價值還是不放入物品能獲得最大價值
        /// </summary>
        /// <param name="m">揹包剩餘能裝的重量</param>
        /// <param name="i">當前放入揹包的物品編號</param>
        /// <param name="w">記錄所有物品重量的陣列</param>
        /// <param name="p">記錄所有物品價格的陣列</param>
        /// <returns>放入揹包的物品的最大價格</returns>
        public static int UpDown(int m,int i,int[] w,int[] p)
        {
            //如果揹包還能裝的重量為0或者放入的物品編號為0,價格自然都是0
            if (i == 0 || m == 0) return 0;
            //將第i個物品放入揹包,但是物品放入後超重,這種情況下就不放入這個物品,看下一個物品是否能放入揹包(物品編號-1)
            if(w[i] > m)
                return UpDown(m,i - 1, w, p);
            //在第i個物品能放入揹包的情況下需要計算比較價格
            else
            {
                //計算並記錄將物品放入揹包的情況下的最大價格(遞迴物品編號-1,從揹包剩餘重量中扣除當前物品重量,由於當前物品放入揹包,揹包中物品價格加上當前物品價格)
                int maxValue1 = UpDown(m - w[i], i - 1, w, p) + p[i];
                //計算並記錄不將物品放入揹包的情況下的最大價格(遞迴物品編號-1,但是由於沒有放入物品,揹包剩餘重量不變)
                int maxValue2 = UpDown(m, i - 1, w, p);
                //比較並取其中價格最大的那個
                if (maxValue1 > maxValue2)
                    return maxValue1;
                return maxValue2;
            }
        }

 

 3.改進分治演算法,使用陣列記錄已經運算過的問題結果

        //定義一個規則二維陣列記錄結果,行數對應揹包重量,列數對應放入的物品個數,值對應這種情況揹包中的最大重量
        public static int[,] result = new int[11, 4];
        static void Main(string[] args)
        {
            int[] w = { 0, 3, 4, 5 };
            int[] p = { 0, 4, 5, 6 };
            Console.WriteLine(UpDown(9, w.Length - 1, w, p));
            Console.ReadKey();
        }

        public static int UpDown(int m,int i,int[] w,int[] p)
        {
            if (i == 0 || m == 0) return 0;
            //判斷這種情況是不是已經計算過了,計算過了就直接返回
            if (result[m, i] != 0)
                return result[m, i];

            //在計算完成後需要將計算結果儲存起來
            if(w[i] > m)
                return result[m,i] = UpDown(m,i - 1, w, p);
            else
            {
                int maxValue1 = UpDown(m - w[i], i - 1, w, p) + p[i];
                int maxValue2 = UpDown(m, i - 1, w, p);
                if (maxValue1 > maxValue2)
                    return result[m, i] = maxValue1;
                return result[m, i] = maxValue2;
            }
        }

4.動態規劃演算法(自下而上計算問題)

        static void Main(string[] args)
        {
            int[] w = { 0, 3, 4, 5 };
            int[] p = { 0, 4, 5, 6 };
            Console.WriteLine(BottomUp(9, w.Length - 1, w, p));
            Console.ReadKey();
        }
        //記錄運算結果的陣列,行數代表揹包重量,列數代表物品編號
        public static int[,] result = new int[11, 4];
        /// <summary>
        /// 計算指定重量m的揹包种放置前i個物品放置的最大價值
        /// </summary>
        /// <param name="m">揹包重量</param>
        /// <param name="i">物品編號</param>
        /// <param name="w">記錄所有物品重量的陣列</param>
        /// <param name="p">記錄所有物品價格的陣列</param>
        /// <returns></returns>
        public static int BottomUp(int m,int i,int[] w,int[] p)
        {
            //外層迴圈從1到m,揹包重量從小到大
            for(int tempM = 1;tempM <= m; tempM++)
            {
                //內層迴圈從1到i,物品個數從1到i
                for(int tempI = 1;tempI <= i;tempI++)
                {
                    //如果已經計算過,不用再計算了
                    if (result[tempM, tempI] != 0) continue;
                    //如果當前物品重量超過揹包剩餘可裝重量,不放入物品,將物品編號減一
                    if (w[tempI] > tempM)
                        result[tempM, tempI] = result[tempM, tempI - 1];
                    //當前放置的物品重量不超過揹包剩餘可裝重量,說明物品能放入揹包
                    //物品不一定放入揹包最後的總價值最大,所以需要比較放入和不放入的情況得到的價格哪種更大
                    else
                    {
                        int maxValue1 = result[tempM - w[tempI], tempI - 1] + p[tempI];
                        int maxValue2 = result[tempM, tempI - 1];
                        result[tempM, tempI] = maxValue1 > maxValue2 ? maxValue1 : maxValue2;
                    }
                }
            }
            //自下而上計算完成後,返回重量為m的揹包放入i個物品的最大價格即可
            return result[m, i];
        }

 

相關文章