字节跳动19春招研发笔试 旅行商(TSP)问题 (记忆化搜索)

本文介绍了字节跳动春季笔试中遇到的旅行商问题,探讨了为什么简单的贪婪和分支界限策略无法有效解决,并详细解释了如何通过记忆化搜索优化解题,以降低最坏情况的时间复杂度。文中还分享了C语言实现的代码,并对比了记忆化搜索和DFS的性能差异,强调了学习和优化的重要性。
摘要由CSDN通过智能技术生成

前言

不会做, 只会DFS (贪婪 + 分支界限都试了, 然而只过了 50%的测试用例, 其他的超时), 学!!

思路

贪婪 (先搜索最近的) 和分支界限都只能提高最优情况的性能, 在最坏情况下仍然没有帮助. 所以 DFS 最坏情况下的复杂度是 ( n − 1 ) ! (n -1)! (n1)! (阶乘就是爆炸)
仔细观察会发现, 假如一共有6个城市, 那么
1- perm{2, 3, 4} - 5 - 6 - 1 这一共6种方案中, 都是在访问完 {1, 2, 3, 4} 后再到5, 最后访问6和1的. 所以出现了重复的子集.
因此, 可以用一个数组来保存一个状态, 表示在访问完 {1, 2, 3, 4} 这个状态后, 从5开始访问其他所有节点的最优解
此时状态用二进制编码, 出口用单独一个数字编码, 即可建表.

我再多解释几句:
表的形式是 d p [ s t a t u s ] [ o u t ] dp[status][out] dp[status][out], 其中 status 的每个位表示是否已经访问过这个城市, 而 out 表示我现在走到了这个城市, 然后以这个城市为起点寻找下一个城市. 也就是说我现在已经访问过了 status(二进制1的个数) + 1 个(out)城市, 之所以要把 out 单独从 status 中抽离出来是为了唯一确定这个状态的出口. 举个例子:
d p [ 0 ] [ 0 ] dp[0][0] dp[0][0] 表示我一个城市还没访问过, 现在在 0 这个城市 (起点), 从 0 开始寻找下一个城市.
d p [ 7 ] [ 4 ] dp[7][4] dp[7][4] 表示我已经访问过了 {0, 1, 2} ( 7 = 二进制的111), 现在走到了 4 这个城市, 然后从 4 这个城市开始寻找下一个目的地.

代码 ( C )

#include <stdio.h>
#define MAXN 20
#define INF (1 << 30)

int min(int a, int b) {
   
	return (a < b) ? a : b;
}

int adj[MAXN][MAXN];
int dp[1 << MAXN][MAXN];
int full = 0;
int status;

/*
在调用这个函数的时刻, 表示已经走过了 status 所标注的地点, 
!!以及!! out 这个地点 (还没标注)
返回的是在这个状态下走完剩余剩余城市并返回城市 0 的最优值
*/
int dfs(int out) {
   
	// 记忆化搜索的标准起手, 如果已经存储了这个状态的值, 直接返回
	int result = dp[status][out];
	if (result) return result;

	// (status | (1 << out)) 表示我现在已经遍历了这些城市, 如果全为 1 的话, 只需要返回 out (现在的位置) 和 0 (起点) 的距离.
	if ((status | (1 << out)) == full) return adj[out][0];

	// 设置状态 (已经访问过这个城市)
	status 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值