大家好,欢迎来到IT知识分享网。
目录
一,哈密顿回路、哈密顿链路
图的哈密顿回路是指包含图的所有节点的回路。
图的哈密顿链路是指包含图的所有节点的链路。
二,相关定理
1,Dirac定理
如果G是一个n(n≥3)个点的无向简单图,所有节点的度数都 ≥ n/2.0,则G中存在哈密顿回路。
证明过程:
(1)G是连通图
(2)设最长路径是L,用抽屉原理可以证明L是个环
(3)如果有节点不在环中,根据连通性可以加入一个点把环变成一条更长的路径。所以所有节点都在这个环中。
2,Tutte定理
所有4连通平面图都有哈密顿圈。证明过程没研究。
关于平面图,参考常见的图
3,竞赛图必有哈密顿链路
关于竞赛图,参考常见的图
竞赛图中不存在环当且仅当所有顶点的出度从小到大排列依次为0, 1, 2, … , n-1 。
翻译成赛事语言就是,单循环赛结果没有环,当且仅当所有人的实力是线性排序的,没有任何一次比赛发生逆袭现象。
三,应用
1,旅行商问题(TSP)
给定一系列城市和每对城市之间的距离,求解访问每一座城市一次并回到起始城市的最短回路。
这是NP难问题。
2,芯片通孔
芯片上有很多不同尺寸的通孔,用于走线。
同一尺寸的通孔用1个钻头全部打完之后,再换钻头。
对于1个钻头来说,如何走最短的距离把所有通孔打完?
四,求解哈密顿链路
哈密顿问题其实和TSP问题差不多,是NP问题,没有特别好的算法。
回溯法:
class Hamilton { public: stack<int> hami;//哈密顿链路 Hamilton(int n, map<int, vector<int>>& m, int type)//type=0是无向图 1是有向图 { this->n = n; this->m = m; this->type = type; for (int i = 0; i < n; i++)dfs(i); } private: bool dfs(int k) { s.push(k); if (s.size() == n) { hami = s; return true; } for (auto nk : m[k]) { if (visit[k])continue; visit[k] = 1; if (dfs(nk))return true; visit[k] = 0; } s.pop(); return false; } int n; int type; map<int, vector<int>> m;//邻接表 map<int, int>visit; stack<int>s; };
力扣 996. 正方形数组的数目
给定一个非负整数数组 A
,如果该数组每对相邻元素之和是一个完全平方数,则称这一数组为正方形数组。
返回 A 的正方形排列的数目。两个排列 A1
和 A2
不同的充要条件是存在某个索引 i
,使得 A1[i] != A2[i]。
示例 1:
输入:[1,17,8] 输出:2 解释: [1,8,17] 和 [17,8,1] 都是有效的排列。
示例 2:
输入:[2,2,2] 输出:1
提示:
1 <= A.length <= 12
0 <= A[i] <= 1e9
vector<int> gnums; class Hamilton { public: map<long long, int>ans; Hamilton(int n, map<int, vector<int>>& m, int type)//type=0是无向图 1是有向图 { this->n = n; this->m = m; this->type = type; for (int i = 0; i < n; i++)dfs(i,1); } private: bool dfs(int k,int deep) { if (visit[k])return false; long long h2 = h; hash(k); if (hashVisit[h]) goto RET; hashVisit[h] = 1; if (deep == n) { ans[h] = 1; h = h2; return true; } visit[k] = 1; for (auto nk : m[k]) { dfs(nk,deep+1); } RET: visit[k] = 0; h = h2; return false; } void hash(int k) { h = ((h * (gnums[k]+123) + 666) * 789)%; } int n; int type; map<int, vector<int>> m;//邻接表 map<int, int>visit; map<long long, int>hashVisit; long long h = ; }; class Solution { public: int numSquarefulPerms(vector<int>& nums) { int n = nums.size(); map<int, vector<int>>m; for (int i = 0; i < n; i++)for (int j = i + 1; j < n; j++) { if (isSquare(nums[i] + nums[j]))m[i].push_back(j), m[j].push_back(i); } gnums = nums; return Hamilton(n, m, 0).ans.size(); } bool isSquare(int n) { int x = sqrt(n); return x * x == n; } };
力扣 980. 不同路径 III
在二维网格 grid
上,有 4 种类型的方格:
1
表示起始方格。且只有一个起始方格。2
表示结束方格,且只有一个结束方格。0
表示我们可以走过的空方格。-1
表示我们无法跨越的障碍。
返回在四个方向(上、下、左、右)上行走时,从起始方格到结束方格的不同路径的数目。
每一个无障碍方格都要通过一次,但是一条路径中不能重复通过同一个方格。
示例 1:
输入:[[1,0,0,0],[0,0,0,0],[0,0,2,-1]] 输出:2 解释:我们有以下两条路径: 1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2) 2. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2)
示例 2:
输入:[[1,0,0,0],[0,0,0,0],[0,0,0,2]] 输出:4 解释:我们有以下四条路径: 1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2),(2,3) 2. (0,0),(0,1),(1,1),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,3),(1,3),(2,3) 3. (0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(1,1),(0,1),(0,2),(0,3),(1,3),(2,3) 4. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2),(2,3)
示例 3:
输入:[[0,1],[2,0]] 输出:0 解释: 没有一条路能完全穿过每一个空的方格一次。 请注意,起始和结束方格可以位于网格中的任意位置。
提示:
1 <= grid.length * grid[0].length <= 20
class Hamilton { public: map<long long, int>ans; Hamilton(int n, map<int, vector<int>>& m, int type,int start,int e)//type=0是无向图 1是有向图 { this->n = n; this->m = m; this->type = type; this->e = e; dfs(start,1); } private: bool dfs(int k,int deep) { if (visit[k])return false; long long h2 = h; hash(k); if (hashVisit[h]) goto RET; hashVisit[h] = 1; if (deep == n) { ans[h] = 1; h = h2; return true; } visit[k] = 1; for (auto nk : m[k]) { if (deep < n - 1 && nk == e)continue; dfs(nk,deep+1); } RET: visit[k] = 0; h = h2; return false; } void hash(int k) { h = ((h * (k+123) + 666) * 789)%; } int n; int type; map<int, vector<int>> m;//邻接表 map<int, int>visit; map<long long, int>hashVisit; long long h = ; int e; }; class Solution { public: int uniquePathsIII(vector<vector<int>>& grid) { row=grid.size(),col = grid[0].size(); int n = row*col; map<int, vector<int>>m; int s, e; for (int i = 0; i < row; i++)for (int j = 0; j < col; j++) { if (grid[i][j] == -1) { n--; continue; } if (grid[i][j] == 1)s = id(i, j); if (grid[i][j] == 2)e = id(i, j); vector<int> v = getNeighbor4(id(i, j)); for (int k : v)if(grid[k/col][k%col]!=-1)m[k].push_back(id(i, j)); } return Hamilton(n, m, 0,s,e).ans.size(); } int id(int x, int y) { return x * col + y; } vector<int> getNeighbor4(int k) { vector<int>ans; if (k >= col)ans.push_back(k - col); if (k < (row - 1) * col)ans.push_back(k + col); if (k % col)ans.push_back(k - 1); if (k % col < col - 1)ans.push_back(k + 1); return ans; } int row,col; };
五,相关puzzle
1,数字满格、数阵迷踪、数字迷途、数字连线
数字满格、数阵迷踪、数字迷途、数字连线
2,踏马、立方骑士
踏马、立方骑士
3,矩形图中马的哈密顿回路、链路
定理:
如果n是偶数(n>4)那么n*n的棋盘有一个哈密顿回路。
如果n是奇数(n>3)那么n*n的棋盘有一个哈密顿链路。
像这样的图还可以构造一些,不过基本上都差不多。
总结起来就是,最中间的格子是比较特殊的,上图中标号1-8的这8个格子构成1个回路,
其余的16个格子也构成1个回路,相当于5*5的棋盘其实就是3个回路组合而成,只要适当地选择断点,即可把3个回路连接成一条链。
如果选取的起点不同,得到的哈密顿链还是略有区别的。
比如:112象棋(12)
关于上面的2个定理,书上的意思是用数学归纳法来证明,从n=k变成n=k+4相当于在原来的基础上,套上了一个宽度为2的边框。这个边框是若干个哈密顿回路组成的,只要在适当的地方断开,就可以和里面的k*k连接起来。
下面讨论,7*7的棋盘去掉中间的3*3如何形成哈密顿回路。
首先,角落的点只有2个邻居,那么在回路里面,这2个点也一定都是他的邻居。
其他的,以此类推,可以得到下图:
对于那些已经连了2条线的点,自然是没有任何悬念了,所以接下来只需要考虑那些恰好连了1条线的点该如何连接起来。将上图化简如下:
黑色的线表示一定相连,红色的线表示可能相连。
这样,可以得到这16个点的哈密顿回路:
与此对应,可以得到原图:
这是由2个哈密顿回路构成的。
这样,整个通过把大的回路断开一个地方,可以得到下图:
在把紫色的哈密顿回路和这个长为41的哈密顿链连接起来,即可得到7*7的哈密顿链
六,点的回路覆盖问题
1,回路覆盖问题
对于没有割边的图,求一组简单回路,覆盖所有的点,且总边数最少。
2,相关定理
n个点的无割边图,存在一组简单回路,覆盖所有的点,且总边数不超过2n-2
3,应用
如果按照这个方式建设网络,就能得到边数很少的无割边图。
无割边意味着至少是2连通,比普通的连通图稳定性更好。
七,最短哈密顿链路
对于有向图,求所有的哈密顿链路中,长度最短的那条。
1,模板
思路:状态压缩DP
时间复杂度:O(n^2 * 2^n)
//最短哈密顿链路 template<typename T = int> class MinHamilton { public: MinHamilton(int n, DirectedGraphData<T>& g) :g(g) //n的上限大概是15 { this->n = n; startId = g.startId; m.clear(); path.clear(); } vector<int> getMinPath() { int allId = (1 << n) - 1; int ans = INT_MAX; for (int i = 0; i < n; i++) { ans = min(ans, dp(i, allId ^ (1 << i))); } for (int i = 0; i < n; i++) { if (m[i][allId ^ (1 << i)] == ans) { reBuild(i, allId ^ (1 << i), ans); return path; } } return vector<int>{}; } private: int dp(int id, int allOtherId) { auto& mi = m[id]; if (mi.find(allOtherId) != mi.end())return mi[allOtherId]; if (!allOtherId)return mi[allOtherId] = 0; int ans = INT_MAX; for (int i = 0; i < n; i++) { if ((allOtherId & (1 << i)) == 0)continue; auto it = g.edgeMap.find(make_pair(id, startId + i)); if (it == g.edgeMap.end())continue; int x = dp(i, allOtherId ^ (1 << i)); if (x < INT_MAX)ans = min(ans, x + it->second); } return mi[allOtherId] = ans; } void reBuild(int id, int allOtherId, int ans) { path.push_back(id); if (!allOtherId)return; for (int i = 0; i < n; i++) { if ((allOtherId & (1 << i)) == 0)continue; auto it = g.edgeMap.find(make_pair(id, startId + i)); if (it == g.edgeMap.end())continue; if (m[i][allOtherId ^ (1 << i)] == ans - it->second) { return reBuild(i, allOtherId ^ (1 << i), ans - it->second); } } return vector<int>{}; } private: DirectedGraphData<T>& g; int n; int startId; map<int, map<int, int>>m; vector<int>path; };
2,力扣 943. 最短超级串
给定一个字符串数组 words
,找到以 words
中每个字符串作为子字符串的最短字符串。如果有多个有效最短字符串满足题目条件,返回其中 任意一个 即可。
我们可以假设 words
中没有字符串是 words
中另一个字符串的子字符串。
示例 1:
输入:words = ["alex","loves","leetcode"] 输出:"alexlovesleetcode" 解释:"alex","loves","leetcode" 的所有排列都会被接受。
示例 2:
输入:words = ["catg","ctaagt","gcta","ttca","atgcatc"] 输出:"gctaagttcatgcatc"
提示:
1 <= words.length <= 12
1 <= words[i].length <= 20
words[i]
由小写英文字母组成words
中的所有字符串 互不相同
class Solution { public: string shortestSuperstring(vector<string>& words) { if (words.size() == 1)return words[0]; vector<DirectedEdge<>> edges; for (int i = 0; i < words.size(); i++) { for (int j = 0; j < words.size(); j++) { if (i == j)continue; edges.push_back(DirectedEdge<>{vector<int>{i, j, -sameNum(words[i], words[j])}}); } } DirectedGraphData<> g(edges); MinHamilton<> opt(words.size(), g); auto v = opt.getMinPath(); string ans = words[v[0]]; for (int i = 1; i < v.size(); i++) { int len = sameNum(words[v[i - 1]], words[v[i]]); ans += words[v[i]].substr(len, words[v[i]].length() - len); } return ans; } int sameNum(string& s1, string& s2) { int len = min(s1.length(), s2.length()); for (int i = len - 1; i; i--) { if (s1.substr(s1.length() - i, i) == s2.substr(0, i))return i; } return 0; } };
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/128663.html