sunny123456

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

在java 算法中如何 区分 A.分治 B.动态规划 C.贪心 D.回溯, 并使用案例说明
用一个清晰的表格和具体案例说明这四种算法的区别:

对比表格

特性 分治 (Divide & Conquer) 动态规划 (Dynamic Programming) 贪心 (Greedy) 回溯 (Backtracking)
核心思想 分解→解决→合并 记忆化存储,避免重复计算 局部最优→全局最优 试探→回溯→继续试探
子问题关系 相互独立 重叠子问题,最优子结构 无后效性 决策树遍历
解决方向 自顶向下(通常递归) 自底向上或自顶向下 自顶向下 深度优先搜索
是否最优 不一定最优 保证最优解 不一定最优 能找到所有解
典型案例 归并排序、快速排序 背包问题、斐波那契 哈夫曼编码、Dijkstra N皇后、全排列

具体案例说明

A. 分治 (Divide & Conquer)

特点:将问题分解为独立的子问题,分别解决后合并结果

// 案例:归并排序(典型的分治算法)
public class MergeSort {
    public void mergeSort(int[] arr, int left, int right) {
        if (left < right) {
            int mid = (left + right) / 2;
            
            // 1. 分:分解为两个子问题
            mergeSort(arr, left, mid);
            mergeSort(arr, mid + 1, right);
            
            // 2. 治:解决子问题(这里排序)
            // 3. 合:合并结果
            merge(arr, left, mid, right);
        }
    }
    
    private void merge(int[] arr, int left, int mid, int right) {
        // 合并两个有序数组
        // ...
    }
}

B. 动态规划 (Dynamic Programming)

特点:有重叠子问题,存储中间结果避免重复计算

// 案例:斐波那契数列(记忆化搜索)
public class Fibonacci {
    // 方法1:递归(效率低,大量重复计算)
    public int fibRecursive(int n) {
        if (n <= 1) return n;
        return fibRecursive(n - 1) + fibRecursive(n - 2); // 大量重复计算
    }
    
    // 方法2:动态规划(自底向上)
    public int fibDP(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];
    }
    
    // 案例2:0-1背包问题(更典型的DP)
    public int knapSack(int capacity, int[] weights, int[] values, int n) {
        int[][] dp = new int[n + 1][capacity + 1];
        
        for (int i = 1; i <= n; i++) {
            for (int w = 0; w <= capacity; w++) {
                if (weights[i - 1] <= w) {
                    // 状态转移方程:要当前物品 vs 不要当前物品
                    dp[i][w] = Math.max(
                        values[i - 1] + dp[i - 1][w - weights[i - 1]],
                        dp[i - 1][w]
                    );
                } else {
                    dp[i][w] = dp[i - 1][w];
                }
            }
        }
        return dp[n][capacity];
    }
}

C. 贪心 (Greedy)

特点:每一步都选择当前看起来最优的,希望得到全局最优

// 案例:找零钱问题(注意:不一定总是得到最优解)
public class CoinChange {
    // 贪心算法:每次选择面值最大的硬币
    public int greedyCoinChange(int[] coins, int amount) {
        Arrays.sort(coins); // 排序
        int count = 0;
        int remaining = amount;
        
        // 从大到小选择硬币
        for (int i = coins.length - 1; i >= 0; i--) {
            while (remaining >= coins[i]) {
                remaining -= coins[i];
                count++;
            }
        }
        return remaining == 0 ? count : -1; // 可能无解
    }
}

// 案例2:区间调度问题(贪心能获得最优解)
public class IntervalScheduling {
    public int maxMeetings(int[][] intervals) {
        // 按结束时间排序
        Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
        
        int count = 1;
        int end = intervals[0][1];
        
        // 贪心选择:每次选择结束时间最早的
        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i][0] >= end) {
                count++;
                end = intervals[i][1];
            }
        }
        return count;
    }
}

D. 回溯 (Backtracking)

特点:尝试所有可能,不行就退回(回溯)

// 案例:N皇后问题
public class NQueens {
    List<List<String>> result = new ArrayList<>();
    
    public List<List<String>> solveNQueens(int n) {
        char[][] board = new char[n][n];
        for (char[] row : board) Arrays.fill(row, '.');
        backtrack(board, 0);
        return result;
    }
    
    private void backtrack(char[][] board, int row) {
        if (row == board.length) {
            // 找到一个解
            result.add(construct(board));
            return;
        }
        
        for (int col = 0; col < board.length; col++) {
            if (isValid(board, row, col)) {
                // 1. 做选择
                board[row][col] = 'Q';
                
                // 2. 进入下一层决策
                backtrack(board, row + 1);
                
                // 3. 撤销选择(回溯的关键)
                board[row][col] = '.';
            }
        }
    }
    
    private boolean isValid(char[][] board, int row, int col) {
        // 检查列、对角线是否有冲突
        // ...
        return true;
    }
}

// 案例2:全排列问题
public class Permutations {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(nums, new ArrayList<>(), result, new boolean[nums.length]);
        return result;
    }
    
    private void backtrack(int[] nums, List<Integer> path, 
                          List<List<Integer>> result, boolean[] used) {
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }
        
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) continue;
            
            // 选择
            used[i] = true;
            path.add(nums[i]);
            
            // 递归
            backtrack(nums, path, result, used);
            
            // 回溯
            used[i] = false;
            path.remove(path.size() - 1);
        }
    }
}

如何快速区分?

记住这个决策流程

  1. 问:是否需要尝试所有可能解?

    • 是 → 回溯(找所有解/排列组合问题)
    • 否 → 进入下一步
  2. 问:子问题是否相互独立?

    • 是 → 分治(排序、归并类问题)
    • 否(有重叠子问题)→ 进入下一步
  3. 问:能否用局部最优推导全局最优?

    • 是 → 贪心(需要证明贪心选择的正确性)
    • 否/不确定 → 动态规划(需要状态转移方程)

经典问题归类

  • 分治:归并排序、快速排序、最近点对、Strassen矩阵乘法
  • 动态规划:背包问题、最长公共子序列、编辑距离、最短路径(Bellman-Ford)
  • 贪心:Dijkstra最短路径、哈夫曼编码、最小生成树(Prim/Kruskal)、区间调度
  • 回溯:N皇后、数独、全排列、组合总和、图的着色

记住:实际中算法可能结合使用,如分治+动态规划、回溯+剪枝等。

posted on 2025-12-30 16:13  sunny123456  阅读(1)  评论(0)    收藏  举报