一、旅行商问题(TSP)

旅行商问题,常被称为 旅行推销员问题(Travelling Salesman Problem, TSP),是指一名推销员要拜访多个地点时,如何找到在拜访 每个地点一次 后再 回到起点 的最短路径。

  • 带权图,完全图
  • NP 难问题(即还没有多项式级别的算法,只有指数级别的算法来解决这类问题)

在物流中的描述是对应一个物流配送公司,欲将 n 个客户的订货沿最短路线全部送到。

二、哈密顿回路

1、哈密顿回路问题的起源

1859 年,爱尔兰数学家哈密顿(Hamilton)提出周游世界的游戏:在正十二面体的二十个顶点上依次标记伦敦、巴黎、莫斯科等世界著名大城市,正十二面体的棱表示连接这些城市的路路线。试问能否在图中做一次旅行,从顶点到顶点,沿着边行走,经过每个城市 恰好一次 之后再 回到出发点

哈密顿图是一个无向图,由指定的起点前往指定的终点,途中经过所有其他节点且只经过一次。在哈密顿图中,含有图中所有顶点的路径称作 哈密顿路径(Hamilton path),闭合的哈密顿路径称作 哈密顿回路(Hamilton cycle)。

旅行商问题对应的是完全图,任意每两个顶点之间都存在路径,并且路径长度已知。
哈密顿回路并不要求图是完全图,当图是加权完全图时,哈密顿回路的问题就演变成了旅行商问题。

2、求哈密顿回路算法(回溯)

四个城市之间的距离如图,求最短哈密顿回路。

由于路线是循环的,所以可以把任何一点作为起点,将城市 0 作为起点和终点。

以 DFS 方式开始从源到相邻节点的遍历。
计算每次遍历的成本,跟踪最小成本,并不断更新最小成本存储值。
以最低成本返回结果。

public class TSP {public static void main(String[] args) {        int[][] graph = {{0, 10, 15, 20},{10, 0, 35, 25},{15, 35, 0, 30},{20, 25, 30, 0}};boolean[] vis = new boolean[graph.length];vis[0] = true;int ans = Integer.MAX_VALUE;ans = tsp(graph, vis, ans, 0, 1, 0);System.out.println(ans);}static int tsp(int[][] graph, boolean[] v, int ans, int currPos, int count, int cost) {int n = graph.length;if (count == n && graph[currPos][0] > 0) {return Math.min(ans, cost + graph[currPos][0]);}/** 回溯步骤(BACKTRACKING STEP)* 循环遍历 currPos 结点的邻接表,将计数增加 1,并按 graph[currPos][i] 值增加成本* cost + graph[currPos][i]*/for (int i = 0; i < n; i++) {if (!v[i] && graph[currPos][i] > 0) {v[i] = true;ans = tsp(graph, v, ans, i, count + 1, cost + graph[currPos][i]);                v[i] = false; }}return ans;}
}

状态压缩

public class TSP {public static void main(String[] args) {        int[][] graph = {{0, 10, 15, 20}, {10, 0, 35, 25}, {15, 35, 0, 30}, {20, 25, 30, 0}};int ans = Integer.MAX_VALUE;ans = tsp(graph, ans, 1, 0, 0);System.out.println(ans);}static int tsp(int[][] graph, int ans, int mask, int curPos, int cost) {int n = graph.length;if (mask == (1 << n) - 1) return Math.min(ans, cost + graph[curPos][0]);for (int i = 0; i < n; i++)if ((mask & 1 << i ) == 0) ans = tsp(graph, ans, mask | 1 << i, i, cost + graph[curPos][i]);return ans;}
}

哈密顿问题和路径压缩

/************************************************************ @Description : 基于 DFS 的回溯法找哈密尔顿回路* @author      : * @date        : 2023-4-27 11:24* @email       : ***********************************************************/
package Chapter09HamiltonLoop.Section1to4HamiltonLoop;
import Chapter02GraphExpress.Graph;import java.util.ArrayList;
import java.util.Collections;
import java.util.List;public class GraphDFSHamiltonLoop {private static final int START = 0;private Graph graph;/*** 存储顶点是否被访问的数组*/private boolean[] visited;/*** 记录顶点的访问顺序 pre[v] = u 表示 v 的上一个访问节点是 u*/private int[] pre;/*** 记录回到起点 START 顶点的前一个顶点,如果 DFS 执行完 end 不是 -1 了表明图中存在哈密尔顿回路*/private int end = -1;/*** 存放图的深度优先遍历的结果*/private List<Integer> orderList = new ArrayList<>();public GraphDFSHamiltonLoop(Graph graph) {this.graph = graph;// 初始化访问数组,用图的顶点个数来访问visited = new boolean[graph.V()];pre = new int[graph.V()];// 根据哈密尔顿回路的特点,回溯法找从 1 个顶点开始遍历即可dfs(START, START, graph.V());}public Iterable<Integer> getOrderList() {return orderList;}/*** 返回哈密尔顿回路*/public List<Integer> getLoop() {List<Integer> path = new ArrayList<>();if (end == -1) {// 等于 -1 表示没找到哈密尔顿环return path;}// cur 初始等于哈密尔顿环回到起点的前一个点int cur = end;while (cur != START) {// 没到起点就一直往前遍历path.add(cur);cur = pre[cur];}// 设置起点path.add(START);Collections.reverse(path);// 终点为起点path.add(START);return path;}/*** DFS遍历,过程中使用回溯法找到哈密尔顿回路* @param v      当前递归遍历到的节点* @param parent v 的上一个访问节点* @param left   本次递归还剩下多少元素没被访问,一定注意 left 是本层递归中的局部变量,不是在所有递归中都有效地!!回退到上一层递归 left 的意义就变了!!每层递归都有自己的 left!!!* @return 是否找到了哈密尔顿回路*/private boolean dfs(int v, int parent, int left) {visited[v] = true;orderList.add(v);pre[v] = parent;left--;for (Integer w : graph.adj(v)) {if (!visited[w]) {// w 点没被访问的话就递归接着访问if (dfs(w, v, left)) {// 遍历过程中任何一层递归返回 True 说明找到了哈密尔顿回路return true;}} else {// 如果 w 已经被访问,且 w 回到了起始点 + 恰好所有定点都被访问,说明存在哈密尔顿回路if (w == START && left == 0) {// 记录回到起点 START 顶点的前一个顶点,如果 DFS 执行完 end 不是 -1 了表明图中存在哈密尔顿回路end = v;// 如果回到了遍历起点并且所有节点都已经被访问了,说明存在哈密尔顿回路return true;}}}// 没找到要回退,所以要把 v 点设置为未被访问过,即设置为 Falsevisited[v] = false;return false;}
}

gitee

980. 不同路径 III

class Solution {   boolean[][] vis;int[] dirs = {1, 0, -1, 0, 1};public int uniquePathsIII(int[][] grid) {int m = grid.length, n = grid[0].length, startX = 0, startY = 0, remain = 2;vis = new boolean[m][n];for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {int x = grid[i][j];if (x == 0) remain++;else if (x == 1) {startX = i;startY = j;} }}return dfs(grid, startX, startY, remain);}int dfs(int[][] grid, int x, int y, int remain) {if (remain == 1 && grid[x][y] == 2) return 1;vis[x][y] = true;int cnt = 0;for (int k = 0; k < 4; k++) {int i = x + dirs[k], j = y + dirs[k + 1];if (i >= 0 && i < grid.length && j >= 0 && j < grid[0].length && grid[i][j] != -1 && !vis[i][j]) {cnt += dfs(grid, i, j, remain - 1);}}vis[x][y] = false;return cnt;}
}

三、求最短 Hamilton 路径

朴素算法,即 dfs 枚举 n 个点的全排列,计算路径长度取最小值,这样时间复杂度为 O ( n ∗ n ! ),如果 n 过大,那么对于计算机也是一个无解问题。

因为 n 个点,每个点有两种情况,经过和暂时未经过,有 2n 种状态,可以使用二进制状态压缩 DP 来优化这个朴素算法,时间复杂度为 O(n * 2 n) ,即状态数乘以状态转移复杂度得到。

使用一个 n 位二进制数来表示状态,若其第 i 位为 1,则表示第 i 个点已经被经过,反之未被经过。

f [ i ] [ j ] f[i][j] f[i][j] i 表示经过点的状态,且当前处于点 j 时的最短路径。

f [ 1 ] [ 0 ] = 0 f[1][0] = 0 f[1][0]=0, 即只经过了点 0,目前处于起点 0,最短路径长度为 0;
将 f 数组的其余值都设成无穷大。

f [ i ] [ j ] = m i n ( f [ i ˆ 1 < < j ] [ k ] + w e i g h t [ k ] [ j ] ) ​ , 其中 0 ≤ k < n ​。 f[i][j] = min(f[i \^\ 1 << j][k] + weight[k][j])​, 其中 0 ≤ k < n​。 f[i][j]=min(f[i ˆ1<<j][k]+weight[k][j])​,其中0≤k<n​。

如果考虑回到起点,就是求哈密顿回路。

import java.util.Arrays;
public class TSP {public static void main(String[] args) {        int[][] graph = {{0, 10, 15, 20},{10, 0, 35, 25},{15, 35, 0, 30},{20, 25, 30, 0}};int n = graph.length, INF = Integer.MAX_VALUE;int[][] dp = new int[1 << n][n];for (int i = 0; i < 1 << n; ++i) Arrays.fill(dp[i], INF);       dp[1][0] = 0; // 只有一个点,还没走当然走了 0for (int i = 0; i < 1 << n; ++i) {for (int j = 0; j < n; ++j) {if ((i >> j & 1) == 0) continue; // 还没有经过 jfor (int k = 0; k < n; ++k) if ((i >> k & 1) == 1) // 从已经过的 k 中取到 j 的最小值dp[i][j] = Math.min(dp[i][j], dp[i ^ 1 << j][k] + graph[k][j]);                          }}System.out.println(dp[(1 << n) - 1][n - 1]);}
}

943. 最短超级串

最终字符串最短,则这些字符串的重复部分最长。

假设已经选出了若干个字符串将它们排成一行且合并了重复部分,并且最后一个选出的字符串是 A[i],那么如果现在选出一个新的字符串 A[j],那么重复部分的长度会增加 overlap(A[i], A[j]),而与在 A[i] 之前选取了哪些字符串无关。

设 dp(mask, i) 表示已经选出的字符串为 mask,且最后一个选出的字符串是 A[i] 时的重复部分的最大长度。
枚举下一个选出的字符串 j,就有

dp(mask ^ 1 << j, j) = max{overlap(A[i], A[j]) + dp(mask, i)}

dp(mask, i) 只记录了重复部分的最大长度,要得到这个最大长度对应的字符串,还需要记录一下每个状态从哪个状态转移得来,最后通过逆推的方式还原这个字符串。

预先计算出所有的 overlap(A[i], A[j]);
使用动态规划计算出所有的 dp(mask, i),并记录每个状态从哪个状态转移得来,记为 parent;
通过 parent 还原这个字符串。

class Solution {public String shortestSuperstring(String[] words) {int n = words.length;// Populate overlapsint[][] overlaps = new int[n][n];for (int i = 0; i < n; ++i)for (int j = 0; j < n; ++j) {if (i == j) continue;int m = Math.min(words[i].length(), words[j].length());for (int k = m; k >= 0; --k)if (words[i].endsWith(words[j].substring(0, k))) {overlaps[i][j] = k;break;}}// dp[mask][i] = most overlap with mask, ending with ith elementint[][] dp = new int[1 << n][n];int[][] parent = new int[1 << n][n];for (int mask = 0; mask < 1 << n; ++mask) {Arrays.fill(parent[mask], -1);for (int bit = 0; bit < n; ++bit) {if ((mask >> bit & 1) == 0) continue; // 还没有选择// Let's try to find dp[mask][bit].  Previously, we had// a collection of items represented by pmask.int pmask = mask ^ 1 << bit; // 选择前的状态if (pmask == 0) continue;for (int i = 0; i < n; ++i) {if ((pmask >> i & 1) == 0) continue;// For each bit i in pmask, calculate the value// if we ended with word i, then added word 'bit'.int val = dp[pmask][i] + overlaps[i][bit];if (val > dp[mask][bit]) {dp[mask][bit] = val;parent[mask][bit] = i;}                   }}}// # Answer will have length sum(len(A[i]) for i) - max(dp[-1])// Reconstruct answer, first as a sequence 'perm' representing// the indices of each word from left to right.int[] perm = new int[n];boolean[] seen = new boolean[n];int t = 0;int mask = (1 << n) - 1;// p: the last element of perm (last word written left to right)int p = 0;for (int j = 0; j < n; ++j)if (dp[(1 << n) - 1][j] > dp[(1 << n) - 1][p])p = j;// Follow parents down backwards path that retains maximum overlapwhile (p != -1) {perm[t++] = p;seen[p] = true;int p2 = parent[mask][p];mask ^= 1 << p;p = p2;}// Reverse permfor (int i = 0; i < t / 2; ++i) {int v = perm[i];perm[i] = perm[t - 1 - i];perm[t - 1 - i] = v;}// Fill in remaining words not yet addedfor (int i = 0; i < n; ++i)if (!seen[i])perm[t++] = i;// Reconstruct final answer given permStringBuilder ans = new StringBuilder(words[perm[0]]);for (int i = 1; i < n; ++i) {int overlap = overlaps[perm[i - 1]][perm[i]];ans.append(words[perm[i]].substring(overlap));}return ans.toString();}
}

String[][] add; // add[i][j]是i字符串后跟着 j 字符串时,增加的字符串。
String[][] dps = new String[1 << len][len]; // dps[i][j] 是状态 i 时,以 j 为最后一个字符串的字符串。

class Solution {public String shortestSuperstring(String[] words) {int len = words.length;String[][] add = new String[len][len];for (int i = 0; i < len; i++) {String s1 = words[i];for (int j = 0; j < len; j++) {if (j != i) {String s2 = words[j];add[i][j] = s2;for (int k = 0; k < s1.length(); k++) {if (s2.startsWith(s1.substring(k))) {add[i][j] = s2.substring(s1.length() - k);break;}}}}}String[][] dps = new String[1 << len][len];for (int i = 0; i < len; i++) {dps[1 << i][i] = words[i];}for (int i = 1; i < 1 << len; i++) {for (int j = 0; j < len; j++) {if (Integer.bitCount(i) == 1) {continue;}int p = 1 << j;if ((i & p) != 0) {int rest = i ^ p;String curStr = null;for (int k = 0; k < len; k++) {if (dps[rest][k] != null && add[k][j] != null) {String str = dps[rest][k] + add[k][j];if (curStr == null || (curStr.length() > str.length())) {curStr = str;}}}dps[i][j] = curStr;}}}String ansStr = dps[(1 << len) - 1][0];for (int i = 1; i < len; i++) {String cur = dps[(1 << len) - 1][i];if (cur.length() < ansStr.length()) {ansStr = cur;}}return ansStr;}
}

847. 访问所有节点的最短路径

方法一:状态压缩 + 广度优先搜索

「访问所有节点的 最短 路径的长度」,图中 边长均为 1,使用 广度优先搜索 求出最短路径。

三元组 (u, mask, dist) 表示队列中的每一个元素中:

  • u 节点编号;
  • mask 是一个长度为 n 的二进制数,表示每一个节点是否经过。
  • dist 到当前节点为止经过的路径长度。

初始时,将所有的 (i, 2i, 0) 放入队列,表示可以从任一节点开始。在搜索的过程中,如果当前三元组中的 mask 包含 n 个 1( 2n - 1),那么就找到答案 dist。

同一个节点 u 以及节点的经过情况 mask 只被搜索到一次,可以使用数组或者哈希表记录 (u, mask) 是否已经被搜索过,防止无效的重复搜索。

class Solution {public int shortestPathLength(int[][] graph) {int n = graph.length;Deque<int[]> q = new LinkedList();boolean[][] vis = new boolean[n][1 << n];for (int i = 0; i < n; i++) {q.offer(new int[]{i, 1 << i, 0});vis[i][1 << i] = true;}while (!q.isEmpty()) {int[] tuple = q.poll();int i = tuple[0], mask = tuple[1], dist = tuple[2];if (mask == (1 << n) - 1) return dist;for (int v : graph[i]) { // 搜索相邻的节点int maskv = mask | 1 << v; // 将 mask 的第 v 位置为 1if (!vis[v][maskv]) {q.offer(new int[]{v, maskv, dist + 1});vis[v][maskv] = true;}}}return 0;}
}

方法二:预处理点对间最短路 + 状态压缩动态规划

图是连通图,计算出任意两个节点之间 u, v 间的最短距离,记为 d(u, v)。使用动态规划计算出最短路径。

对于任意一条经过所有节点的路径,它的某一个子序列一定是 0, 1, ⋯, n−1 的一个排列。称这个子序列上的节点为「关键节点」。在动态规划的过程中,通过枚举「关键节点」进行状态转移。

f[u][mask] 表示从任一节点开始到节点 u 为止,并且经过的「关键节点」状态为 mask 时的最短路径长度。由于 u 是最后一个「关键节点」,那么在进行状态转移时,可以枚举上一个「关键节点」v,即:


其中 mask\u 表示将 mask 的第 u 位从 1 变为 0 后的二进制表示。「关键节点」v 在 mask 中的对应位置必须为 1,将 f[v][mask\u] 加上从 v 走到 u 的最短路径长度为 d(v, u),取最小值即为 f[u][mask]。

最终的答案即为:

当 mask 中只包含一个 1 时,无法枚举满足要求的上一个「关键节点」v。若 mask 中只包含一个 1,说明位于开始的节点,还未经过任何路径,因此状态转移方程直接写为:

f[u][mask] = 0

在状态转移方程中,需要多次求出 d(v, u),因此在动态规划前将所有的 d(v, u) 预处理出来。这里有两种可以使用的方法,时间复杂度均为 O(n^3):

使用 Floyd 算法求出所有点对之间的最短路径长度;

可以进行 n 次广度优先搜索,第 i 次从节点 i 出发,也可以得到所有点对之间的最短路径长度。

class Solution {public int shortestPathLength(int[][] graph) {int n = graph.length;int[][] d = new int[n][n];for (int i = 0; i < n; ++i) Arrays.fill(d[i], n + 1);for (int i = 0; i < n; ++i)for (int j : graph[i]) d[i][j] = 1;// floyd for (int k = 0; k < n; ++k) for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) d[i][j] = Math.min(d[i][j], d[i][k] + d[k][j]); int[][] f = new int[n][1 << n];for (int i = 0; i < n; ++i) Arrays.fill(f[i], Integer.MAX_VALUE / 2);for (int mask = 1; mask < (1 << n); ++mask) {// 如果 mask 只包含一个 1,即 mask 是 2 的幂if ((mask & (mask - 1)) == 0) {int u = Integer.bitCount((mask & (-mask)) - 1);f[u][mask] = 0;} else {for (int u = 0; u < n; ++u) {if ((mask & (1 << u)) == 0) continue;for (int v = 0; v < n; ++v) if ((mask & (1 << v)) != 0 && u != v)f[u][mask] = Math.min(f[u][mask], f[v][mask ^ (1 << u)] + d[v][u]);                        }}}int ans = Integer.MAX_VALUE;for (int u = 0; u < n; ++u) ans = Math.min(ans, f[u][(1 << n) - 1]);return ans;}
}

1、旅行商问题的一般形式

旅行商问题(TSP):给定一系列城市和每对城市之间的距离,求解访问每一座城市一次并回到起始城市的最短回路。从图论的角度来看,该问题实质是在一个带权完全无向图中,找一个权值最小的哈密顿回路。

与旅行商问题的区别在于:可以重复访问某些节点,且在遍历完最后一个节点后不用回到出发点。

2、广度优先搜索的原理

广度优先搜索(BFS)算法是一种盲目搜索算法,目的是系统地检查图中所有节点,直到找到结果为止。由于广度优先搜索的扩展原则是先生成的节点先扩展,所以可以求得最短路径。利用一个队列 queue 来存储当前已经生成的节点,每次弹出队头元素进行下一步扩展。
例子:在以下图中寻找值为 8 的节点

首先将起点放入队列,这是第一个生成的节点。

开始第一轮循环,本轮队列中仅 1 个元素。
弹出队头元素 1,扩展,生成了 2, 3 两个节点,均放入队列。
队列中 1 个元素扩展完成,本次循环结束。开始新一轮循环,本轮队列中有 2 个元素

弹出队头元素 2,扩展,生成了 4, 5 两个节点,放入队列。

弹出队头元素 3,扩展生成 6, 7,放入队列。
队列中 2 个元素扩展完成,本次循环结束。开始新一轮循环,本轮队列中有 4 个元素

弹出队头元素 4,扩展生成 8,找到了答案,当前处在第 3 轮循环,所以最短路径为 3。此时本轮仍有 3 个元素未被扩展,但因为已经找到了答案,所以直接退出搜索。

但是 BFS 算法扩展的前提 是,每个节点可以以任意顺序扩展,也即一个节点与所有它可以扩展的 节点距离都相同。本题而言,需要求最短路径,且任意两个节点之间距离均为 1,所以可以使用 BFS 算法。

特别地,需要每次记录本轮循环队列中的节点数量,以便最终判定最短路径长度;另一方面,对于已生成的节点,需要标记,防止重复被生成。**为了写代码时更加方便直观,在扩展过程中不判断是否找到了答案,而是每次弹出队头元素时进行判断。**所以一般的 BFS 代码框架如下:

# 1.初始化队列及标记数组,存入起点
# 1.初始化队列及标记数组,存入起点
from collections import dequeq = deque()# graph = [[1],[0,2,4],[1,3,4],[2],[1,2]]
graph = [[1,2],[0,3,4],[0,5,6],[1,7],[1,7],[2,7,8],[2,8],[3,4,5],[5,6]]
target = 4
vis = [False for i in graph] # vectorq.extend(graph[0]) # 存入起点,标记
vis[0] = True# 2.开始搜索
while q:cur = q.popleft() # 弹出队头元素
#         找到答案,退出搜索if cur == target: print("hello")break#         action(cur) # 有些题目需要对当前元素做处理for x in graph[cur]:if not vis[x]:q.append(x)vis[x] = True

当然,我们也可以将当前扩展的距离作为一个变量一起存入队列:

from collections import dequeq = deque()
# graph = [[1],[0,2,4],[1,3,4],[2],[1,2]]
graph = [[1,2],[0,3,4],[0,5,6],[1,7],[1,7],[2,7,8],[2,8],[3,4,5],[5,6]]
target = 7
vis = [False for i in graph] q.append((0,0))
vis[0] = Truewhile q:cur, dist = q.popleft() print(cur,dist)if cur == target: print("distance = ", dist)break#         action(cur) # 有些题目需要对当前元素做处理for x in graph[cur]:if not vis[x]:q.append((x, dist + 1))vis[x] = True

3、状态压缩

本题需要遍历完图内全部节点,且可以重复访问某些节点。所以需要在搜索过程中,记录当前已经遍历了哪些节点。如果利用数组来存储每个节点的状态,在传参时较为不方便,效率不高。数据范围 n ≤ 12,说明可以利用状态压缩。

状态压缩也即用一个变量来表示当前状态,比较常用的方式是利用一个 n 位 k 进制数 mask 表示当前 n 个节点的所处的 k 个不同状态。对于本题,某个节点只需要记录是否遍历过,所以利用二进制即可。

一般而言,mask 从低到高第 i 位为 0 表示第 i 个节点还未被访问过,为 1 则相反。例如,假设有 3 个点,点 1 遍历过,点 2, 3 未遍历,则 mask = 001;若点 3 遍历过,点 1, 2 未遍历,则 mask = 100 。特别地,三个点均未遍历时,mask = 000 = 0,均遍历过时,mask = 111 = 2 k
−1
一些状态压缩的基本操作如下:

  • 访问第 i 个点的状态:state = (1 << i) & mask
  • 更改第 i 个点状态为 1:mask = mask | (1 << i)

4、基于状态压缩的广度优先搜索算法

通过广度优先搜索算法对图中节点进行扩展,并利用状态压缩记录节点的遍历情况。具体实现细节如下:

  • BFS 参数:当前节点编号 idx,当前搜索状态 mask,当前扩展距离 dist
  • BFS 起点:题目不限制起点,所以最开始可以将每个点都存入队列,对应状态为仅该点遍历。例如图中有 2 个点时,分别将第一个点及其对应的 mask = 01,第二个点和其对应的 mask = 10 存入。
  • BFS 终点:最终要求所有点均遍历,所以当 mask = 2n - 1 时搜索结束。
  • BFS 标记:尽管本题可以重复访问某些节点,但是在同一状态下重复访问某一节点必然是无用功。所以在实现时,利用一个二维标记数组记录某一状态下,某一节点的拓展情况,防止被重复扩展。

对于某一个状态,可以得到它由哪一个状态转移过来。
例如: 设有 7 个点,当前状态 1000001 表示 1、7 个点走过,它的上一个状态可以为 0000001 或 1000000。0000001 -> 1000001 说明此时是走了 7 -> 1 这条边。

dp[i][j] 表示当前 i 状态且正在访问第 j 个节点的最小花费。
那么上述结果为 dp[1000001][1] = min(dp[0000001][7] + w[7][1], dp[1000001][1]);

主要步骤
1 : 枚举状态从 1 ~(1<<n)
2 : 找到上一个走的点
3 : 枚举上一个状态
4 : 更新当前状态的最小值
5: 最后输出一个 dp[(1 << n) - 1][…] 或者枚举这个状态的所有点输出最小值或者再加上 w[…][1] 的权值计算哈密顿回路。

旅行商问题(TSP)相关推荐

  1. 遗传算法解决旅行商问题(TSP)

    遗传算法解决旅行商问题(TSP) 参考文章: (1)遗传算法解决旅行商问题(TSP) (2)https://www.cnblogs.com/studylyn/p/5097238.html 备忘一下.

  2. python实现大规模邻域搜索(LNS)求解旅行商问题(TSP)

    文章目录 1. 大规模邻域搜索算法 1.1. LNS定义 1.2. LNS邻域 1.3. LNS框架 2. 旅行商问题TSP 3. python代码示例及结果 1. 大规模邻域搜索算法 参考<H ...

  3. (Python)模拟退火算法解决旅行商问题(TSP)

    两种写法思路,最全备注,第二种个人感觉上理解起来稍容易一点: 第一种: import numpy as np import matplotlib.pyplot as plt import pdb# 解 ...

  4. 《MATLAB智能算法30个案例》:第22章 蚁群算法的优化计算——旅行商问题(TSP)优化

    @[TOC](<MATLAB智能算法30个案例>:第22章 蚁群算法的优化计算--旅行商问题(TSP)优化) 1. 前言 <MATLAB智能算法30个案例分析>是2011年7月 ...

  5. 粒子群算法求解旅行商问题TSP (JAVA实现)

    粒子群算法求解旅行商问题TSP 写在开头: 最近师妹的结课作业问我,关于使用粒子群求解TSP问题的思路.我想了想,自己去年的作业用的是遗传算法,貌似有些关联,索性给看了看代码.重新学习了一遍粒子群算法 ...

  6. 双调欧几里得旅行商问题_教学 | 旅行商问题(TSP)的整数规划模型

    介绍旅行商问题(TSP)的整数规划模型. 最近有朋友在后台咨询TSP的整数规划建模问题,经过学习相关资料,现简单介绍TSP两种经典的整数规划模型. 1 TSP问题概述 旅行商问题 (Traveling ...

  7. java 蚁群算法_Java蚁群算法(Ant Colony)求解旅行商问题(TSP)(二)

    算法准备 旅行商问题(TSP)是一个经典的图论问题.在给定一系列城市和他们之间的距离以后,一个旅行商人希望能够找到一条能够走遍所有城市,并返回起点城市的最短路径.既然路径能串起来所有的城市,那么问题中 ...

  8. java 双调旅行商 hamiltonian,双调欧几里得旅行商问题(TSP)

    最小环+欧拉回路=最短哈密顿图 介绍 TSP(Traveling Salesman Problem)即旅行商问题,是数学领域中著名问题之一.这个问题是这样的:假设有一个旅行商人要拜访n个城市,他必须选 ...

  9. 【数学建模】基于随机机会约束规划方法对旅行商问题TSP求解

    前言 旅行商问题(Traveling Salesman Problem, abbr. TSP)是一个典型的组合优化难题,属于 NP 难题,在交通运输.管道铺设.路线选择等很多领域具有广泛应用,对这个问 ...

  10. 旅行商问题(TSP)概述

    文章目录 引言 问题分析 认识本质 深入分析 解决方案 引言 TSP(Traveling Salesman Problem)即旅行商问题,是数学领域中著名问题之一.这个问题是这样的:假设有一个旅行商人 ...

最新文章

  1. JavaSE的一些基础内容
  2. Swift5.1 语言参考(十) 语法汇总
  3. 2021年春季学期期末统一考试 成本管理 试题
  4. Learn OpenGL(三)——顶点着色器(Vertext Shader)
  5. 过滤器和拦截器区别以及执行顺序
  6. 【LibTorch】Microsoft C++ 异常: c10::NotImplementedError,位于内存位置 0x000000E8A9DAEDC0 处。
  7. Debian 10上设置和配置证书颁发机构(CA)
  8. excel入门/常用的技巧
  9. 借问钱程何处有,牧童劝我学Python——2019python职位分析
  10. 笔记本电脑计算机怎么放在桌面,苹果电脑怎么把文件放在桌面
  11. 计算机启动时滴滴两声,电脑开机时出现滴滴两声后,不能开机,怎么回事。
  12. 兰德公司:零日漏洞平均生存期为6.9年
  13. VBA工程加密,工程不可查看加密
  14. @Value 读取yml 文件
  15. 海康威视网络摄像头购买指南(焦距像素等参数)
  16. linux美化桌面,Linux_设置动态壁纸来美化Ubuntu桌面,我们知道你想拥有一个有格调 - phpStudy...
  17. 毕业论文ppt的研究方法及过程计算机专业,计算思维原理研究与实现数据组织毕业论文4喜欢就下吧(全文完整版)...
  18. 杰奇1.X-3.X通用极端简系统,php7高效,杰奇系统多模版一库教程
  19. 内是不是半包围结构_半包围结构是什么意思 半包围结构字的书写规则
  20. JAVA的反射机制==用反射分析类的实现

热门文章

  1. 完全平方数(C语言,调用函数)
  2. A-Level经济真题(7)
  3. 这9个程序员岗位最牛!AI百万年薪夺冠
  4. 写了一个增量式的爬虫,但是并不完美,希望大牛们可以指正指正!
  5. 人工智能 2.知识表示
  6. JAVA中的CAS算法
  7. python 求 牛顿插值法中的差商表
  8. Matlab对图像进行傅里叶变换实例
  9. docker安装和启动
  10. 适合后台管理系统开发的前端框架