深度优先搜索是一种枚举所有完整路径以遍历所有情况的搜索方法。
从起点开始前进,当碰到岔道口时,总是选择其中一条岔路前进,在岔路上如果又遇到新的岔道口,
仍然选择新岔道口的其中一条岔路前进,直到碰到死胡同才回退到最近的岔道口选择另一条岔路。
(当碰到岔道口时,总是以“深度”作为前进的关键词,不碰到死胡同就不回头)
例1:
有n件物品,每件物品重量为w[i],价值为v[i]。现在需要选出若干物品放入一个容量为bag的背包中,
使得在选入背包的物品总重量不超过容量bag的情况下,让背包中物品的价值之和最大。
求最大价值。(1<=n<=20)
输入:5 8
3 5 1 2 2
4 5 2 1 3输出:10
解:
public class Main {
static int n;// 物品数量
static int bag;// 背包容量
static int[] weight;// 物品重量
static int[] value;// 物品价值
static int max = 0;// 背包能装下的最大价值
static void dfs(int i, int w, int v) {// 物品序号,当前重量,当前价值
if (i == n)
return;
dfs(i + 1, w, v);// 不选第i件物品
// 只有在 当前总量 + 第i件物品 不超过背包容量时,才会选下第i件物品
if (w + weight[i] <= bag) {
if (v + value[i] > max) {
max = v + value[i];// 更新最大价值max
}
dfs(i + 1, w + weight[i], v + value[i]);// 选第i件物品
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
bag = in.nextInt();
weight = new int[n];
value = new int[n];
for (int i = 0; i < n; i++)
weight[i] = in.nextInt();
for (int i = 0; i < n; i++)
value[i] = in.nextInt();
dfs(0, 0, 0);// 初始时均为0
System.out.println(max);
}
}
例2:
给定n个整数(可能有负数),从中选择k个数,使得这k个数之和恰好等于一个给定的整数x。
如果有多种方案,选择它们中平方和最大的一个。
数据保证这样的方案唯一。
输入:4
2 3 3 4
2
6
输出:2 4
解:
public class Main {
static int n;
static int k;// 从a中选k个数
static int x;// 使这k个数之和为x
static int[] a;
static Integer[] b;// 最优方案
static LinkedList<Integer> c;// 临时方案
static int maxSumP = 0;// 最大平方和
// 整数编号,当前已经选择的个数,当前已选整数之和,当前已选整数的平方和
static void dfs(int i, int nowK, int sum, int sumP) {
if (nowK == k && sum == x) {
if (sumP > maxSumP) {// 当sumP为最大时,此时的方案作为最优方案
maxSumP = sumP;
b = c.toArray(b);
}
return;
}
// 已经遍历完n个数,或选了超过k个数,或和超过x,均返回
if (i == n || nowK > k || sum > x)
return;
c.add(a[i]);// 编号为i的数进入临时队列
dfs(i + 1, nowK + 1, sum + a[i], sumP + a[i] * a[i]);// 选编号为i的数
c.pollLast();// 编号为i的数退出临时队列
dfs(i + 1, nowK, sum, sumP);// 不选编号为i的数
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
n = in.nextInt();
a = new int[n];
for (int i = 0; i < n; i++)
a[i] = in.nextInt();
k = in.nextInt();
b = new Integer[k];
c = new LinkedList<>();
x = in.nextInt();
dfs(0, 0, 0, 0);// 初始均为0
for (int i = 0; i < k; i++)
System.out.print(b[i] + " ");
}
}
上面问题中的每个数都只能选择一次,现在稍微修改题目:
假设n个整数中的每一个都可以都可以被选择多次,那么选择k个数,使得k个数之和恰好为x。
假设有三个数1、4、7,需要从中选5个数,使它们的和为17。
显然只需要选3个1和2个7,即可得到17。
解:
由于每个整数都可以被选择多次,因此当选择了i号数时,不应当直接进入i+1号数的处理。
应当继续选择i号数,直到某个时刻决定不再选择i号数,就会通过“不选编号为i的数”这条分支进入i+1号数的处理。
因此只需要把“选编号为i的数”这条分支的代码中的i+1修改为i即可。