dp[1]{2,3} 只需要求出
d
p
[
2
]
{
3
}
dp[2]{3}
dp[2]{3} 即可,而
d
p
[
2
]
{
3
}
=
d
p
[
3
]
{
}
D
3
2
dp[2]{3}=dp[3]{}+D_3^2
dp[2]{3}=dp[3]{}+D32,
d
p
[
3
]
{
}
dp[3]{}
dp[3]{} 代表从城市3回到起点的距离,也就是
d
p
[
3
]
{
}
=
D
0
3
dp[3]{}=D_0^3
dp[3]{}=D03。
那么如何建立一个数组来表达上述状态转移方程呢?
我们可以使用状态压缩的方法,用一个int
数字的每一位来表达
d
p
[
n
]
{
p
1
,
p
2
,
.
.
.
,
p
m
}
dp[n]{p_1,p_2,…,p_m}
dp[n]{p1,p2,…,pm} 中的
{
p
1
,
p
2
,
.
.
.
,
p
m
}
{p_1,p_2,…,p_m}
{p1,p2,…,pm},即
p
m
p_m
pm是否存在等价于该int
数字的第m
位是否为1,所以一个int
数字可以表达
{
p
1
,
p
2
,
.
.
.
,
p
32
}
{p_1,p_2,…,p_{32}}
{p1,p2,…,p32},即32个城市。在刚才的字节跳动笔试题中,题目已经给出
n
≤
20
n\leq20
n≤20,所以使用一个int
数字已经足够了。所以最后,dp
数组的宽度为城市的数量
x
x
x,长度为
2
x
−
1
2^{x-1}
2x−1。
所以该算法的时间复杂度为
O
(
2
n
n
2
)
O(2nn2)
O(2nn2),空间复杂度为
O
(
2
n
)
O(2^n)
O(2n),虽然看上去时间复杂度还是很大,但好在基数
n
n
n 并不大,所以一般在
n
<
20
n<20
n<20几秒钟就能解决问题。要知道在
n
=
20
n=20
n=20的时候,和回溯法相比理论效率提高了50亿倍,时间开销小了很多。
解决数组问题后,接下来就是建立初始状态。
刚才已经说过
d
p
[
n
]
{
}
=
D
0
n
dp[n]{}=D_0^n
dp[n]{}=D0n,所以我们可以把各个城市n
到起点的距离
D
0
n
D_0^n
D0n赋值给
d
p
[
n
]
[
0
]
dp[n][0]
dp[n][0]:
int[][] dp = new int[n][1 << (n - 1)];
for(int i = 0; i < n; i++) {
dp[i][0] = map[i][0];
}
接下来就是考虑如何填充dp
表。
首先,我们现在已知
d
p
[
1
]
{
}
,
d
p
[
2
]
{
}
,
.
.
.
,
d
p
[
n
]
{
}
dp[1]{},dp[2]{},…,dp[n]{}
dp[1]{},dp[2]{},…,dp[n]{}的值
例如选定
d
p
[
1
]
{
}
dp[1]{}
dp[1]{},我们可以直接推导出
d
p
[
0
]
{
1
}
,
d
p
[
2
]
{
1
}
,
.
.
.
,
d
p
[
m
]
{
1
}
dp[0]{1},dp[2]{1},…,dp[m]{1}
dp[0]{1},dp[2]{1},…,dp[m]{1}(因为
d
p
[
n
]
{
1
}
=
d
p
[
1
]
{
}
D
1
m
dp[n]{1}=dp[1]{}+D_1^m
dp[n]{1}=dp[1]{}+D1m)
同理选定
d
p
[
n
]
{
}
dp[n]{}
dp[n]{},我们可以直接推导出
d
p
[
0
]
{
n
}
,
d
p
[
2
]
{
n
}
,
.
.
.
,
d
p
[
m
]
{
n
}
dp[0]{n},dp[2]{n},…,dp[m]{n}
dp[0]{n},dp[2]{n},…,dp[m]{n}(因为
d
p
[
m
]
{
n
}
=
d
p
[
m
]
{
}
D
n
m
dp[m]{n}=dp[m]{}+D_n^m
dp[m]{n}=dp[m]{}+Dnm)
所以第一个循环为遍历城市集合,即依次遍历
{
1
}
,
{
2
}
,
{
1
,
2
}
,
{
3
}
,
{
1
,
3
}
,
{
2
,
3
}
,
{
1
,
2
,
3
}
,
.
.
.
,
{
1
,
.
.
.
,
n
}
{1},{2},{1,2},{3},{1,3},{2,3},{1,2,3},…,{1,…,n}
{1},{2},{1,2},{3},{1,3},{2,3},{1,2,3},…,{1,…,n},因为只有根据小的集合才能推导出大的集合,将这个集合
P
P
P 对应的int
数赋值为
X
X
X(下列代码中为p
):
for (int p = 1; p < 1 << (n - 1); p++) {
//...
}
第二个循环选择起点城市(当然起点城市不能包含在
P
P
P 中 ),并将起始城市赋值给变量
i
i
i,此时城市集合和起点城市就选定好了,也就是我们要计算的
d
p
[
i
]
[
p
]
dp[i][p]
dp[i][p]:
for (int p = 1; p < 1 << (n - 1); p++) { //遍历所有集合
for (int i = 0; i < n; i++) { //选定一个起点城市
if(self(i, p)) { //起点城市不能包含在P中
continue;
}
//...
}
}
如何计算
d
p
[
i
]
[
p
]
dp[i][p]
dp[i][p]呢,我们之前之前提过:
d
p
[
n
]
{
p
1
,
p
2
,
.
.
.
,
p
m
}
=
m
i
n
(
d
p
[
p
1
]
{
p
2
,
.
.
.
,
p
m
}
D
p
1
n
,
d
p
[
p
2
]
{
p
1
,
p
3
,
.
.
.
,
p
m
}
D
p
2
n
,
.
.
.
.
.
.
,
d
p
[
p
m
]
{
p
1
,
p
2
,
.
.
.
p
m
−
1
}
D
p
m
n
)
dp[n]{p_1,p_2,…,p_m}= min(dp[p_1]{p_2,…,p_m}+D_{p_1}^n, dp[p_2]{p_1,p_3,…,p_m}+D_{p_2}n,…,dp[p_m]{p_1,p_2,…p_{m-1}}+D_{p_m}n)
dp[n]{p1,p2,…,pm}=min(dp[p1]{p2,…,pm}+Dp1n,dp[p2]{p1,p3,…,pm}+Dp2n,…,dp[pm]{p1,p2,…pm−1}+Dpmn)
所以第三个循环就是从集合
P
P
P 中选取每个城市作为子问题的起点
p
x
p_x
px,也就是需要计算
d
p
[
p
x
]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
加入社区》https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0
=
m
i
n
(
d
p
[
p
1
]
{
p
2
,
.
.
.
,
p
m
}
D
p
1
n
,
d
p
[
p
2
]
{
p
1
,
p
3
,
.
.
.
,
p
m
}
D
p
2
n
,
.
.
.
.
.
.
,
d
p
[
p
m
]
{
p
1
,
p
2
,
.
.
.
p
m
−
1
}
D
p
m
n
)
dp[n]{p_1,p_2,…,p_m}= min(dp[p_1]{p_2,…,p_m}+D_{p_1}^n, dp[p_2]{p_1,p_3,…,p_m}+D_{p_2}n,…,dp[p_m]{p_1,p_2,…p_{m-1}}+D_{p_m}n)
dp[n]{p1,p2,…,pm}=min(dp[p1]{p2,…,pm}+Dp1n,dp[p2]{p1,p3,…,pm}+Dp2n,…,dp[pm]{p1,p2,…pm−1}+Dpmn)
所以第三个循环就是从集合
P
P
P 中选取每个城市作为子问题的起点
p
x
p_x
px,也就是需要计算
d
p
[
p
x
]
[外链图片转存中…(img-RPC3aLqR-1725788429195)]
[外链图片转存中…(img-UM64m55E-1725788429195)]
[外链图片转存中…(img-rcaPIzQl-1725788429196)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
加入社区》https://bbs.csdn.net/forums/4304bb5a486d4c3ab8389e65ecb71ac0