回溯法
回溯法:对解空间树结点 扩展或回溯
确定结点的扩展搜索规则之后,以深度优先方式搜索解空间树,在搜索过程中采用剪枝函数来避免无用搜索。
- 结点扩展规则:中间结点 选择或不选择,叶子结点 停止扩展并判断终止条件
- 深度优先搜索:
- 剪枝函数:约束函数 选择物品总质量不能大于背包可承受重量
解空间树
- 子集树(此代码是子集树):达到要求的子集,元素子集。选择某个元素/不选择某个元素
- 排列树:达到要求的排列,所有元素都在。第1层把那个元素放到第1位,第2层把那个元素放到第2位,依次类推
活结点:还没生孩子的结点 死结点:不能生孩子的结点
剪枝函数
- 约束函数:剪去不满足约束条件的子树
- 限界函数:剪去得不到需要解(无解,非最优解)的子树;最大值问题采用上界函数;能求得的上界都不满足要求;即便选择剩下所有物品,也不可能找到一个更好的解
可以找到问题的所有解,当然如果只需要1个解,找到一个解之后结束即可
对比 回溯法 分枝限界法
回溯法 | 分枝限界法 |
---|
回溯法使用栈进行DFS | 分枝限界法使用队列进行BFS | 回溯法可以找到所有解 | 分枝限界法只能找到1个解(最优解) | 回溯法所有可行结点都会被遍历 | 分枝限界法不会遍历所有可行结点,每个结点只有1次成为活结点的机会 |
代码
package xcrj.kchalgorithm.backtracking;
import java.util.Arrays;
public class Knapsack01 {
private static int n = 4;
private static int availableWeight = 6;
private static int[] weights = {0, 5, 3, 2, 1};
private static int[] values = {0, 4, 4, 3, 1};
private static double maxValue = Double.MIN_VALUE;
private static int[] opitimalXs = new int[n + 1];
public static void dfs(int i, int sumWeight, int sumValue, int restWeight, int[] xs) {
if (i > n) {
if (sumWeight <= availableWeight && sumValue > maxValue) {
maxValue = sumValue;
for (int j = 1; j <= n; j++) {
opitimalXs[j] = xs[j];
}
}
} else {
if (sumWeight + weights[i] <= availableWeight) {
xs[i] = 1;
dfs(i + 1, sumWeight + weights[i], sumValue + values[i], restWeight - weights[i], xs);
}
xs[i] = 0;
if (sumWeight + restWeight > availableWeight) {
dfs(i + 1, sumWeight, sumValue, restWeight - weights[i], xs);
}
}
}
public static void main(String[] args) {
int restWeight = 0;
for (int i = 1; i <= n; i++) {
restWeight += weights[i];
}
dfs(1, 0, 0, restWeight, new int[n + 1]);
System.out.println("最大价值:" + maxValue);
System.out.println("装入物品:" + Arrays.toString(opitimalXs));
}
}
时间复杂度
回溯法的过程中具有深度优先遍历过程 考虑遍历的结点数量,考虑结点个数即可 最多
2
n
?
1
2^n-1
2n?1个结点 时间复杂度就是遍历
2
n
?
1
2^n-1
2n?1个结点:
O
(
2
n
)
O(2^n)
O(2n)
解空间树是子集树时,时间复杂度通常是
O
(
2
n
)
O(2^n)
O(2n) 解空间树是排序树时,时间复杂度通常是
O
(
n
!
)
O(n!)
O(n!)
|