> 技术文档 > 贪心、回溯、动态规划、分治算法的区别(附Java代码)_贪心回溯动态规划区别

贪心、回溯、动态规划、分治算法的区别(附Java代码)_贪心回溯动态规划区别


文章目录

  • 前言
  • 一、贪心算法 (Greedy Algorithm)
    • 思想
    • 适用场景
    • 优点
    • 缺点
    • 示例代码
  • 二、回溯算法 (Backtracking)
    • 思想
    • 适用场景
    • 优点
    • 缺点
    • 示例代码
  • 三、动态规划 (Dynamic Programming)
    • 思想
    • 适用场景
    • 优点
    • 缺点
    • 示例代码
  • 四、分治算法 (Divide and Conquer)
    • 思想
    • 适用场景
    • 优点
    • 缺点
    • 示例代码
  • 如何快速区分使用哪种算法?

前言

贪心、回溯、动态规划、分治算法都是常见的算法思想,主要区别在于它们解决问题的方式和适用的场景


一、贪心算法 (Greedy Algorithm)

思想

每一步都做出局部最优选择,以期最终获得全局最优解。

适用场景

问题具有贪心选择性质,即每个局部最优选择最终会导致全局最优解的产生。

优点

实现简单,效率较高。

缺点

不能保证所有问题都能得到全局最优解,通常用于有贪心性质的问题。

示例代码

找零钱问题(贪心解法)
通过贪心选择,每次选取能减少金额最多的硬币,最终获得最少硬币数。

import java.util.Arrays;public class GreedyChange { public static int minCoins(int[] coins, int amount) { Arrays.sort(coins); // 先排序,保证从最大面值开始 int count = 0; for (int i = coins.length - 1; i >= 0; i--) { if (amount >= coins[i]) { count += amount / coins[i]; // 用尽可能多的当前面值 amount %= coins[i]; // 更新剩余金额 } } return count; } public static void main(String[] args) { int[] coins = {1, 2, 5, 10, 20, 50}; int amount = 93; System.out.println(\"最少硬币数:\" + minCoins(coins, amount)); }}

二、回溯算法 (Backtracking)

思想

尝试所有可能的解,当发现某个解不可行时,回退到上一步并尝试其他可能性。

适用场景

问题具有多层决策,每层决策依赖上一步的选择。常用于组合问题、排列问题、路径问题等。

优点

能够解决所有的可能解问题。

缺点

效率低,容易造成指数级的时间复杂度。

示例代码

N皇后问题
通过递归和回溯尝试每一行放置皇后的位置,若当前选择不可行,回溯到上一行重新选择。

public class NQueens { final int N = 8; // N表示棋盘大小 int[] board = new int[N]; // 判断某位置是否合法 boolean isValid(int row, int col) { for (int i = 0; i < row; i++) { // 检查同列和对角线冲突 if (board[i] == col || Math.abs(board[i] - col) == Math.abs(i - row)) { return false; } } return true; } // 回溯核心逻辑 boolean solve(int row) { if (row == N) { printSolution(); return true; // 找到解 } boolean hasSolution = false; for (int col = 0; col < N; col++) { if (isValid(row, col)) { board[row] = col; // 选择此列 hasSolution |= solve(row + 1); // 递归下一行 } } return hasSolution; } // 打印棋盘 void printSolution() { for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (board[i] == j) {  System.out.print(\"Q \"); } else {  System.out.print(\". \"); } } System.out.println(); } System.out.println(); } public static void main(String[] args) { NQueens queens = new NQueens(); queens.solve(0); }}

三、动态规划 (Dynamic Programming)

思想

将问题拆解为子问题,并通过存储子问题的解来避免重复计算,通常用于求解最优解。

适用场景

问题可以分解为重叠子问题,且问题具有最优子结构(即子问题的最优解可以构成整个问题的最优解)。

优点

效率高,避免了重复计算。

缺点

需要额外的存储空间,适合求解具有子问题重叠性质的问题。

示例代码

斐波那契数列(动态规划解法)
通过动态规划的方式保存之前的计算结果,从而避免了递归带来的重复计算。

public class FibonacciDP { public static int fib(int n) { if (n <= 1) return n; int[] dp = new int[n + 1]; dp[0] = 0; dp[1] = 1; for (int i = 2; i <= n; i++) { dp[i] = dp[i - 1] + dp[i - 2]; // 状态转移方程 } return dp[n]; } public static void main(String[] args) { int n = 10; System.out.println(\"第 \" + n + \" 个斐波那契数:\" + fib(n)); }}

四、分治算法 (Divide and Conquer)

思想

将问题分解为多个子问题,分别解决子问题,然后将子问题的解合并为原问题的解。

适用场景

问题可以被分成独立的子问题,且子问题的解可以合并成整体问题的解。

优点

通过递归解决子问题,减少复杂度。

缺点

某些问题无法有效分治,递归会带来额外的栈空间消耗。

示例代码

归并排序
通过递归分治的方式将数组分成若干小部分进行排序,最后合并这些部分以得到最终的有序数组。

import java.util.Arrays;public class MergeSort { // 归并排序主函数 public static void mergeSort(int[] arr, int left, int right) { if (left < right) { int mid = left + (right - left) / 2; // 分治,递归排序左右两边 mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); // 合并排序后的两个子数组 merge(arr, left, mid, right); } } // 合并两个有序子数组 public static void merge(int[] arr, int left, int mid, int right) { int[] temp = new int[right - left + 1]; int i = left, j = mid + 1, k = 0; while (i <= mid && j <= right) { if (arr[i] <= arr[j]) { temp[k++] = arr[i++]; } else { temp[k++] = arr[j++]; } } while (i <= mid) { temp[k++] = arr[i++]; } while (j <= right) { temp[k++] = arr[j++]; } // 将合并后的结果复制回原数组 System.arraycopy(temp, 0, arr, left, temp.length); } public static void main(String[] args) { int[] arr = {38, 27, 43, 3, 9, 82, 10}; System.out.println(\"排序前: \" + Arrays.toString(arr)); mergeSort(arr, 0, arr.length - 1); System.out.println(\"排序后: \" + Arrays.toString(arr)); }}

如何快速区分使用哪种算法?

  1. 是否可以分治:如果问题可以分成多个独立子问题,并且每个子问题的解可以合并,那就考虑分治算法
  2. 是否有重叠子问题:如果问题可以通过求解子问题来优化效率,并且子问题存在重复计算,可以考虑动态规划
  3. 是否需要尝试所有可能解:如果问题要求找到所有可能的解决方案,并且可以通过回溯进行剪枝,选择回溯算法
  4. 是否存在贪心选择性质:如果问题允许通过局部最优选择直接找到全局最优解,可以考虑使用贪心算法