动态规划、贪心算法、分治算法和回溯算法的完整 Java 实现
以下是动态规划、贪心算法、分治算法和回溯算法的完整 Java 实现,包含详细注释和应用场景分析:
import java.util.*;
public class CoreAlgorithms {
public static void main(String[] args) {
// 动态规划示例
System.out.println("动态规划 - 斐波那契数列(10): " + dpFibonacci(10));
System.out.println("动态规划 - 零钱兑换(18): " + coinChange(new int[]{1, 2, 5}, 18));
// 贪心算法示例
System.out.println("\n贪心算法 - 活动选择: ");
activitySelection();
// 分治算法示例
int[] arr = {12, 11, 13, 5, 6, 7};
System.out.println("\n分治算法 - 归并排序:");
System.out.println("排序前: " + Arrays.toString(arr));
mergeSort(arr, 0, arr.length - 1);
System.out.println("排序后: " + Arrays.toString(arr));
// 回溯算法示例
System.out.println("\n回溯算法 - N皇后问题(4皇后):");
solveNQueens(4);
}
// ========== 动态规划算法 ==========
/**
* 1. 斐波那契数列(基础DP)
* 时间复杂度: O(n)
*/
public static int dpFibonacci(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. 零钱兑换问题(完全背包DP)
* 时间复杂度: O(amount * coins.length)
*/
public static int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1); // 初始化为大值
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (coin <= i) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
// ========== 贪心算法 ==========
/**
* 活动选择问题(区间调度)
* 时间复杂度: O(n log n)
*/
public static void activitySelection() {
// 活动格式: [开始时间, 结束时间]
int[][] activities = {{1, 4}, {3, 5}, {0, 6}, {5, 7}, {3, 9}, {5, 9}, {6, 10}, {8, 11}, {8, 12}, {2, 14}, {12, 16}};
// 按结束时间排序
Arrays.sort(activities, (a, b) -> a[1] - b[1]);
List<int[]> selected = new ArrayList<>();
int lastEnd = -1;
for (int[] act : activities) {
if (act[0] >= lastEnd) { // 贪心选择:选择最早结束且不冲突的活动
selected.add(act);
lastEnd = act[1];
}
}
System.out.println("选择活动数量: " + selected.size());
System.out.println("选择活动详情:");
for (int[] act : selected) {
System.out.println(Arrays.toString(act));
}
}
// ========== 分治算法 ==========
/**
* 归并排序(经典分治)
* 时间复杂度: O(n log n)
*/
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);
}
}
private 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);
}
// ========== 回溯算法 ==========
/**
* N皇后问题
* 时间复杂度: O(n!)
*/
public static void solveNQueens(int n) {
List<List<String>> solutions = new ArrayList<>();
char[][] board = new char[n][n];
for (char[] row : board) Arrays.fill(row, '.');
backtrack(board, 0, solutions);
// 打印解决方案
System.out.println("解决方案数量: " + solutions.size());
for (int i = 0; i < solutions.size(); i++) {
System.out.println("方案 " + (i + 1) + ":");
for (String row : solutions.get(i)) {
System.out.println(row);
}
System.out.println();
}
}
private static void backtrack(char[][] board, int row, List<List<String>> solutions) {
if (row == board.length) {
solutions.add(formatSolution(board));
return;
}
for (int col = 0; col < board.length; col++) {
if (isValid(board, row, col)) {
board[row][col] = 'Q'; // 放置皇后
backtrack(board, row + 1, solutions); // 递归下一行
board[row][col] = '.'; // 回溯:撤销选择
}
}
}
private static boolean isValid(char[][] board, int row, int col) {
// 检查列
for (int i = 0; i < row; i++) {
if (board[i][col] == 'Q') return false;
}
// 检查左上对角线
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q') return false;
}
// 检查右上对角线
for (int i = row - 1, j = col + 1; i >= 0 && j < board.length; i--, j++) {
if (board[i][j] == 'Q') return false;
}
return true;
}
private static List<String> formatSolution(char[][] board) {
List<String> solution = new ArrayList<>();
for (char[] row : board) {
solution.add(new String(row));
}
return solution;
}
}
算法详解与输出示例
1. 动态规划 (Dynamic Programming)
核心思想:将复杂问题分解为重叠子问题,存储子问题解避免重复计算
适用场景:最优子结构、重叠子问题的问题
示例输出:
动态规划 - 斐波那契数列(10): 55
动态规划 - 零钱兑换(18): 4 // 使用 [1,2,5] 硬币凑 18 元最少需要 4 枚(5+5+5+3)
2. 贪心算法 (Greedy Algorithm)
核心思想:每一步选择当前最优解,希望最终达到全局最优
适用场景:局部最优能导致全局最优的问题
示例输出:
贪心算法 - 活动选择:
选择活动数量: 4
选择活动详情:
[1, 4]
[5, 7]
[8, 11]
[12, 16]
3. 分治算法 (Divide and Conquer)
核心思想:将问题分解为多个子问题,递归解决后合并结果
适用场景:可分解为独立子问题的问题
示例输出:
分治算法 - 归并排序:
排序前: [12, 11, 13, 5, 6, 7]
排序后: [5, 6, 7, 11, 12, 13]
4. 回溯算法 (Backtracking)
核心思想:通过尝试所有可能的选择,发现无效解时回溯
适用场景:求所有解、组合优化、约束满足问题
示例输出:
回溯算法 - N皇后问题(4皇后):
解决方案数量: 2
方案 1:
.Q..
...Q
Q...
..Q.
方案 2:
..Q.
Q...
...Q
.Q..
四大算法对比分析
算法 | 核心思想 | 时间复杂度 | 空间复杂度 | 典型应用场景 |
---|---|---|---|---|
动态规划 | 存储子问题解 | O(子问题数×子问题规模) | O(子问题数) | 最短路径、背包问题、编辑距离 |
贪心算法 | 局部最优选择 | 通常O(n log n) | O(1) | 活动选择、霍夫曼编码、最小生成树 |
分治算法 | 分-解-合 | O(n log n) | O(n) | 归并排序、快速排序、最近点对 |
回溯算法 | 尝试-回溯 | 指数级O(kⁿ) | O(递归深度) | N皇后、数独、组合优化 |
算法选择决策树
各算法最佳实践
动态规划
// 通用DP框架
public int dpSolution(int[] params) {
int n = params.length;
// 1. 定义DP数组
int[] dp = new int[n + 1];
// 2. 初始化基础状态
dp[0] = 0;
// 3. 状态转移
for (int i = 1; i <= n; i++) {
dp[i] = Math.min(dp[i - 1], dp[i - 2]) + params[i - 1];
}
// 4. 返回结果
return dp[n];
}
贪心算法
// 贪心选择框架
public void greedySolution(List<Item> items) {
// 1. 按贪心策略排序
Collections.sort(items, Comparator.comparingDouble(Item::valuePerWeight));
double totalValue = 0;
// 2. 贪心选择
for (Item item : items) {
if (canTake(item)) {
take(item);
totalValue += item.value;
}
}
}
分治算法
// 分治三步骤
public Result divideConquer(Problem problem) {
// 1. 分解:将大问题分解为子问题
if (problem.isSimple()) {
return solveDirectly(problem);
}
Problem[] subProblems = split(problem);
// 2. 解决:递归解决子问题
Result result1 = divideConquer(subProblems[0]);
Result result2 = divideConquer(subProblems[1]);
// 3. 合并:合并子问题解
return mergeResults(result1, result2);
}
回溯算法
// 回溯框架
public void backtrack(State state, List<Choice> choices) {
// 1. 终止条件
if (isSolution(state)) {
recordSolution(state);
return;
}
// 2. 遍历选择
for (Choice choice : choices) {
// 3. 做出选择
makeChoice(state, choice);
// 4. 递归进入下一层
backtrack(state, getNextChoices());
// 5. 撤销选择
undoChoice(state, choice);
}
}
性能优化技巧
-
动态规划优化
- 状态压缩:滚动数组减少空间复杂度
- 记忆化搜索:递归+缓存替代迭代DP
// 斐波那契记忆化搜索 private int fibMemo(int n, int[] memo) { if (n <= 1) return n; if (memo[n] > 0) return memo[n]; memo[n] = fibMemo(n-1, memo) + fibMemo(n-2, memo); return memo[n]; }
-
回溯剪枝
for (Choice choice : choices) { if (!isValid(state, choice)) continue; // 剪枝:跳过无效选择 makeChoice(state, choice); backtrack(state, nextChoices); undoChoice(state, choice); }
-
贪心算法证明
- 贪心选择性质:局部最优 → 全局最优
- 最优子结构:问题最优解包含子问题最优解
-
分治并行化
// 使用ForkJoinPool并行处理子问题 Result left = forkJoinPool.submit(() -> divideConquer(leftProblem)).join(); Result right = forkJoinPool.submit(() -> divideConquer(rightProblem)).join();
常见问题解决方案
问题类型 | 推荐算法 | 替代方案 |
---|---|---|
最短路径问题 | 动态规划 (Dijkstra) | BFS (无权图) |
任务调度问题 | 贪心算法 | 动态规划 |
大规模排序 | 分治 (归并/快排) | 堆排序 |
组合优化 (如N皇后) | 回溯算法 | 启发式搜索 |
资源分配问题 | 贪心算法 | 动态规划 |
字符串编辑距离 | 动态规划 | 有限自动机 |
提示:实际应用中常组合使用多种算法,如回溯+剪枝、贪心+动态规划验证等混合策略。