软考算法-算法篇

news2024/11/23 3:39:55

软考算法

  • 一:故事背景
  • 二:分治法
    • 2.1 概念
    • 2.2 题目描述
    • 2.3 代码实现
    • 2.4 总结提升
  • 三:回溯法
    • 3.1 概念
    • 3.2 题目描述
    • 3.3 代码实现
      • 3.3.1 TreeNode 类
      • 3.3.2 将数组处理成二叉树结构并且返回根节点
      • 3.3.3 进行搜索
    • 3.4 总结提升
  • 四:回溯法-皇后问题
    • 4.1 概念
    • 4.2 画图表示
    • 4.3 代码实现
      • 4.3.1 实现思路
      • 4.3.2 具体代码
    • 4.4 总结提升
  • 五:贪心法
    • 5.1 概念
    • 5.2 画图表示
    • 5.3 代码实现
      • 5.3.1 题目描述
      • 5.3.2 java代码
    • 5.4 总结提升
  • 六:动态规划
    • 6.1 概念
    • 6.2 画图表示
    • 6.3 代码实现
      • 6.3.1 实现思路
      • 6.3.2 具体代码
    • 6.4 总结提升
  • 七:动态规划-0/1背包问题
    • 7.1 概念
      • 7.1.1 例子
      • 7.1.2 限定
    • 7.2 画图表示
    • 7.3 代码实现
    • 7.4 总结提升
  • 总结&提升

一:故事背景

最近正在准备五月份的软件工程师考试,上文我们总结了常用的8中排序算法。本文我们就来盘一盘软考中设计到的其他各种算法,这些算法体现的思想,是我们学习的核心。希望通过此篇文章可以让大家更深刻的理解什么是算法。领会不同算法的精妙之处。

二:分治法

2.1 概念

分治法,顾名思义就是分而治之的意思。就是把一个复杂的问题拆分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

2.2 题目描述

我们使用分支法的思想,在一个有序的数组内,搜索指定的数值。例如:
在下面的数组内搜索值为28的数组
在这里插入图片描述

2.3 代码实现

    public static void main(String[] args) {
        int [] nums ={8,20,28,74,92,188,372,500};
        int targetNumber = 28;
        int i = binarySearch(targetNumber, 0, nums.length, nums);
        System.out.println(i);
    }
    
        /**
     * 二分查找
     *
     * @param targetNumber 要查找的目标数字
     * @param beginIndex 查找区间的起始下标
     * @param endIndex 查找区间的结束下标
     * @param nums 给定的已排序数组
     * @return 如果找到目标数字,返回目标数字的下标;否则返回 -1
     */
    public static int binarySearch(int targetNumber, int beginIndex, int endIndex, int[] nums) {
        // 计算中间位置
        int middleIndex = (beginIndex + endIndex) / 2;
        // 如果找到目标数字,返回目标数字的下标
        if (targetNumber == nums[middleIndex]) {
            return middleIndex;
        }
        // 如果目标数字比中间位置的数大,说明目标数字在右半区间
        if (targetNumber > nums[middleIndex]) {
            return binarySearch(targetNumber, middleIndex, endIndex, nums);
        }
        // 如果目标数字比中间位置的数小,说明目标数字在左半区间
        return binarySearch(targetNumber, beginIndex, middleIndex, nums);
    }

2.4 总结提升

我们的代码实现了一个经典的二分查找算法,使用递归的方式进行查找。以下为详细解释:

  • 在 main 方法中定义了一个数组 nums 和一个目标数字 targetNumber,然后调用了 binarySearch 方法进行查找,并输出查找结果。
  • binarySearch 方法是一个递归函数,用于在已排序数组 nums 中查找目标数字 targetNumber。函数参数中的 beginIndex 和 endIndex 表示查找区间的起始下标和结束下标,nums 表示给定的已排序数组。
  • 首先计算中间位置 middleIndex,如果找到目标数字,返回目标数字的下标 middleIndex。
  • 如果目标数字比中间位置的数大,说明目标数字在右半区间,递归调用 binarySearch 方法,将查找区间的起始下标改为 middleIndex,结束下标不变。
  • 如果目标数字比中间位置的数小,说明目标数字在左半区间,递归调用 binarySearch 方法,将查找区间的结束下标改为 middleIndex,起始下标不变。
  • 如果没有找到目标数字,返回 -1。

该算法的时间复杂度为 O(log n),其中 n 为数组的长度。这是因为每次查找都会将查找区间缩小一半,最多需要查找 log n 次。该算法是一种高效的查找算法,常用于对已排序数组的查找操作。

三:回溯法

3.1 概念

  • 回溯法,可以系统的搜索一个问题的所有解或任一解。
  • 回溯法通常涉及到对问题状态的深度优先搜索,在搜索过程中,算法尝试一步步地构建解决方案,每次决策都会将问题状态转移到下一步,并检查当前状态是否满足问题的要求。如果当前状态满足问题要求,则继续向下搜索;如果不满足要求,则回溯到上一个状态,并尝试其他的决策。

3.2 题目描述

  • 给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

  • 叶子节点 是指没有子节点的节点。
    在这里插入图片描述

    输入:root = [1,2,3,4,5,6,7,8,9]
    输出:[“1->2->4->8”,“1->2->4->9”,“1->2->5”,“1->3->6”,“1->3->7”]

提示:

树中节点的数目在范围 [1, 100] 内
-100 <= Node.val <= 100

3.3 代码实现

我们的代码实现将使用深度优先搜索和回溯的方法进行。

  • 首先从根节点开始,对二叉树进行深度优先遍历。
  • 遍历过程中使用一个字符串构建器 StringBuilder 记录当前路径
  • 每当遍历到叶子节点时,将当前路径添加到结果列表中,并回溯到上一层节点。

3.3.1 TreeNode 类

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

3.3.2 将数组处理成二叉树结构并且返回根节点

public static TreeNode constructTree(int[] nums) {
    if (nums == null || nums.length == 0) {
        return null;
    }
    // 创建根节点
    TreeNode root = new TreeNode(nums[0]);  
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    int i = 1;
    while (i < nums.length) {
        // 取出队头元素
        TreeNode parent = queue.poll();  
        if (i < nums.length && nums[i] != null) {
            // 创建左子节点
            parent.left = new TreeNode(nums[i]);  
            queue.offer(parent.left);
        }
        i++;
        if (i < nums.length && nums[i] != null) {
            // 创建右子节点
            parent.right = new TreeNode(nums[i]);  
            queue.offer(parent.right);
        }
        i++;
    }
    return root;
}

3.3.3 进行搜索

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        // 用于保存所有从根节点到叶子节点的路径
        List<String> result = new ArrayList<String>();  
        // 处理特殊情况,如果根节点为空,则返回空路径列表
        if (root == null) {  
            return result;
        }
        // 开始深度优先搜索
        dfs(root, new StringBuilder(), result);  
        return result;
    }

    private void dfs(TreeNode node, StringBuilder path, List<String> result) {
        // 如果当前节点为空,返回
        if (node == null) {  
            return;
        }
        // 保存当前路径的长度
        int len = path.length();  
        // 如果当前节点是叶子节点,则将当前路径添加到结果列表中
        if (node.left == null && node.right == null) {  
            result.add(path.append(node.val).toString());
            // 恢复当前路径的长度
            path.setLength(len);  
            return;
        }
        // 将当前节点添加到路径中,并加上箭头
        path.append(node.val).append("->");  
        // 递归遍历左子树
        dfs(node.left, path, result);
        // 递归遍历右子树  
        dfs(node.right, path, result);
        // 恢复当前路径的长度  
        path.setLength(len);  
    }
}

3.4 总结提升

  • 这个了例子里,我们需要将当前节点添加到当前路径中
  • 然后递归遍历该节点的左右子树
  • 在回溯的过程中,需要将当前节点从当前路径中删除,同时选择其它分支继续搜索,直到找到所有的路径。
  • 本问题体现了回溯算法的核心思想,即通过试错的方式搜索问题的解空间。

四:回溯法-皇后问题

4.1 概念

  1. 按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
  2. n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

4.2 画图表示

可行解示例:
在这里插入图片描述
过程构造示例:
在这里插入图片描述

4.3 代码实现

4.3.1 实现思路

宏观:
使用深度搜索的方法,按照先行后列的顺序,查看每一个位置是否满足条件。
微观:
定义二维数组表示棋盘,定义一个变量n表示几个皇后
定义一个方法用来判断当前摆放的皇后是否与之前的皇后冲突(同列、左上方,右上方),冲突返回0;否则,返回1,表示此位置可以放置皇后。
定义一个递归函数,尝试在当前行放置皇后。

4.3.2 具体代码

package com.lsn.NQueen;

public class NQueens {

    // 定义一个二维数组表示棋盘
    int[][] board;

    // 定义一个变量表示几个皇后
    int n;

    // 构造函数,初始化棋盘和n
    public NQueens(int n) {
        board = new int[n][n];
        this.n = n;
    }

    // 判断当前摆放的皇后是否与之前的皇后冲突
    public boolean isSafe(int row, int col) {
        int i, j;

        // 检查当前列是否有皇后
        for (i = 0; i < row; i++) {
            if (board[i][col] == 1) {
                return false;
            }
        }

        // 检查左上方是否有皇后
        for (i = row, j = col; i >= 0 && j >= 0; i--, j--) {
            if (board[i][j] == 1) {
                return false;
            }
        }

        // 检查右上方是否有皇后
        for (i = row, j = col; i >= 0 && j < n; i--, j++) {
            if (board[i][j] == 1) {
                return false;
            }
        }

        // 如果都没有冲突,则返回true
        return true;
    }

    // 递归函数,尝试在当前行放置皇后
    public boolean solve(int row) {
        // 如果所有行都已经摆放完毕,则返回true(终止条件,收集结果)
        if (row == n) {
            return true;
        }

        // 尝试在当前行的每一列放置皇后(单层逻辑,处理节点)
        for (int col = 0; col < n; col++) {
            // 判断当前位置是否安全
            if (isSafe(row, col)) {
                // 如果安全,则将皇后放置在当前位置
                board[row][col] = 1;

                // 递归调用solve函数,尝试在下一行放置皇后
                if (solve(row + 1)) {
                    return true;
                }

                // 如果下一行无法放置皇后,则回溯到当前行,重新尝试放置皇后(撤销处理节点的情况)
                board[row][col] = 0;
            }
        }

        // 如果当前行的每一列都无法放置皇后,则返回false
        return false;
    }

    // 打印棋盘
    public void printBoard() {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                System.out.print(board[i][j] + " ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        // 创建一个NQueens对象,并初始化规模为8
        NQueens nQueens = new NQueens(3);

        // 调用solve函数,尝试解决N皇后问题
        if (nQueens.solve(0)) {
            // 如果找到了解,则打印棋盘
            nQueens.printBoard();
        } else {
            // 如果没有找到解,则打印无解
            System.out.println("无解.");
        }
    }
}

通过上述代码,我们可以搜索到皇后的一个可行解。

4.4 总结提升

皇后问题也是回溯法的一种体现。它使用了回溯法提高效率的减枝策略,不符合条件的就不必向下进行递归,大大的提高了算法的效率。相较于上文给出二叉树回溯法的例子,它更加的复杂,体现的是一个二维的搜索,但是其核心思想都是回溯。

五:贪心法

5.1 概念

总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,它所做出的仅仅是在某种意义上的局部最优解。

  • 贪心法体现:Djikstra(迪杰斯特拉)算法,构造最小生成树的Prim(普里姆)算法和Kruskal(克鲁斯卡尔)算法
  • 算法目的 将一个大的任务拆分成若干个小的任务逐个解决来完成拆分之前总任务的效果。
  • 算法过程
  1. 把求解的问题分成若干个子问题
  2. 对每个子问题求解,得到子问题的局部最优解
  3. 把子问题的解局部最优解合成原来问题的一个解
  • 该算法存在的问题
  1. 不能保证求得的最后解是最佳的
  2. 只能求满足某些约束条件的可行解的范围

5.2 画图表示

在这里插入图片描述
在这里插入图片描述

5.3 代码实现

5.3.1 题目描述

给你一个整数数组 ,请你找出一个具有的连续子数组最大和(子数组最少包含一个元素),返回其最大和。
注意:子数组是数组中的一个连续部分。

5.3.2 java代码

public class MaximumSubarray {
    public int maxSubArray(int[] nums) {
        int currentSum = 0;  // 当前子数组的和
        int maxSum = Integer.MIN_VALUE;  // 最大子数组的和

        // 遍历数组中的每个元素
        for (int num : nums) {
            // 如果当前子数组的和加上当前元素小于当前元素本身,那么当前子数组的和不再对后续子数组产生正向影响,重新开始一个新的子数组
            currentSum = Math.max(num, currentSum + num);
            // 更新最大子数组的和
            maxSum = Math.max(maxSum, currentSum);
        }

        return maxSum;
    }

    public static void main(String[] args) {
        int[] nums = { -2, 1, -3, 4, -1, 2, 1, -5, 4 };
        MaximumSubarray solution = new MaximumSubarray();
        int maxSum = solution.maxSubArray(nums);
        System.out.println("最大和为:" + maxSum);
    }
}

在以上代码中,我们使用贪心法的思想,从数组的第一个元素开始遍历,计算当前子数组的和 currentSum 和最大子数组的和 maxSum。在每次遍历过程中,我们用 Math.max(num, currentSum + num) 更新 currentSum 的值,这样可以保证 currentSum 始终是以当前元素结尾的最大子数组的和。同时,我们用 Math.max(maxSum, currentSum) 更新 maxSum 的值,以保证最终得到的 maxSum 是整个数组中的最大子数组和。

5.4 总结提升

  • 贪心法是一种求解问题的策略,其核心思想是在每一步选择中都采取当前状态下最优的选择,从而希望最终达到全局最优解。贪心法通常适用于满足「最优子结构」和「贪心选择性质」的问题。

  • 总结贪心法的思想可以归纳为以下几点:

  1. 最优子结构(Optimal Substructure):问题的最优解包含了子问题的最优解。这意味着通过选择当前最优的解,可以将原问题拆分为更小的子问题,并且每个子问题的最优解可以组合成原问题的最优解。

  2. 贪心选择性质(Greedy Choice Property):在每一步选择中,采取当前最优的选择,即局部最优解,希望通过局部最优解达到全局最优解。这意味着贪心法不会回退或撤销之前的选择,而是根据当前情况做出决策。

  3. 不可回退(不考虑后效性):贪心法做出的选择一旦确定就不可更改,不会对之前的选择进行修改。它只关注当前状态下的最优选择,并相信通过每一步的最优选择能够达到整体最优。

  • 需要注意的是,贪心法并不适用于所有问题,因为某些问题无法通过贪心的方式获得最优解。在使用贪心法时,需要经过严格的推导和证明,以确保其正确性。

  • 贪心法的优点是简单、高效,并且通常可以在较短的时间内得到一个近似最优解。然而,其缺点是不能保证一定能够得到全局最优解,因此在某些情况下可能得到次优解或错误的结果。

总而言之,贪心法通过选择当前最优解来逐步构建问题的解决方案,希望通过局部最优解达到全局最优解。它是一种简单而高效的求解问题的策略,但需要仔细分析问题的性质和特点,以确定贪心选择和最优子结构的存在性。

六:动态规划

6.1 概念

将待求问题划分为若干个子问题,按划分的顺序求解子阶段问题,前一个子问题的解,为后一个子问题的求解提供了有用的信息(最优子结构)。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其它局部解。依次解决各个子问题,最后求出原问题的最优解

6.2 画图表示

这里以一个斐波那契数列举例
在这里插入图片描述
在这里插入图片描述

6.3 代码实现

6.3.1 实现思路

  1. 定义一个要求的斐波那契数
  2. 判断求的斐波那契数是否小于等于1,是的话直接返回求的斐波那契数,否的话,则创建一个数组,用来记录子问题的解
  3. 初始化第一个斐波那契数值,和第二个斐波那契数值。求第三个斐波那契数值的时候,把前两个下标的值进行相加,得出第三个斐波那契的数值。依次按照这种方式,最终得出需要求的斐波那契数值

6.3.2 具体代码

public class Fibonacci {

    public static void main(String[] args) {
        int numberArr = 10; // 求第10个斐波那契数
        int result = fibonacci(numberArr); // 第10个斐波那契数的值
        System.out.println("第10个斐波那契数值为:"+ result); // 输出第10个斐波那契数的值
    }
    /**
     * 斐波那契数列的动态规划算法
     * @paramn  第numberArr个斐波那契数,dp为数据处理后的值
     * @return 第numberArr个斐波那契数的值
     */
    public static int fibonacci(int numberArr) {
        if (numberArr <= 1) {
            // 当numberArr小于等于1时,直接返回numberArr
            return numberArr;
        }

        //创建数组,用于记录子问题的解
        //dp为数据处理后的值
        int[] dp = new int[numberArr + 1];
        dp[0] = 0; // 初始化第一个数
        dp[1] = 1; // 初始化第二个数

        // 递推求解子问题
        for (int i = 2; i <= numberArr; i++) {
            //dp[i]:i为该求下标
            //dp[i-1]:i为该求下标-1的下标的值
            //dp{[i-2]:i为该求下标-2的下标的值
            //算出两个下标的值后进行相加,最后得出该求下标的值
            dp[i] = dp[i - 1] + dp[i - 2];
            System.out.println("第"+i+"轮numberArr的值为"+ dp[i]);
        }
        // 返回第numberArr个斐波那契数的值
        return dp[numberArr];
    }
}

6.4 总结提升

动态规划(Dynamic Programming)是一种将复杂问题分解为更小子问题并以递推的方式求解的方法。它通常适用于具有「最优子结构」和「重叠子问题」性质的问题。

简单总结动态规划法的思想可以归纳为以下几点:

  1. 最优子结构(Optimal Substructure):问题的最优解可以通过子问题的最优解来构建。这意味着原问题的解可以由相关子问题的解组合而成。

  2. 重叠子问题(Overlapping Subproblems):在递归求解过程中,许多子问题会被重复计算多次。动态规划利用记忆化或者自底向上的方法,将子问题的解存储起来以避免重复计算,提高效率。

  3. 状态转移方程(State Transition Equation):动态规划通过定义状态和状态之间的转移方程来描述问题的求解过程。状态转移方程表示问题的当前状态与前一状态之间的关系,通过状态转移方程来推导出最优解。

  4. 自底向上的计算顺序:动态规划通常使用自底向上的计算顺序,从最小规模的子问题开始逐步求解,直到推导出原问题的解。这样可以保证所有的子问题在求解时都已经得到解决。

  5. 动态规划的优点是能够求解复杂问题并得到最优解,避免了重复计算,提高了计算效率。然而,动态规划的缺点是需要较大的空间来存储中间结果,有时会牺牲一定的空间复杂性来换取时间上的优化。

总而言之,动态规划通过将复杂问题分解为更小的子问题并以递推的方式求解,利用最优子结构和重叠子问题的性质,通过状态转移方程来推导最优解。它是一种常用的求解优化问题的方法,能够高效地求解多种问题。

七:动态规划-0/1背包问题

7.1 概念

7.1.1 例子

用一个例子来说明0/1背包问题:
现有四个物品,小偷背包总容量为8,怎么样可以偷走价值最多的物品
物品编号:1 2 3 4
物品重量:2 3 4 5
物品价值:3 4 5 8

7.1.2 限定

各种限定条件解决问题:

  • 部分背包问题:所有物品是可再分的,即允许将某件物品的一部分(例如 1/3)放入背包;
  • 0-1 背包问题:所有物品不可再分,要么整个装入背包,要么放弃,不允许出现“仅选择物品的 1/3 装入背包”的情况;
  • 完全背包问题:不对每一件物品的数量做限制,同一件物品可以选择多个装入背包;

7.2 画图表示

在这里插入图片描述

7.3 代码实现

    public static void main(String[] args) {

        String[] nameArr = {"鞋子", "音响", "电脑"};

        // 商品重量数组
        int[] weightArr = {1/*鞋子*/, 4/*音响*/, 3/*电脑*/};

        // 商品价格数组
        int[] priceArr = {1500/*鞋子*/, 3000/*音响*/, 2000/*电脑*/};

        // 背包容量
        int packageCapacity = 4;

        backpackWithoutRepeat(nameArr, weightArr, priceArr, packageCapacity);
    }



    private static void backpackWithoutRepeat(String[] nameArr, int[] weightArr, int[] priceArr, int packageCapacity) {
        /**
         * 声明一个能装入 0、1、2、3磅......的背包的二维价格表;举例:就好比 v数组是表2的数据
         */
        int[][] nameBackpack = new int[nameArr.length + 1][packageCapacity + 1];

        // 构建可能装入背包的二维数组
        // 值为0时说明不会装进背包, 值为1说明可能装入背包
        int[][] contentArr = new int[nameArr.length + 1][packageCapacity + 1];

        /**
         * 为什么i一开始是1不是0?看表2的数据,是不是第一行全是0啊
         */
        for (int i = 1; i < nameBackpack.length; i++) {

            /**
             * 为什么j一开始是1不是0?看表2的数据,是不是第一列全是0啊
             */
            System.out.println(nameBackpack[i]);
            for (int j = 1; j < nameBackpack[i].length; j++) {

                /**
                 * 文章中当 w[i] > j 时,就有 nameBackpack[i][j] = nameBackpack[i-1][j];
                 * 因为我们程序i是从1开始的,因此原来公式中的w[i]修改成w[i-1];
                 * 当前商品 > 背包容量, 取同列上一行数据
                 */
                if (weightArr[i - 1] > j) {
                    nameBackpack[i][j] = nameBackpack[i - 1][j];
                } else {
                    /**
                     *  当前商品 <= 背包容量, 对两部分内容进行比较;
                     *  第一部分, 该列上一行数据
                     */
                    int onePart = nameBackpack[i - 1][j];

                    /**
                     * 还记得文章中写的 当j >= w[i] 时,有 nameBackpack[i][j]=max{nameBackpack[i-1][j],nameBackpack[i-1][j-w[i]]+nameBackpack[i]} 这个公式成立吗?
                     * priceArr[i - 1]: 当前商品价格;
                     * w[i - 1]: 当前商品重量;
                     * j - w[i - 1]: 去掉当前商品, 背包剩余容量;
                     * 不可重复: nameBackpack[i - 1][j - w[i - 1]]: 在上一行, 取剩余重量下的价格最优解;
                     */
                    int otherPart = priceArr[i - 1] + nameBackpack[i - 1][j - weightArr[i - 1]];

                    /**
                     *  取最大值为当前位置的最优解
                     */
                    nameBackpack[i][j] = Math.max(onePart, otherPart);

                    /**
                     *  如果最优解包含当前商品, 则表示当前商品已经被使用, 进行记录
                     */
                    if (otherPart == nameBackpack[i][j]) {
                        contentArr[i][j] = 1;
                    }
                }
            }
        }


        // 不能重复的场景中
        // 如果该位置的标志位为1, 说明该商品参与了最终的背包添加
        // 如果该位置的标志位为0, 即使该位置的价格为最大价格, 也是从其他位置引用的价格
        // 因为不能重复, 所以每行只取一个数据参与最终计算, 并只判断在最大位置该商品是否参与
        // 该最大位置会随着已经遍历出其他元素而对应不断减小, 直到为0


        // 二维数组最后一个元素必然是最大值, 但是需要知道该最大值是自身计算的 还是比较后引用其他的
        int totalPrice = 0;
        // 最大行下标数, 即商品数
        int maxLine = contentArr.length - 1;
        // 最大列下标数, 即重量
        int maxColumn = contentArr[0].length - 1;
        for (;maxLine > 0 && maxColumn > 0;) {
            // 等于1表示在该位置该商品参与了计算
            if (contentArr[maxLine][maxColumn] == 1) {
                // 遍历后, 对重量减少, 下一次从剩余重量中取参与商品
                maxColumn -= weightArr[maxLine - 1];
                totalPrice += priceArr[maxLine - 1];
                System.out.println(nameArr[maxLine - 1] + "加入了背包");
            }
            // 因为不能重复
            // 所以如果该商品参与了背包容量, 则肯定剩余的最大位置处参与,
            // 否则跟该数据无关, 直接跳过
            maxLine--;
        }
        System.out.println("背包可容纳的最大价值: " + totalPrice);
    }


7.4 总结提升

动态规划的思想可以用以下步骤来解决01背包问题:

  • 定义状态:设dp[i][j]表示在前i个物品中,背包容量为j时的最大总价值。
  • 初始化:将dp数组初始化为0,即dp[i][j]=0,其中0≤i≤N,0≤j≤W。
  • 状态转移方程:对于第i个物品,有两种选择:放入背包或不放入背包。
  • 如果wi > j,即当前物品的重量大于背包容量,无法放入背包,则dp[i][j] = dp[i-1][j],即不放入该物品时的最大价值与前i-1个物品相同。
  • 如果wi ≤ j,即当前物品的重量小于等于背包容量,可以选择放入或不放入背包。比较两种情况下的最大价值,取较大值:
  • 放入背包:dp[i][j] = dp[i-1][j-wi] + vi,即放入当前物品的价值加上前i-1个物品中容量为j-wi时的最大价值。
  • 不放入背包:dp[i][j] = dp[i-1][j],即不放入当前物品时的最大价值。
  • 遍历计算:使用双重循环遍历物品和背包容量,根据状态转移方程更新dp数组的值。
  • 结果输出:最终的最大总价值为dp[N][W],其中N为物品的个数,W为背包的容量。

总结&提升

通过对算法的学习,利于锻炼我们的逻辑思维,并且指导开发,希望此篇博客可以让大家学会这几种经典的算法,领略其中的思想,应用到实践中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/535592.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据分析之道-Matplotlib(四)】Matplotlib散点图

文章目录 专栏导读1、Matplotlib散点图语法2、Matplotlib散点图设置图标大小3、Matplotlib散点图自定义点颜色4、Matplotlib散点图设置两组散点图5、Matplotlib散点图使用随机数来设置散点图6、Matplotlib散点图显示颜色条 Colormap 专栏导读 ✍ 作者简介&#xff1a;i阿极&…

Github Copilot Chat的规则泄露,详细分析这31条规则

GitHub Copilot 是一款由 GitHub 和 OpenAI 共同开发的人工智能编程助手。它是一种基于机器学习的代码自动完成工具&#xff0c;旨在帮助开发人员更高效地编写代码。 GitHub Copilot Chat是GitHub Copilot的一部分&#xff0c;它是一个基于人工智能的编程助手&#xff0c;由Op…

怎么取消只读模式?硬盘进入只读模式怎么办?

案例&#xff1a;电脑磁盘数据不能修改怎么办&#xff1f; 【今天工作的时候&#xff0c;我想把最近的更新的资料同步到电脑上的工作磁盘&#xff0c;但是发现我无法进行此操作&#xff0c;也不能对磁盘里的数据进行改动。有没有小伙伴知道这是怎么一回事&#xff1f;】 在使…

文件夹中不显示SolidWorks缩略图的解决办法

在文件夹中显示SolidWorks缩略图能帮助我们快速找到需要打开的图纸文件&#xff0c;但是有时候装上SolidWorks之后在文件夹中并不显示图纸文件的缩略图&#xff0c;解决办法如下&#xff1a; 1.打开SolidWorks&#xff0c;点击“选项—系统选项—普通”&#xff0c;勾选“在资…

项目采购管理

一、规划采购管理(编制采购管理计划) 记录采购决策、明确采购方法、识别潜在卖方的过程 作用:确定是否需要外部支持,如果需要,则还要决定采购什么、如何采购、采购多少、以及何时采购。 输入: 组织过程资产 合同类型:一、总价合同;二、成本补偿合同;三、工料合同 …

基于html+css的图展示75

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

python连接kafka

背景&#xff1a;读取TXT文件&#xff0c;加载到kafka中&#xff0c;然后通过logstash消费kafka中的数据加载到es中 第一步&#xff1a;导入相应的依赖包 pip install kafka-python pip install loguru pip install msgpack第二步&#xff1a;编写连接kafka的代码 # -*- …

chatGPT 中科院学术优化 超详细安装教程

1.下载 项目地址&#xff1a;https://github.com/binary-husky/chatgpt_academic 第一种&#xff1a;可以直接下载zip安装包&#xff0c;然后直接解压。 第二种&#xff1a;采用git来下载 git clone https://github.com/binary-husky/chatgpt_academic.git cd chatgpt_acad…

全新推出Bard,谷歌google或许可以靠它打败微软OpenAI ChatGPT

目录 前言Bard优势Bard新功能更直观的Bard互动Bard深度集成google search“Help me write” in Gmail谷歌地图路线的全新沉浸式视图谷歌照片全新Magic Editor体验Bard与其他服务的结合谷歌为Android开发者推出AI编码机器人其他 总结参考资料其它资料下载 前言 截止到目前&…

LSTM-理解 Part-2(RNN的局限性)

之前写过一部分LSTM-理解 Part-1&#xff08;RNN&#xff1a;循环神经网络&#xff09; 这是其中的第二部分&#xff0c;阐述RNN的局限性。 The Problem of Long-Term Dependencies 长期依赖问题 长期依赖问题指的是在序列数据中&#xff0c;某些元素之间存在着较长时间的依赖…

项目开发任务单发布规范-Tower

前言 这是针对低权情况下&#xff0c;美术组内使用的敏捷快速任务单&#xff0c;特点是便捷快速。 选择Tower的原因是因为当年他免费&#xff08;如果用飞书合集工具效果更好&#xff09; 在游戏开发中&#xff0c;选择一个合理的任务单工具&#xff0c;并规范任务单具体内容&a…

免费的Cloudflared实现外网访问群晖(续)

这两天可能中了甲流&#xff0c;因为做了抗原始终是一条杠 老婆是前天晚上开始发烧的&#xff0c;我则是从昨天中午开始的&#xff0c;昨晚是 37.8℃&#xff0c;今早起来是 38℃&#xff0c;症状就是肌肉酸痛&#xff0c;头有点昏昏沉沉的&#xff0c;公众号后台、博客上都积…

大佬强推:高速PCB Layout设计技巧及经验

高速PCB Layout设计是电子设计的重要环节&#xff0c;它的好坏&#xff0c;将直接影响到电路板的性能和可靠性&#xff0c;而且很多工程师在学习高速PCB Layout时非常容易走歪路&#xff0c;为了少走歪路&#xff0c;本文将根据多位名师的教学内容&#xff0c;归纳总结整理出这…

pycharm pyqt5 gui designer 安装及配置使用

参考文章&#xff1a; https://blog.csdn.net/wshyb0314/article/details/127916084 https://zhuanlan.zhihu.com/p/32259868 一、安装pyqt5、pyqt5-tools(QtDesigner在这个包里) pip install pyqt5 -i https://pypi.douban.com/simple --trusted-host pypi.douban.com pip i…

怎么给图片加水印?

怎么给图片加水印&#xff1f;不管我们是在平时的工作还是生活中&#xff0c;很多小伙伴都喜欢在各种平台上分享自己拍摄的照片。然而&#xff0c;在收到赞美和点赞之时&#xff0c;我们是否也会因为担心别人转发或滥用图片而感到困扰呢&#xff1f;要解决这个问题非常简单&…

FL Studio2023水果音乐制作入门教程

“没有早期音乐教育&#xff0c;干什么事我都会一事无成”。这并非某位音乐家精心熬制的心灵鸡汤&#xff0c;而是出自物理学家爱因斯坦之口&#xff0c;朋友们没有看错&#xff0c;就是那个被称为二十世纪伟大科学家的爱因斯坦&#xff0c;所以&#xff0c;别不信哦&#xff0…

[FMC150]基于VITA57.1 的2 路125MSPS AD 采集、2 路250MSPS DA 回放FMC 子卡模块(AD9268/AD9747)

板卡概述 FMC150_V30 是一款基于VITA57.1 规范的2 路125MSPS 采样率16 位分辨率AD 采集、2 路250MSPS 采样率16 位分辨率DA回放FMC 子卡模块。该模块遵循VITA57.1 规范&#xff0c;可直接与符合VITA57.1 规范的FPGA 载卡配合使用&#xff0c;板卡ADC 器件采用ADI 公司的AD9268…

上传成功但是在app管理中心找不到版本提交的解决方法

转载&#xff1a;Appuploader 常见错误及解决方法 Appuploader常见错误及解决方法 问题解决秘籍 遇到问题&#xff0c;首先请登录苹果开发者官网检查账号是否有权限&#xff0c;是否被停用&#xff0c;是否过期&#xff0c;是否有协议需要同意&#xff0c;并且在右上角切换账…

Golang笔记:使用http包实现基础WebServer功能

文章目录 目的监听请求并响应请求解析进行响应静态文件服务总结 目的 WebServer是一种非常常用的功能&#xff0c;Golang的高并发特性在处理此类工作中也有较大的优势&#xff0c;同时借助标准库中的 net/http 包可以非常快速的编写WebServer应用。这篇文章将简单记录下相关内…

商业智能上阵,城商行突围数字经济时代

作者 | 曾响铃 文 | 响铃说 ChatGPT爆火&#xff0c;究竟带来了什么&#xff1f; 有人说&#xff0c;它以一种面向C端的直观方式&#xff0c;让普罗大众第一次直观感受到“智能化”的能力和价值。 只要大胆“提出要求”&#xff0c;一个智能化的应用就能够“给出回应”&…