Может быть, вы задали много вопросов о рюкзаке 01, но действительно ли вы хорошо понимаете рюкзак 01? , прочитайте мою статью, чтобы вы могли лучше понять рюкзак 01 [код + схема + текстовое описание]

1. Концептуальное понимание: что такое рюкзак 01

Понимание концепции рюкзака 01 заключается в следующем: рюкзак 01 состоит в том, чтобы вынуть несколько предметов из M предметов и положить их в рюкзак с пространством W. Объем каждого предмета составляет от W1, от W2 до Wn, и соответствующий значение P1, от P2 до Pn. Условие ограничения рюкзака 001 состоит в том, что дано несколько предметов, каждый предмет имеет один и только один и имеет два атрибута: стоимость и объем. В задаче о рюкзаке 01, поскольку имеется только один предмет каждого предмета, необходимо рассмотреть только два случая выбора и невыбора для каждого предмета. Если вы не решите положить его в свой рюкзак, вам не нужно иметь с ним дело. Если вы решите положить его в рюкзак, поскольку неясно, сколько места занимают ранее размещенные предметы, необходимо перечислить все ситуации, которые могут занять место в рюкзаке после того, как этот предмет будет помещен в рюкзак.

2. Причина, по которой большинство людей не совсем понимают рюкзак 01

Что касается того, почему большинство людей не совсем понимают рюкзак 01, мы анализируем его со следующих двух точек зрения:

①Код для задачи о рюкзаке 01 относительно короткий, но его не так легко понять.Это экономит время и труд, но как только в задачу о рюкзаке 01 будет внесено небольшое изменение, он будет возвращен в прототип немедленно~

②Даже если я понимаю стратегию планирования рюкзака 01, значение массива и индексов массива, созданных рюкзаком 01, все еще расплывчато, и я могу даже быть AC по теме рюкзака 01, и я не знать конкретное значение массива dp рюкзака 01

3.01 Теоретическая стратегия рюкзака (насильственная рекурсия)

По сути, анализ рюкзака 01 можно понимать как анализ вместимости рюкзака, стоимости предмета и соответствующего веса: для каждого предмета у нас есть два варианта: класть его в рюкзак или нет. положите его в рюкзак, мы выбираем стратегию спереди назад в соответствии с порядком предметов. Независимо от того, кладется ли текущий предмет в рюкзак или нет, мы должны гарантировать результат. После того, как мы обработали решение о том, положить последний предмет или нет, общий вес предмета Он не должен быть больше, чем общая вместимость рюкзака.Что касается максимального значения, которое может вместить рюкзак, нам нужно только выбрать максимальное значение, соответствующее требованиям и возвращать его после каждого решения по последнему пункту. Что касается добавления предмета в стратегию, то общий вес предметов превышает общую вместимость рюкзака, мы можем напрямую прекратить эту стратегию, и эта теоретическая стратегия есть просто идея насильственной рекурсивной реализации Проблема рюкзака Мы выбираем ковать железо, пока горячо, и напрямую отправляем агрессивный рекурсивный код (с примечаниями) о задаче 01 о рюкзаке:
 

public class Bag {
    public static void main(String[] args) {
        int[]w={3,4,5};
        int[]v={6,4,8};
        System.out.println(bestBag(w, v, 10));
    }
    public static int bestBag(int []w,int []v,int weight){
        //检查有效性
        if(w.length==1&&w[0]>weight){
            return 0;
        }
       return process(w,v,0,0,weight);
    }
    //递归过程

    /**
     * 
     * @param w 物品的重量数组
     * @param v 物品的价值数组
     * @param index 遍历的当前物品的序号
     * @param alWeight 当前所遍历过的物品中在选择放与不放之后的重量
     * @param weight 背包的容量
     * @return
     */
    private static int process(int[] w, int[] v, int index, int alWeight, int weight) {
        //判断出口
        if(alWeight>weight){
            return -1;
        }
        if(index==w.length){
            return 0;//成功
        }
        //第一种情况:当前节点上不放值(因为当前物品一旦加入到背包中,背包的总容量就超了)
        int value1=process(w, v, index+1, alWeight, weight);
        //第二种情况:当前结点放入值
        int value2Next=process(w, v, index+1, alWeight+w[index], weight);
        int value2=-1;
        if(value2Next!=-1){
            value2=value2Next+v[index];
        }
        //返回两种结果的最大值
        return Math.max(value1, value2);
    }
}

4. Стратегия динамического программирования для реализации рюкзака 01

4.1 Использование двумерного массива для реализации динамического программирования рюкзака 01

Что касается задачи динамического программирования, моя стратегия реализации и анализа состоит в том, чтобы разделить ее на пять частей: ① Нижний индекс элемента рекурсивного массива и значение элемента ② Реализация рекурсивного уравнения ③ Инициализация массива dp ④ Порядок обхода dp Массив ⑤ При возникновении ошибки мы быстро и точно определяем место возникновения проблемы, распечатывая массив dp и повышая эффективность отладки.

Для анализа проблемы рюкзака 01 мы анализируем следующие пять аспектов: ① Мы создаем двумерный массив dp, например, элемент в массиве dp — это dp[i][j], i представляет текущий элемент для прохождения , а j представляет емкость текущего массива dp (в этом случае мы сначала пройдем элементы, а затем емкость по умолчанию (одноуровневый цикл предназначен для прохождения элементов, а двухуровневый цикл — для прохождения емкости), в на самом деле возможен и обратный обход, мы объясним это позже~) , конкретное значение в dp[i][j] представляет собой максимальное значение, которое может удовлетворить пропускную способность j при выборе текущих i элементов

②Рекурсивная формула: Анализируем задачу 01 рюкзака: если мы хотим достичь максимальной вместимости рюкзака, то мы должны сделать вместимость рюкзака как можно более полной, и это может быть среди всех предметов, которые можно выбрать в настоящее Сделать выбор, поэтому, когда мы сталкиваемся с выбором каждого предмета, когда мы хотим использовать все предметы, которые можно выбрать в данный момент, и максимально использовать оставшуюся вместимость всех рюкзаков в сочетании с dp[i][ j] мы упоминали выше. Значением является максимальное значение, которое может быть размещено при выборе текущих предыдущих i элементов при текущей емкости j, поэтому рекурсивная формула dp[i][j] должна быть основана на выборе и не- стратегии выбора текущего элемента. Возьмем его оптимальное решение: dp[i][j]=max(dp[i-1][j],dp[i][j-weight[i]]+value[i]), мы рекурсивно Формула объясняется: Значение dp[i-1][j] Мы не выбираем текущий элемент, поэтому мы выбираем только из i-1 элементов, потому что нет необходимости учитывать влияние текущего элемента на вместимость рюкзака, поэтому для выбора вместимости рюкзака мы выбираем максимальную вместимость рюкзака j, которую можно выбрать в данный момент; dp[i][j-weight[i]]+value[i]: это текущий элемент мы выбираем положить в рюкзак, так как текущий предмет Поместите его в рюкзак, то после помещения этого предмета максимальная вместимость, которая может быть размещена, составляет только [j-вес[i]], поэтому мы имеем j-вес[ i] в первых i-1 пунктах В случае выбрать оптимальное решение, а затем добавить значение последнего пункта. Заодно поясним упомянутый выше вопрос про обход предметов в первую очередь или прохождение вместимости рюкзака в первую очередь: давайте сначала поставим здесь вывод: все в порядке, почему? Мы наблюдаем следующие изображения в сочетании с рекурсивной формулой, и мы можем обнаружить, что для каждого элемента он запускается совместно элементом с правой стороны текущего элемента и элементом над текущим элементом, поэтому независимо от того, проходим ли мы элемент сначала или сначала проходим емкость, все мы Обнаружено, что оба элемента справа и сверху элемента удовлетворяют элементу, но емкость просматривается первой (по сравнению с обходом по столбцу) или элемент просматривается первым (обход по строке) , а все элементы справа и сверху проходятся, поэтому оба этих метода обхода могут

③Инициализация: (мы по-прежнему комбинируем приведенное выше изображение) Для случая нулевой емкости, независимо от того, сколько элементов есть, мы все равно не можем разместить один элемент: для первого столбца данных мы инициализируем его 0, только для в случае предмета 0, если вместимость нашего текущего рюкзака может быть больше или равна весу предмета, мы можем поместить значение предмета в массив, но если вместимость текущего рюкзака меньше вес предмета, значение в этом рюкзаке по-прежнему 0, но как насчет других значений строк и столбцов, которые не участвуют? Мы можем получить из рекурсивной формулы: значение элемента в текущей строке и столбце не имеет ничего общего с текущим элементом, а элементами выше и левее, поэтому для этих элементов может быть инициализировано любое значение, мы выбираем один , инициализируем его 0

④Порядок обхода: поскольку все элементы являются производными от верхнего и левого элементов вместе, порядок обхода наших элементов должен проходить сверху вниз и слева направо.

⑤Распечатайте массив: если мы обнаружим, что окончательный результат неверен, мы можем распечатать массив от начала до конца, чтобы быстро найти ошибку.

код [код + комментарий] выглядит следующим образом:
 

/**
 * @author tongchen
 * @create 2023-04-13 15:26
 */
public class DpBag {
    public static void main(String[] args) {
        int[] weight = {1,3,4};
        int[] value = {15,20,30};
        int bagSize = 4;
        MostWeightDpBag(weight, value, 4);

    }

    /**
     * 五部曲:1.dp[i][j] dp[i]是在0-i内存放东西的策略 j是背包容量为j,则dp[i][j]可以理解为在0-i的物品内和容量为j的背包所能存放的最大容量
     * 2.递推方程:dp[i][j]=math.max(dp[i-1][j],dp[i-1][j-w[i]])3.初始化当背包容量为0时,每一个i都是0,当物品为i时,每个背包能存放的最大价值为i
     * @param
     * @return
     */
    public static void MostWeightDpBag(int[]weight,int []value,int maxSize){
        //创建数组
        int[][]dp=new int[weight.length][maxSize+1];
        //初始化
        for(int i=weight[0];i<=maxSize;++i){
            dp[0][i]=value[0];
        }
        //循环
        //一层循环物品
        for(int i=1;i< weight.length;++i){
            //二层循环背包
            for(int j=1;j<=maxSize;++j){
                dp[i][j]=dp[i-1][j];
                if(weight[i]<=j){
                    dp[i][j]=Math.max(dp[i][j],dp[i][j-weight[i]]+value[i]);
                }
            }
        }
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j <= maxSize; j++) {
                System.out.print(dp[i][j] + "\t");
            }
            System.out.println("\n");
        }
    }
}

4.2 Использование одномерного массива для реализации динамического программирования рюкзака 01 (скользящий массив)

4.2.1 Переход от двумерного массива к одномерному массиву

Прежде чем объяснять реальную деградацию от двумерного массива к одномерному массиву, давайте взглянем на переход от двумерного массива к одномерному массиву: от двумерного массива к одномерному массиву , существенное изменение заключается в том, что мы отменяем элемент i Разница в том, что только нижний индекс j, одномерный массив, представляет максимальное значение, которое может быть размещено под емкостью j. Наиболее существенная причина заключается в том, что элемент dp [i-1][j] напрямую копируется в dp[i][j] Кроме того, давайте проанализируем другие изменения: ①Порядок обхода больше не разрешен, но сначала необходимо пройти по элементам, а затем по емкости. обход емкости, мы должны принять метод обхода емкости от большого к малому ②Об инициализации: определение элементов инициализации больше не копируется из элементов в предыдущей строке, а выбирается (dp[j]) и не выбирается для размещения текущий предмет в рюкзаке (dp[j-weight[i]]+value [i]) принимается во внимание, и берется его максимальное значение, поэтому в процессе сравнения между ними мы не можем повлиять на нашу инициализацию. , а значение dp[j] все еще находится под емкостью j, рюкзак может нести максимальное значение, поэтому инициализация не может быть отрицательной, и она не может влиять на сравнение, поэтому мы инициализируем ее 0 ( наименьшее неотрицательное число)

4.2.2 Повторно проанализируйте проблему и проанализируйте сложность перехода от двумерного массива к одномерному.

Сначала мы анализируем проблему:
мы все еще начинаем с
① индекса элемента рекурсивного массива и значения элемента ② реализации рекурсивного уравнения ③ инициализации массива dp ④ порядка обхода массива dp ⑤ При ошибке происходит, мы быстро печатаем массив dp Точно определить, где возникает проблема, и повысить эффективность отладки.Эти пять частей анализируются:

①dp[j] представляет собой максимальную стоимость предметов, которые могут быть размещены в рюкзаке при j-й вместимости, а j представляет собой вместимость рюкзака.

②Поскольку в этом случае текущий элемент не будет выбран (элемент i-1 будет перенесен непосредственно в i), поэтому наша рекурсивная формула по-прежнему выбирает максимальное значение из выбора и невыбора текущего элемента: dp[ j - Weight[i] ] представляет максимальное значение, которое может нести рюкзак вместимостью j - Weight[i]. dp[j - weight[i]] + value[i] означает рюкзак вместимостью j - вес предмета i плюс стоимость предмета i. (То есть стоимость рюкзака вместимостью j, после помещения предмета i в: dp[j], в это время dp[j] имеет два варианта, один из них — взять свой собственный dp[j], который эквивалентно двумерному массиву dp The dp[i-1][j], то есть не ставить элемент i, нужно взять dp[j - weight[i]] + value[i], то есть , поставить элемент i, указанное брать наибольшее, ведь это самое большое значение, поэтому рекурсивная формула: dp[j]=max(dp[j],dp[j-weight[i]] +значение[i]);

③Инициализация: dp[j] означает: для рюкзака вместимостью j стоимость переносимых предметов может достигать dp[j], тогда dp[0] должно быть равно 0, поскольку максимальная стоимость переносимых предметов рюкзак вместимостью 0 Это 0. Затем массив dp инициализируется значением 0, за исключением положения индекса 0, сколько других индексов необходимо инициализировать? Взгляните на рекурсивную формулу: dp[j] = max(dp[j], dp[j - вес[i]] + значение[i]); массив dp должен быть числом с наибольшим значением при выводе, и инициализированное значение. Это максимальное значение, которое может выдержать рюкзак, поэтому инициализация не может быть отрицательным числом и не может влиять на сравнение, поэтому мы инициализируем его равным 0 (наименьшее неотрицательное число).

④Порядок обхода: в отличие от двумерного массива, мы должны сначала пройти элементы в однобитном массиве, а затем пройти по емкости, и емкость должна пройти от задней части к передней (емкость проходит от большой к маленький): может быть, в это время, товарищи, у меня есть сомнения: почему мы не можем сначала пройти емкость? Почему обход емкости не может быть спереди назад (емкость от большой к меньшей), ответим по очереди: ① Если обход емкости сначала эквивалентен обходу массива столбец за столбцом (обход в порядке столбцов по колонке), мы должны Что необходимо обеспечить, так это то, что максимальное значение, которое может вместить текущая вместимость j, правильное, тогда значения, соответствующие выбору и невыбору текущего предмета в рюкзак, должны тоже правильно: Разбираем рюкзак со следующей картинкой:

Если он проходится по столбцу, то фактически нарушается принцип, согласно которому текущая емкость может вместить политику максимального значения: почему вы так говорите? Потому что, если мы проходим по столбцу, мы возвращаем сцену обратно к двумерному массиву, что эквивалентно прямому копированию элементов предыдущей строки, что нарушает тот факт, что каждый из наших элементов фактически представляет собой максимальное значение, которое может быть размещено. по пунктам текущей мощности.

Если вы считаете, что это все еще немного абстрактно, вы можете распечатать элементы массива, а затем проверить результаты и идеи .

Позвольте мне рассказать о том, почему обход емкости должен проходить от большего к меньшему? Потому что, если вы переходите от малого к большому, это приведет к повторному размещению одного и того же элемента:

Обход в обратном порядке заключается в том, чтобы убедиться, что элемент i помещается только один раз! . Но как только он будет пройден в положительном порядке, элемент 0 будет добавлен несколько раз!

Для примера: вес элемента 0 weight[0] = 1, значение value[0] = 15

Если ход dp[1] = dp[1 - вес[0]] + значение[0] = 15dp[2] = dp[2 - вес[0]] + значение[0] = 30 в это время dp[2] ] уже равно 30, что означает, что элемент 0 был вставлен дважды, поэтому его нельзя пройти в прямом порядке; почему можно гарантировать, что элемент будет вставлен только один раз, проходя в обратном порядке? Обратный порядок заключается в вычислении dp[2]dp[2] = dp[2 - weight[0]] + value[0] = 15 (массив dp был инициализирован 0) dp[1] = dp[1 - weight[0] ]] + value[0] = 15, поэтому он зацикливается от конца к началу, и состояние, полученное каждый раз, не будет совпадать с состоянием, полученным ранее, поэтому каждый элемент берется только один раз. Если у вас все еще есть сомнения, я предлагаю вам распечатать массив один раз, чтобы разобраться во всем процессе обхода.

В соответствии с этим даем код:


/**
 * @author tongchen
 * @create 2023-04-14 18:00
 */
public class RollingDpBag {
    public static void main(String[] args) {

    }

    /**
     * 1.j代表容量为j2.dp[j]代表j容量下存放的最大值2.dp[j]=max(dp[j],dp[j-weight[i]]+value[i])3.dp[i]=0;4.第一层是物品,从上到下,第二层是容量,从左到右
     */
    public static void dpBag(int[]weight,int []value,int maxSize){
        int[]dp=new int[maxSize+1];
        for(int i=0;i< weight.length;++i){
            for(int j=maxSize;j>=weight[i];--j){
                dp[j]=Math.max(dp[j],dp[j-weight[i]]+value[i]);
            }
        }


    }
}

Supongo que te gusta

Origin blog.csdn.net/m0_65431718/article/details/130177137
Recomendado
Clasificación