DP问题的性质
动态规划(Dynamic Programming,DP)是指在解决动态规划问题时所依赖的一些基本特征和规律。动态规划是一种将复杂问题分解为更小子问题来解决的方法,它适用于具有重叠子问题和最优子结构性质的问题。动态规划问题通常具有以下特点:
特点:
1.最优子结构:问题的最优解包含其子问题的最优解。也就是说,一个问题的最优解可以从其子问题的最优解构造而来。
2.重叠子问题:在问题的求解过程中,相同的子问题会被多次计算。动态规划通过存储这些子问题的解来避免重复计算。
3.无后效性:一旦某个阶段的状态确定之后,它就不会再受之后阶段的决策影响。即一个阶段的状态一旦确定,就不会再改变。
4.状态转移方程:动态规划问题通常可以通过状态转移方程来描述问题的递推关系,即如何从一个或多个子问题的解来得到当前问题的解。
常见用法:
1.背包问题:如0/1背包问题、完全背包问题等。
2.最长公共子序列:如编辑距离、最长公共子序列等。
3.最短路径问题:如Floyd-Warshall算法、Dijkstra算法等。
4.计数问题:如硬币找零问题、计数问题等。
5.序列问题:如最长上升子序列、最长回文子序列等。
经典C语言例题:
题目: 使用动态规划解决0/1背包问题。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
// 计算最大值
int max(int a, int b) {
return (a > b) ? a : b;
}
// 定义背包问题的结构体
typedef struct {
int capacity; // 背包容量
int* weights; // 物品重量数组
int* values; // 物品价值数组
int n; // 物品种类数
} Knapsack;
// 创建背包问题实例
Knapsack* createKnapsack(int capacity, int* weights, int* values, int n) {
Knapsack* knapsack = (Knapsack*)malloc(sizeof(Knapsack));
knapsack->capacity = capacity;
knapsack->weights = weights;
knapsack->values = values;
knapsack->n = n;
return knapsack;
}
// 计算最大价值
int knapsack_(Knapsack* knapsack) {
int** dp = (int**)malloc((knapsack->n + 1) * sizeof(int*));
for (int i = 0; i <= knapsack->n; i++) {
dp[i] = (int*)malloc((knapsack->capacity + 1) * sizeof(int));
memset(dp[i], 0, (knapsack->capacity + 1) * sizeof(int));
}
for (int i = 1; i <= knapsack->n; i++) {
for (int w = 1; w <= knapsack->capacity; w++) {
if (knapsack->weights[i - 1] <= w) {
dp[i][w] = max(dp[i - 1][w], dp[i - 1][w - knapsack->weights[i - 1]] + knapsack->values[i - 1]);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
int maxValue = dp[knapsack->n][knapsack->capacity];
for (int i = 0; i <= knapsack->n; i++) {
free(dp[i]);
}
free(dp);
return maxValue;
}
int main() {
int capacity = 50;
int weights[] = {10, 20, 30};
int values[] = {60, 100, 120};
int n = sizeof(weights) / sizeof(weights[0]);
Knapsack* knapsack = createKnapsack(capacity, weights, values, n);
printf("Maximum value in knapsack: %d\n", knapsack_(knapsack));
free(knapsack->weights);
free(knapsack->values);
free(knapsack);
return 0;
}
例题分析:
1.创建背包问题实例:createKnapsack
函数创建一个背包问题实例,包括背包容量、物品重量数组、物品价值数组和物品种类数。
2.计算最大价值:knapsack
函数使用动态规划方法计算背包问题的最大价值。它首先创建一个二维数组dp
来存储子问题的解,然后通过两层循环遍历所有物品和所有可能的重量,计算每个子问题的解,并更新dp
数组。
3.主函数:在main
函数中,定义了一个背包问题实例,并调用knapsack
函数计算最大价值,最后打印结果。
这个例题展示了如何在C语言中使用动态规划解决0/1背包问题。通过这个例子,可以更好地理解动态规划在解决背包问题中的应用,以及如何使用动态规划来高效地解决具有重叠子问题和最优子结构性质的问题。动态规划通过存储子问题的解来避免重复计算,从而提高了算法的效率。
编码方法(记忆化递归、递推)
编码方法通常指的是在编程中用于解决问题的特定技巧或策略。在动态规划(DP)问题中,编码方法主要涉及记忆化递归和递推两种技术。
记忆化递归(Memoization)
记忆化递归是一种优化递归调用的技术,它通过存储已经计算过的子问题的解来避免重复计算,从而减少计算时间。记忆化通常使用一个数组或哈希表来存储子问题的解。
特点:
1.避免重复计算:通过存储子问题的解,避免了对相同子问题的重复计算。
2.提高效率:减少了不必要的计算,提高了算法的效率。
3.空间换时间:需要额外的空间来存储子问题的解。
常见用法:
1.动态规划问题:在解决具有重叠子问题的动态规划问题时,使用记忆化递归可以显著提高效率。
2.计算斐波那契数列:使用记忆化递归可以高效地计算斐波那契数列的值。
经典C语言例题:
题目: 使用记忆化递归计算斐波那契数列的第n项。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
// 计算斐波那契数列的第n项,使用记忆化递归
int fibonacci(int n, int* memo) {
if (n <= 1) {
return n;
}
// 检查是否已经计算过
if (memo[n] != -1) {
return memo[n];
}
// 计算并存储结果
memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo);
return memo[n];
}
int main() {
int n = 10;
int* memo = (int*)malloc((n + 1) * sizeof(int));
memset(memo, -1, (n + 1) * sizeof(int));
printf("Fibonacci number at position %d is %d\n", n, fibonacci(n, memo));
free(memo);
return 0;
}
例题分析:
1.记忆化递归函数:fibonacci
函数接受一个整数n
和一个整数数组memo
作为参数。memo
数组用于存储已经计算过的斐波那契数列的值。
2.递归计算:函数首先检查n
是否小于或等于1,如果是,则直接返回n
。否则,函数检查memo[n]
是否已经被计算过,如果是,则直接返回memo[n]
。
3.计算并存储结果:如果memo[n]
没有被计算过,则递归地调用fibonacci
函数计算n-1
和n-2
的斐波那契数,并将结果存储在memo[n]
中。
4.主函数:在main
函数中,定义了一个整数n
和一个整数数组memo
。调用fibonacci
函数计算斐波那契数列的第n
项,并打印结果。
这个例题展示了如何在C语言中使用记忆化递归来计算斐波那契数列的第n项。通过这个例子,可以更好地理解记忆化递归在解决动态规划问题中的应用,以及如何使用记忆化技术来提高算法的效率。记忆化递归通过存储子问题的解,避免了对相同子问题的重复计算,从而减少了计