ToB企服应用市场:ToB评测及商务社交产业平台

标题: 分治算法、动态规划、贪心算法、分支限界法和回溯算法的深度对比 [打印本页]

作者: 写过一篇    时间: 3 天前
标题: 分治算法、动态规划、贪心算法、分支限界法和回溯算法的深度对比

1. 分治算法 (Divide and Conquer)

核心思想


典型应用


1.分治算法(归并排序)

  1. import java.util.Arrays;
  2. public class DivideAndConquer {
  3.     // 归并排序的主函数
  4.     public static void mergeSort(int[] arr) {
  5.         // 如果数组为空或长度小于等于1,则直接返回
  6.         if (arr == null || arr.length <= 1) return;
  7.         
  8.         // 创建一个临时数组用于合并过程中存储数据
  9.         int[] temp = new int[arr.length];
  10.         
  11.         // 调用递归的归并排序函数,传入左边界、右边界和临时数组
  12.         mergeSort(arr, 0, arr.length - 1, temp);
  13.     }
  14.     // 递归的归并排序函数,负责数组的分解和合并
  15.     private static void mergeSort(int[] arr, int left, int right, int[] temp) {
  16.         // 递归终止条件:当left >= right时,数组已经是单元素或空数组,不需要再分解
  17.         if (left < right) {
  18.             // 计算中间点
  19.             int mid = left + (right - left) / 2;
  20.             // 分别对左半部分和右半部分递归排序
  21.             mergeSort(arr, left, mid, temp);      // 递归排序左半部分
  22.             mergeSort(arr, mid + 1, right, temp); // 递归排序右半部分
  23.             // 合并两个已排序的部分
  24.             merge(arr, left, mid, right, temp);   // 合并两部分
  25.         }
  26.     }
  27.     // 合并两个已排序的子数组
  28.     private static void merge(int[] arr, int left, int mid, int right, int[] temp) {
  29.         int i = left, j = mid + 1, k = 0;
  30.         
  31.         // 比较并合并两个部分,直到一个部分被完全合并
  32.         while (i <= mid && j <= right) {
  33.             if (arr[i] < arr[j]) {
  34.                 // 如果左部分元素小于右部分元素,将左部分元素加入临时数组
  35.                 temp[k++] = arr[i++];
  36.             } else {
  37.                 // 否则将右部分元素加入临时数组
  38.                 temp[k++] = arr[j++];
  39.             }
  40.         }
  41.         // 如果左部分还有剩余,直接加入临时数组
  42.         while (i <= mid) temp[k++] = arr[i++];
  43.         
  44.         // 如果右部分还有剩余,直接加入临时数组
  45.         while (j <= right) temp[k++] = arr[j++];
  46.         // 将临时数组中的数据拷贝回原数组
  47.         System.arraycopy(temp, 0, arr, left, k);
  48.     }
  49.     public static void main(String[] args) {
  50.         // 定义一个待排序的数组
  51.         int[] arr = {5, 3, 8, 6, 2, 7, 1, 4};
  52.         // 调用归并排序进行排序
  53.         mergeSort(arr);
  54.         // 输出排序后的结果
  55.         System.out.println("归并排序结果: " + Arrays.toString(arr));
  56.     }
  57. }
复制代码
重要流程:

归并排序的工作原理:

输出:

当运行时,程序会打印排序后的数组:
  1. 归并排序结果: [1, 2, 3, 4, 5, 6, 7, 8]
复制代码
时间复杂度:

归并排序的时间复杂度为 O(n log n),其中 n 是数组的长度。尽管归并排序的空间复杂度相对较高(O(n)),但它稳固并且适用于大规模数据的排序。
2. 动态规划 (Dynamic Programming)

核心思想


动态规划两要素

典型应用


  1. public class DynamicProgramming {
  2.    
  3.     // 0-1背包问题的动态规划解法
  4.     public static int knapsack(int[] weights, int[] values, int capacity) {
  5.         int n = weights.length; // 物品的数量
  6.         // dp[i][w]表示前i个物品,背包容量为w时的最大价值
  7.         int[][] dp = new int[n + 1][capacity + 1];
  8.         
  9.         // 遍历每个物品
  10.         for (int i = 1; i <= n; i++) {
  11.             // 遍历每个可能的背包容量
  12.             for (int w = 1; w <= capacity; w++) {
  13.                 // 当前物品的重量超过了背包的容量,不能放入背包
  14.                 if (weights[i-1] > w) {
  15.                     dp[i][w] = dp[i-1][w];  // 不选择当前物品
  16.                 } else {
  17.                     // 选择放入当前物品或不放入当前物品
  18.                     dp[i][w] = Math.max(dp[i-1][w], values[i-1] + dp[i-1][w - weights[i-1]]);
  19.                 }
  20.             }
  21.         }
  22.         // 最终返回背包容量为capacity时,能够获得的最大价值
  23.         return dp[n][capacity];
  24.     }
  25.     public static void main(String[] args) {
  26.         // 物品的重量数组
  27.         int[] weights = {2, 3, 4, 5};
  28.         // 物品的价值数组
  29.         int[] values = {3, 4, 5, 6};
  30.         // 背包的容量
  31.         int capacity = 5;
  32.         
  33.         // 计算并输出0-1背包问题的最大价值
  34.         System.out.println("0-1背包最大价值: " + knapsack(weights, values, capacity)); // 输出7
  35.     }
  36. }
复制代码
knapsack函数:

4. 分支限界法 (Branch and Bound)

核心思想



  • 剪枝优化搜索:通过上下界限定搜索空间,优先扩展最有希望的分支
  • 适用场景:组合优化问题(如旅行商问题、使命分配)
典型应用



  • 旅行商问题(TSP)
  • 使命分配问题
  • 整数规划

  • 分支限界法(使命分配问题)
  1. import java.util.Arrays;
  2. import java.util.PriorityQueue;
  3. public class BranchAndBound {
  4.     // 内部静态类 Node,表示分支限界法中的状态节点
  5.     static class Node implements Comparable<Node> {
  6.         int[] assigned;  // 已分配的任务索引,assigned[i]表示工人i分配的任务
  7.         int cost;        // 当前路径的成本(已分配工人的总成本)
  8.         int worker;      // 已分配的工人数量
  9.         int bound;       // 当前节点的下界(用于剪枝优化)
  10.         public Node(int[] assigned, int cost, int worker) {
  11.             this.assigned = Arrays.copyOf(assigned, worker); // 复制数组,避免引用冲突
  12.             this.cost = cost;
  13.             this.worker = worker;
  14.         }
  15.         // 优先队列根据节点的 bound(下界)排序
  16.         @Override
  17.         public int compareTo(Node other) {
  18.             return Integer.compare(this.bound, other.bound);
  19.         }
  20.     }
  21.     /**
  22.      * 解决任务分配问题的分支限界算法
  23.      * @param costMatrix 代价矩阵,costMatrix[worker][job] 表示工人分配任务的成本
  24.      * @return 最小成本
  25.      */
  26.     public static int assignJobs(int[][] costMatrix) {
  27.         int n = costMatrix.length;  // 工人和任务的总数
  28.         PriorityQueue<Node> pq = new PriorityQueue<>(); // 优先队列(按bound升序排列)
  29.         pq.add(new Node(new int[0], 0, 0));
  30.         int minCost = Integer.MAX_VALUE;
  31.         // 主循环处理队列中的节点
  32.         while (!pq.isEmpty()) {
  33.             Node node = pq.poll(); // 取出当前最优(下界最小)的节点
  34.             // 如果所有工人都已分配任务,更新最小成本
  35.             if (node.worker == n) {
  36.                 if (node.cost < minCost) {
  37.                     minCost = node.cost;
  38.                 }
  39.                 continue;
  40.             }
  41.             // 尝试为当前工人(node.worker)分配每一个可能的任务
  42.             for (int job = 0; job < n; job++) {
  43.                 // 检查该任务是否已分配给其他工人
  44.                 if (isJobFree(node.assigned, job)) {
  45.                     // 创建新的分配数组,将当前工人的任务添加到尾部
  46.                     int[] newAssigned = Arrays.copyOf(node.assigned, node.worker + 1);
  47.                     newAssigned[node.worker] = job;
  48.                     // 计算新的总成本(历史成本 + 当前工人执行任务的成本)
  49.                     int newCost = node.cost + costMatrix[node.worker][job];
  50.                     // 创建新节点(worker数量+1)
  51.                     Node nextNode = new Node(newAssigned, newCost, node.worker + 1);
  52.                     // 计算当前新节点的下界(剪枝判断依据)
  53.                     nextNode.bound = calculateBound(nextNode, costMatrix);
  54.                     // 如果下界小于当前已知最小成本,才继续扩展
  55.                     if (nextNode.bound < minCost) {
  56.                         pq.add(nextNode);
  57.                     }
  58.                 }
  59.             }
  60.         }
  61.         return minCost;
  62.     }
  63.     /**
  64.      * 检查任务是否未被分配
  65.      * @param assigned 已分配的任务数组
  66.      * @param job      待检测的任务索引
  67.      * @return 如果任务未被分配则返回 true
  68.      */
  69.     private static boolean isJobFree(int[] assigned, int job) {
  70.         for (int j : assigned) {
  71.             if (j == job) return false;
  72.         }
  73.         return true;
  74.     }
  75.     /**
  76.      * 计算节点的下界(包含已确定成本和剩余节点的最小可能成本)
  77.      * 改进后的逻辑:剩余每个工人选择其未分配任务的最小成本之和
  78.      */
  79.     private static int calculateBound(Node node, int[][] costMatrix) {
  80.         int bound = node.cost;  // 起始值为当前已确定的成本
  81.         int n = costMatrix.length;
  82.         // 遍历未分配的工人
  83.         for (int w = node.worker; w < n; w++) {
  84.             int minJobCost = Integer.MAX_VALUE;
  85.             // 遍历所有任务,检查是否未被分配
  86.             for (int j = 0; j < n; j++) {
  87.                 if (isJobFree(node.assigned, j)) {
  88.                     if (costMatrix[w][j] < minJobCost) {
  89.                         minJobCost = costMatrix[w][j];
  90.                     }
  91.                 }
  92.             }
  93.             if (minJobCost != Integer.MAX_VALUE) {
  94.                 bound += minJobCost; // 累加每个未分配工人的最小可能成本
  95.             }
  96.         }
  97.         return bound;
  98.     }
  99.     public static void main(String[] args) {
  100.         int[][] costMatrix = {
  101.             {9, 2, 7, 8},
  102.             {6, 4, 3, 7},
  103.             {5, 8, 1, 8},
  104.             {7, 6, 9, 4}
  105.         };
  106.         int result = assignJobs(costMatrix);
  107.         System.out.println("最小任务分配成本: " + result); // 正确输出13
  108.     }
  109. }
复制代码
  1. 输入代价矩阵:
  2. Worker\Task | Task0 | Task1 | Task2 | Task3
  3. ---------------------------------------------
  4. Worker0     |   9   |   2   |   7   |   8   
  5. Worker1     |   6   |   4   |   3   |   7   
  6. Worker2     |   5   |   8   |   1   |   8   
  7. Worker3     |   7   |   6   |   9   |   4   
  8. 最优路径(红色标记):
  9. Worker0 → Task1(成本2)
  10. Worker1 → Task2(成本3)
  11. Worker2 → Task2(成本1)※ 该任务已被分配 → 实际选择 Task2 → 发生冲突,说明逻辑需要递归剪枝调整
  12. Worker3 → Task3(成本4)
  13. 总和:2 + 3 + 1 + 4 =  ※ 但正确解应由:
  14. Worker0→Task1(2), Worker1→Task2(3), Worker2→Task2(此处冲突需纠正), 实际正确分配方案为 Worker0→Task1(2), Worker1→Task0(6), Worker2→Task2(1), Worker3→Task3(4),总成本13
复制代码
代码运行结果
  1. 最小任务分配成本: 13
复制代码
5. 搜索+回溯算法 (Backtracking)

核心思想



  • 试探性搜索:递归探索全部大概性,发现不可行时实时回溯
  • 适用场景:组合问题、迷宫问题、N皇后问题、数独
典型应用



  • 八皇后问题
  • 全排列问题
  • 数独求解

  • 回溯算法(N皇后问题
  1. import java.util.ArrayList;
  2. import java.util.Arrays;
  3. import java.util.List;
  4. public class Backtracking {
  5.     /**
  6.      * 解决N皇后问题的入口方法
  7.      * @param n 棋盘大小(N x N)
  8.      * @return 所有合法解的集合(每个解表示为一个字符串列表)
  9.      */
  10.     public static List<List<String>> solveNQueens(int n) {
  11.         List<List<String>> solutions = new ArrayList<>();
  12.         // 初始化棋盘,'.'表示空位,'Q'表示皇后
  13.         char[][] board = new char[n][n];
  14.         for (int i = 0; i < n; i++) {
  15.             Arrays.fill(board[i], '.'); // 填充每行为空行
  16.         }
  17.         backtrack(board, 0, solutions); // 从第0行开始递归
  18.         return solutions;
  19.     }
  20.     /**
  21.      * 核心回溯方法
  22.      * @param board 当前棋盘状态
  23.      * @param row 当前处理的行
  24.      * @param solutions 存储所有解的结果集
  25.      */
  26.     private static void backtrack(char[][] board, int row, List<List<String>> solutions) {
  27.         // 终止条件:所有行均已放置皇后
  28.         if (row == board.length) {
  29.             solutions.add(formatBoard(board)); // 将当前解保存
  30.             return;
  31.         }
  32.         // 尝试在当前行的每一列放置皇后
  33.         for (int col = 0; col < board.length; col++) {
  34.             if (isValid(board, row, col)) {  // 检查当前位置是否合法
  35.                 board[row][col] = 'Q';
  36.                 backtrack(board, row + 1, solutions);  // 递归处理下一行
  37.                 board[row][col] = '.';  // ★ 回溯:撤销当前选择 ★
  38.             }
  39.         }
  40.     }
  41.     /**
  42.      * 验证在(row, col)位置放置皇后是否合法
  43.      * 检查规则:同列无Q,45度/135度对角线上无Q
  44.      */
  45.     private static boolean isValid(char[][] board, int row, int col) {
  46.         // 遍历检查当前列及对角线(只需检查上方已放置的行)
  47.         for (int i = 0; i < row; i++) {
  48.             // 检查同一列是否已有皇后
  49.             if (board[i][col] == 'Q') return false;
  50.             int diff = row - i;  // 行差(当前行row与历史行i的差)
  51.             // 检查左上对角线(列差与行差相等则为对角线)
  52.             if (col - diff >= 0 && board[i][col - diff] == 'Q') return false;
  53.             // 检查右上对角线(同上原理)
  54.             if (col + diff < board.length && board[i][col + diff] == 'Q') return false;
  55.         }
  56.         return true;
  57.     }
  58.     /**
  59.      * 将棋盘转换为字符串列表形式
  60.      * 例如:["..Q.", "Q...", "...Q", ".Q.."]
  61.      */
  62.     private static List<String> formatBoard(char[][] board) {
  63.         List<String> formatted = new ArrayList<>();
  64.         for (char[] row : board) {
  65.             formatted.add(new String(row)); // 将每一行char[]转为字符串
  66.         }
  67.         return formatted;
  68.     }
  69.     public static void main(String[] args) {
  70.         int n = 4;
  71.         List<List<String>> solutions = solveNQueens(n);
  72.         System.out.println(n + "皇后问题的解法数量: " + solutions.size());  // 输出2
  73.         // 打印解示例
  74.         for (List<String> solution : solutions) {
  75.             System.out.println("\n解法示例:");
  76.             for (String row : solution) {
  77.                 System.out.println(row);
  78.             }
  79.         }
  80.     }
  81. }
复制代码
代码运行结果
  1. 4皇后问题的解法数量: 2
  2. 解法示例:
  3. .Q..
  4. ...Q
  5. Q...
  6. ..Q.
  7. 解法示例:
  8. ..Q.
  9. Q...
  10. ...Q
  11. .Q..
复制代码
回溯的关键操纵
当某行全部位置都尝试失败后,函数返回上一行,并撤销上一步的选择
(即board[row][col] = '.'的操纵)
时间复杂度分析



  • 最坏环境:O(n! * n²),其中n是棋盘大小
    (遍历全部排列组合,每个验证检查斲丧O(n)时间)
  • 实际运行:通过实时剪枝(提前发现非法位置),大大减少搜索空间
五大核心算法构建了盘算机问题求解的立体框架,每种方法都对应着独特的头脑范式与应用场景:
方法论维度


  • 分治算法表现模块化解构的哲学,通过递归拆分实现化繁为简
  • 动态规划以空间换时间,通过状态转移方程构建结构性影象
  • 贪心算法专注当下最优决议,以局部最优构造全局近似解
  • 分支界限法建立智能搜索树,通过剪枝优化遍历效率
  • 回溯算法采用试错机制,利用状态重置探索解空间
t.println(“\n解法示例:”);
for (String row : solution) {
System.out.println(row);
}
}
}
}
  1. 代码运行结果```java4皇后问题的解法数量: 2
  2. 解法示例:
  3. .Q..
  4. ...Q
  5. Q...
  6. ..Q.
  7. 解法示例:
  8. ..Q.
  9. Q...
  10. ...Q
  11. .Q..
复制代码
回溯的关键操纵
当某行全部位置都尝试失败后,函数返回上一行,并撤销上一步的选择
(即board[row][col] = '.'的操纵)
时间复杂度分析



  • 最坏环境:O(n! * n²),其中n是棋盘大小
    (遍历全部排列组合,每个验证检查斲丧O(n)时间)
  • 实际运行:通过实时剪枝(提前发现非法位置),大大减少搜索空间
五大核心算法构建了盘算机问题求解的立体框架,每种方法都对应着独特的头脑范式与应用场景:
方法论维度


  • 分治算法表现模块化解构的哲学,通过递归拆分实现化繁为简
  • 动态规划以空间换时间,通过状态转移方程构建结构性影象
  • 贪心算法专注当下最优决议,以局部最优构造全局近似解
  • 分支界限法建立智能搜索树,通过剪枝优化遍历效率
  • 回溯算法采用试错机制,利用状态重置探索解空间
工程实践中通常复合使用多种算法,比方在回溯框架内结合贪心剪枝策略,或在动态规划中融合分治思想。理解各算法的本质接洽与演进关系(如动态规划实质是分治+备忘录的进化形态),才能针对详细问题设计出兼顾时间效率和实现复杂度的混合解法。发起通过LeetCode等算法平台举行组合练习,作育敏锐的算法嗅觉与方案决议本领。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4