软考算法-算法篇

软考算法

  • 一:故事背景
  • 二:分治法
    • 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为背包的容量。

总结&提升

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

头歌计算机组成原理实验—运算器设计(7) 第7关:6位有符号补码阵列乘法器

第7关&#xff1a;6位有符号补码阵列乘法器 实验目的 帮助学生掌握补码阵列乘法器的实现原理。 视频讲解 实验内容 在 Logisim 中打开 alu.circ 文件&#xff0c;在6位补码阵列乘法器中利用5位阵列乘法器以及求补器等部件实现补码阵列乘法器&#xff0c;实验框架如图所示&a…

【Linux】进程信号

目录 一、信号概念 二、信号捕捉预备知识 三、产生信号 1、通过终端按键 Core Dump 概念 Core Dump 用法 2、系统调用 2.1、kill 2.2、raise 2.3、abort 3、软件条件 4、硬件异常 4.1、除0 4.2、野指针 四、保存信号 1、信号其他相关概念 2、内核中的表示 3、…

【全网首测】5G随身Wi-Fi —— 中兴U50 Pro

说到随身Wi-Fi&#xff0c;大家应该都不陌生。 它是一个专门将移动信号转换成Wi-Fi信号的设备&#xff0c;经常被用于旅行和出差场景&#xff0c;也被人们亲切地称为“上网宝”。 现在&#xff0c;我们已经全面进入了5G时代&#xff0c;随身Wi-Fi也升级迭代&#xff0c;出现了支…

一个有趣的avs编码器(注意,是avs,而不是avs2噢)

本章附件是一个清华大学写的关于avs编解码器: https://download.csdn.net/download/weixin_43360707/87793302 该编码器遵循了stuffing bit: 打开文件夹后&#xff0c;如下&#xff1a; 可以看出这个是个跨平台的工程&#xff0c;提供了windows vs2015的工程文件sln&#x…

【最新可用】chatGPT镜像网站国内使用,免费稳定!

新建了一个网站 https://ai.weoknow.com/ 每天给大家更新可用的国内可用chatGPT 2023.5.8新增一个 ChatGPT 国内免翻版 【网站名称】&#xff1a;Chat GPT Ai 【使用环境】&#xff1a;移动端/电脑网页端 ChatGPT是一款功能强大的免费在线聊天机器人&#xff0c;具有人工智能…

网络编程(TCP与UDP协议)

文章目录 1. 网络编程1.1 软件架构1.2 网络基础 2. 网络通信要素2.1 如何实现网络中的主机互相通信2.2 通信要素一&#xff1a;IP地址和域名2.2.1 IP地址2.2.2 域名 2.3 通信要素二&#xff1a;端口号2.4 通信要素三&#xff1a;网络通信协议 3. 传输层协议&#xff1a;TCP与UD…

机器人工程学习和研究的结构性失衡

结论&#xff1a;无解&#xff0c;谁是那屈指可数的幸运者/(ㄒoㄒ)/~~ 供给&#xff1a;培养的机器人工程专业人才 需求&#xff1a;市场企业主体招聘的相关人才 不匹配&#xff0c;错配&#xff0c;导致供给无效。 机器人工程学习和研究的结构性失衡可能是由多种原因导致的…

Qt6之万能数据类型QVariant详解

QVariant&#xff0c;被称为万能数据类型&#xff0c;实际上它是类似C的联合union类型。简单的说自定义性能强就像一个盒子几乎可以让你放任意的qt类型&#xff0c;同时可以轻松构造任意类型的任意复杂数据结构&#xff0c;但请注意复杂类型意味着性能和效率的让步。 qt6在文档…

自然语言处理与其Mix-up数据增强方法报告

自然语言处理与其Mix-up数据增强方法 1绪论1.课题背景与意义1.2国内外研究现状 2 自然语言经典知识简介2.1 贝叶斯算法2.2 最大熵模型2.3神经网络模型 3 Data Augmentation for Neural Machine Translation with Mix-up3.1 数据增强3.2 对于神经机器翻译的软上下文的数据增强3.…

2023年市场规模将超147亿美元,中国人工智能产业的“风口”来了吗?

2023年IDC中国ICT市场趋势论坛于5月10日召开&#xff0c;会议重点探讨了人工智能、工业互联网、网络安全、大数据、云计算等领域&#xff0c;并强调了智能终端、智慧城市和半导体等行业的前景。 IDC预计&#xff0c;中国人工智能市场规模在2023年将超过147亿美元&#xff0c;到…

springboot+jsp高校社交校友交流平台的设计与实现

在学校里我们结识了很多朋友。当我们毕业离校走上各自的人生道路&#xff0c;这份友谊将成为宝贵的人生精神财富。但世事变迁&#xff0c;或许我们原本留下的联系方式已经不能再用&#xff0c;使得朋友之间失去联系&#xff0c;更别提相聚&#xff0c;这份精神财富也将丢失。这…

python字典

和列表相同&#xff0c;字典也是许多数据的集合&#xff0c;属于可变序列类型。不同之处在于&#xff0c;它是无序的可变序列&#xff0c;其保存的内容是以“键值对”的形式存放的。 字典类型是Python中唯一的映射类型。“映射”是数学中的术语&#xff0c;简单理解&#xff0…

点亮未来明灯,引领绿色革命

随着全球气候变化日趋严重&#xff0c;能源转型成为解决气候问题和提高全球能源安全合理性的必要措施之一。可持续能源技术因其对环境的友好性和可再生性而成为了当前热点话题。可持续能源技术已经成为人们日益关注的焦点。这项技术可以帮助我们减少对化石燃料的依赖&#xff0…

机械大专生能学会云计算吗,完全零基础的

机械大专生能学会云计算吗&#xff0c;完全零基础的 正常来说&#xff0c;大专及以上学历都能学会云计算&#xff0c;但是会和满足就业需求是两回事哈。如果你想通过学习就业&#xff0c;就需要根据当下相关岗位的普遍技术需求以及其他方面的要求&#xff0c;来针对性的学习和提…

chatgpt赋能Python-pythondoc

PythonDoc&#xff1a;了解Python的文档工具 什么是PythonDoc&#xff1f; PythonDoc是Python官方文档。它是Python编程语言的权威指南和参考资料&#xff0c;提供丰富而全面的信息&#xff0c;从基础语法到高级主题&#xff0c;都有许多实用和详细的文档说明。 PythonDoc的…

【重新定义matlab强大系列七】利用matlab函数ischange查找数据变化点

&#x1f517; 运行环境&#xff1a;matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

水电站泄洪监测预警系统解决方案

一、方案背景 每到汛期水库或电站泄洪时&#xff0c;下游各责任单位接到泄洪通知后&#xff0c;组织人员对下游河道进行巡查&#xff0c;耗费大量的人力物力&#xff0c;且信息传递效果不明显。巡查办法老套单一&#xff0c;信息传递速度慢、覆盖范围小&#xff0c; 无法让沿途…

oa是什么意思?oa系统哪个好用?

一、oa是什么意思 oa&#xff08;Office Automation办公自动化&#xff09;是一种将智能化科技应用于企业管理中的应用系统。它可以通过电脑网络、互联网等技术手段&#xff0c;将企业的各种业务流程、各种业务数据进行集成和处理&#xff0c;将各种业务流程和各种业务数据统一…

在 React 中使用 highlight.js 和 Clipboard.js 实现代码块和复制功能

参考链接&#xff1a;https://blog.csdn.net/huangjuan0229/article/details/130319050 在前端开发中&#xff0c;代码块高亮和复制功能是十分常见的需求。而在 React 中&#xff0c;常用的代码高亮库是 highlight.js&#xff0c;常用的复制库是 Clipboard.js。本篇文章将介绍…

kicad学习笔记6:kicad启动及其grid参数设置和修改

1。kicad启动&#xff1a; single_top.cpp 启动函数&#xff1a; 1。 IMPLEMENT_APP( APP_SINGLE_TOP )2。 PGM_SINGLE_TOP::OnPgmInit()3。 PGM_BASE::InitPgm2。kicad参数 grid参数定义&#xff1a; struct GRID_SETTINGS {bool axes_enabled;std::vector<wxString&…