1. 程式人生 > 實用技巧 >完全揹包問題

完全揹包問題

完全揹包問題貪心演算法入門

題目描述

一個揹包容量為c, 共有n中物品, 第i個物品的重量為Wi, 價值為Vi. 選擇物品裝入揹包, 可以只裝入一部分, 請問,如何裝,可以使揹包的價值最大.

解決思路

此題和0-1揹包問題的不同在於, 0-1揹包問題要求物品全部裝進去, 而完全揹包問題要求物品可以只裝入一部分,採用貪心的策略解決這個問題

核心策略就是 : 優先選擇單價高的物品裝入揹包

  • 首先計算物品單價
  • 優先選擇單價高的物品裝入揹包
  • 如果物品重量小於揹包剩餘重量, 接著物品全部裝入揹包, 重新計算揹包剩餘重量, 裝入下一個物品
  • 如果物品重量大於揹包剩餘價值, 物品部分裝入揹包, 此時揹包已經滿了, 不再裝入.

程式碼思路

// 因為要按照物品的單價排序, 所以可以將物品當作一個類
// 1.自定排序規則, java如何自定排序規則
// 2.java中使用Arrays.sort()或者Collections.sort()進行排序
/**
 * @Author Fizz Pu
 * @Date 2020/10/30 下午3:51
 * @Version 1.0
 * 失之毫釐,繆之千里!
 */

/**
 * 題目描述
 * 臨近開學了,大家都忙著收拾行李準  備返校,但 I_Love_C 卻不為此擔心! 因為他的心思全在暑假作業上:目前為止還未開動。
 * 暑假作業是很多張試卷,我們這些從試卷裡爬出來的人都知道,卷子上的題目有選擇題、填空題、簡答題、證明題等。
 * 而做選擇題的好處就在於工作量很少,但又因為選擇題題目都普遍很長。如果有 5 張試卷,其中 4 張是選擇題,最後一張是填空題,
 * 很明顯做最後一張所花的時間要比前 4 張長很多。但如果你只做了選擇題,雖然工作量很少,但表面上看起來也已經做了4/5的作業了。
 * I_Love_C決定就用這樣的方法來矇混過關,他統計出了做完每一張試卷所需的時間以及它做完後能得到的價值(按上面的原理,選擇題越多價值當然就越高咯)。
 * 現在就請你幫他安排一下,用他僅剩的一點時間來做最有價值的作業。
 
 * 輸入
 * 測試資料包括多組。每組測試資料以兩個整數 M,N(1<M<20,1<N<10000) 開頭,分別表示試卷的數目和 I_Love_C 剩下的時間。
 * 接下來有 M 行,每行包括兩個整數 T,V(1<T<N,1<V<10000)分別表示做完這張試卷所需的時間以及做完後能得到的價值,輸入以 0 0 結束。
 *
 * 輸出
 * 對應每組測試資料輸出 I_Love_C 能獲得的最大價值。保留小數點 2 位
 *
 * 提示:float 的精度可能不夠,你應該使用 double 型別。
 *
 * 樣例輸入
 * 4 20
 * 4 10
 * 5 22
 * 10 3
 * 1 2
 * 0 0
 * 樣例輸出
 * 37.00
 */

import java.util.Arrays;
import java.util.Scanner;

/**
 * 此題可以轉換為完全揹包問題
 * 不是0,1揹包問題,原因是作業可以只做一部分
 * N : 可以看成揹包總重
 * 試卷價值:物品價值, 試卷所需時間:物品重量
 * 那個卷子單位時間的價值高,我就先做
 */


public class Main {

    static class Node implements Comparable<Node>{
        double price;
        int id; // 在陣列中的下標

        public Node(double price, int id)  {
            this.price = price;
            this.id = id;
        }

        @Override
        public int compareTo(Node ob){
            if(ob.price > this.price) return 1;
            else return -1;
        }
    }

    double getMaxValue(double[] times, double[] value, int restTime) {
        int r = restTime; // 揹包剩餘重量
        double sums = 0;
        // 要通過單價找到對應物品的時間,價值.單個數組排序的方式肯定不行
        // 利用節點記錄下資訊即可
        Node[] nodes = new Node[times.length];
        for(int i = 0; i < times.length; ++i){
            nodes[i] = new Node(value[i]/times[i], i);
        }

        // 降序排序
        Arrays.sort(nodes);

        for(Node node: nodes){
            int id = node.id;
            if(times[id] <= r){
                sums += value[id];
                r -= times[id];
            } else {
                sums += (r * node.price);
                break;
            }
        }
        return sums;
    }
    
    
    public static void main(String[] args) {
        Main main = new Main();

        // 讀取資料
        Scanner scanner = new Scanner(System.in);
        int itemCount, restTime;
        while (true){
            itemCount = scanner.nextInt();
            restTime = scanner.nextInt();
            if(itemCount == 0 && restTime == 0)break;
            double[] times = new double[itemCount];
            double[] values = new double[itemCount];
            for(int i = 0; i < itemCount; ++i){
                times[i] = scanner.nextDouble();
                values[i] = scanner.nextDouble();
            }
            // java中使用System.out.printf()進行格式化輸出
            System.out.printf("%.2f\n", main.getMaxValue(times, values, restTime));
        }
    }
}

思考:

其實可以有多種貪心策略, 比如此題中我可以先做價值大的作業, 也可以先做耗時最小的作業, 這些儘管是區域性最優解, 但不是全域性最優解. 貪心就是要選出一種策略, 它是區域性最優, 最後可以得到全域性最優 可以某些區域性最優不一定可以到達全域性最優. 想想做人有時候也是這樣啊, 步步為贏,每一步都順風順水, 最終也不一定會得到最好的結果, 年輕人還是多用困難歷練以下自己