首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【动态规划】【广度优先搜索】【状态压缩】847 访问所有节点的最短路径

  • 25-02-22 05:01
  • 4161
  • 12279
blog.csdn.net

作者推荐

视频算法专题

本文涉及知识点

动态规划汇总
广度优先搜索 状态压缩

LeetCode847 访问所有节点的最短路径

存在一个由 n 个节点组成的无向连通图,图中的节点按从 0 到 n - 1 编号。
给你一个数组 graph 表示这个图。其中,graph[i] 是一个列表,由所有与节点 i 直接相连的节点组成。
返回能够访问所有节点的最短路径的长度。你可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边。
示例 1:
输入:graph = [[1,2,3],[0],[0],[0]]
输出:4
解释:一种可能的路径为 [1,0,2,0,3]
示例 2:
输入:graph = [[1],[0,2,4],[1,3,4],[2],[1,2]]
输出:4
解释:一种可能的路径为 [0,1,4,2,3]
参数范围:
n == graph.length
1 <= n <= 12
0 <= graph[i].length < n
graph[i] 不包含 i
如果 graph[a] 包含 b ,那么 graph[b] 也包含 a
输入的图总是连通图

广度优先搜索

需要记录那些节点已经访问,用状态压缩 (1 << i )表示第i个节点已访问。
还要记录此路径的最后节点。
这两个状态相同,后面的路径则相同。 由于是广度优先搜索,所以路径短的先处理,每个状态只会处理一次。
vDis 记录各状态的最短路径数。
que 记录状态。
时间复杂度:O(n2nn) 枚举起点O(n) 枚举状态数O(2^n) 每个状态处理。

核心代码

class Solution {
public:
	int shortestPathLength(vector<vector<int>>& graph) {
		m_c = graph.size();
		m_iMaskCount = 1 << m_c;
		for (int i = 0; i < m_c; i++)
		{
			BFS(graph, i);
		}
		return m_iRet;
	}
	void BFS(vector<vector<int>>& neiBo,int start)
	{
		vector<vector<int>> vDis(m_c, vector<int>(m_iMaskCount, m_iNotMay));
		queue<pair<int, int>> que;
		auto Add = [&](int node, int iPreMask,int iNew)
		{
			const int iMask = iPreMask | (1 << node);
			if (vDis[node][iMask] <= iNew )
			{
				return ;
			}
			vDis[node][iMask] = iNew;
			que.emplace(node, iMask);
		};
		Add( start,0, 0);
		while (que.size())
		{
			auto [preNode, preMask] = que.front();
			const int iNew = vDis[preNode][preMask]+1;
			que.pop();
			for (const auto& next : neiBo[preNode])
			{
				Add(next, preMask, iNew);
			}
		}
		for (const auto& v : vDis)
		{
			m_iRet = min(m_iRet, v.back());
		}
	}
	const int m_iNotMay = 100'000;
	int m_c, m_iMaskCount;
	int m_iRet = m_iNotMay;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

测试用例

template<class T>
void Assert(const T& t1, const T& t2)
{
	assert(t1 == t2);
}

template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
	if (v1.size() != v2.size())
	{
		assert(false);
		return;
	}
	for (int i = 0; i < v1.size(); i++)
	{
		Assert(v1[i], v2[i]);
	}

}

int main()
{	
	vector<vector<int>> graph;
	{
		Solution sln;
		graph = { {1,2,3},{0},{0},{0} };
		auto res = sln.shortestPathLength(graph);
		Assert(res, 4);
	}

	{
		Solution sln;
		graph = { {1},{0,2,4},{1,3,4},{2},{1,2} };
		auto res = sln.shortestPathLength(graph);
		Assert(res, 4);

	}
	
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

动态规划

节点的距离用多源路径的最短距离。

动态规划的状态表示

mask&(1 << next)表示经过了next节点。
vDis[node][mask] 有以下两种含义:
一, 以node结尾,经过mask指定节点的最短路径经过的节点数。
二,以node结尾,且只经过node节点一次,经过mask指定节点的最短路径经过的节点数。
含义二,如果存在,则是含义二,否则是含义一。 必须枚举所有符合含义二的可能。

动态规划的转移方程

vDis[next][maks|next]= MinSelf n e x t = 0 m c − 1 \Large_{next=0}^{m_c-1} next=0mc​−1​vDis[i][mask]+距离(i,next)
vDis[i][mask] 必须合法,且mask不包括next节点

动态规划的填表顺序

mask从1到大,确保动态规划的无后效性。某路径的编码是mask,经过新节点next后,新编码为iNewMask。则iNewMask-mask = 1 << next
1 << next 恒大于0。

动态规划的初始值

全部为不存在的数

动态规划的返回值

Min j = 0 m c − 1 \Large_{j=0}^{m_c-1} j=0mc​−1​vDis[j].back() -1

证明

将最短路径的重复节点删除,保留任意一个。删除后为: i 1 \Large_1 1​ i 2 \Large_2 2​ …i n \Large_n n​ 。任意i k \Large_k k​到i k + 1 \Large_{k+1} k+1​的路径一定是最短,否则替换成最短。直接枚举,12! 超时。 用动态规划,共2nn种状态,空间复杂度O(2nn),每种状态转移时间复杂度O(n),故总时间复杂度O(2nnn)。

代码

//多源码路径
template<class T, T INF = 1000 * 1000 * 1000>
class CFloyd
{
public:
	CFloyd(const  vector<vector<T>>& mat)
	{
		m_vMat = mat;
		const int n = mat.size();
		for (int i = 0; i < n; i++)
		{//通过i中转
			for (int i1 = 0; i1 < n; i1++)
			{
				for (int i2 = 0; i2 < n; i2++)
				{
					//此时:m_vMat[i1][i2] 表示通过[0,i)中转的最短距离
					m_vMat[i1][i2] = min(m_vMat[i1][i2], m_vMat[i1][i] + m_vMat[i][i2]);
					//m_vMat[i1][i2] 表示通过[0,i]中转的最短距离
				}
			}
		}
	};
	vector<vector<T>> m_vMat;
};

class Solution {
public:
	int shortestPathLength(vector<vector<int>>& graph) {
		m_c = graph.size();
		m_iMaskCount = 1 << m_c;
		vector<vector<int>> mat(m_c, vector<int>(m_c, 1000 * 1000 * 1000));
		for (int i = 0; i < m_c; i++)
		{
			for (const auto& j : graph[i])
			{
				mat[i][j] = 1;
			}
		}
		CFloyd floyd(mat);
		vector<vector<int>> vDis(m_c, vector<int>(m_iMaskCount, m_iNotMay));
		for (int i = 0; i < m_c; i++)
		{	
			vDis[i][1 << i] = 1;
		}
		for (int mask = 1; mask < m_iMaskCount; mask++)
		{
			for (int i = 0; i < m_c; i++)
			{
				if (vDis[i][mask] >= m_iNotMay)
				{
					continue;
				}
				for (int next = 0 ;next < m_c ;next++ )
				{
					if ((1 << next) & mask)
					{
						continue;//已经访问
					}
					const int iNewMask = (1 << next) | mask;
					vDis[next][iNewMask] = min(vDis[next][iNewMask], vDis[i][mask] + floyd.m_vMat[i][next]);
				}
			}
		}
		int iRet = m_iNotMay;
		for (const auto& v : vDis)
		{
			iRet = min(iRet, v.back());
		}
		return iRet-1;
	}
	const int m_iNotMay = 100'000;
	int m_c, m_iMaskCount;

};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

2023年1月

class Solution {
public:
int shortestPathLength(vector& graph) {
auto Add = [this](int iMask, int iPos, int iOpeNum)
{
if (INT_MAX != m_vMaskPosMinOpe[iMask][iPos])
{
return;
}
m_vQue.emplace_back(iMask, iPos);
m_vMaskPosMinOpe[iMask][iPos] = iOpeNum;
};
m_c = graph.size();
for (int i = 0; i < sizeof(m_vMaskPosMinOpe) / sizeof(m_vMaskPosMinOpe[0]); i++)
{
for (int j = 0; j < sizeof(m_vMaskPosMinOpe[0]) / sizeof(m_vMaskPosMinOpe[0][0]); j++)
{
m_vMaskPosMinOpe[i][j] = INT_MAX;
}
}
for (int i = 0; i < m_c; i++)
{
Add(1 << i, i, 0);
}
for (int i = 0; i < m_vQue.size(); i++)
{
const int iMask = m_vQue[i].first;
const int iPos = m_vQue[i].second;
for (auto& next : graph[iPos])
{
int iNewMask = iMask | (1 << next);
Add(iNewMask, next, m_vMaskPosMinOpe[iMask][iPos] + 1);
}
}
int iMin = INT_MAX;
for (int i = 0; i < sizeof(m_vMaskPosMinOpe[0]) / sizeof(m_vMaskPosMinOpe[0][0]); i++)
{
iMin = min(iMin, m_vMaskPosMinOpe[(1 << m_c) - 1][i]);
}
return iMin;
}
vector> m_vQue;
int m_vMaskPosMinOpe[1 << 12 ][12];
int m_c;
};

2023年8月

class Solution {
public:
int shortestPathLength(vector& graph) {
auto Add = [this](int iMask, int iPos, int iOpeNum)
{
if (INT_MAX != m_vMaskPosMinOpe[iMask][iPos])
{
return;
}
m_vQue.emplace_back(iMask, iPos);
m_vMaskPosMinOpe[iMask][iPos] = iOpeNum;
};
m_c = graph.size();
for (int i = 0; i < sizeof(m_vMaskPosMinOpe) / sizeof(m_vMaskPosMinOpe[0]); i++)
{
for (int j = 0; j < sizeof(m_vMaskPosMinOpe[0]) / sizeof(m_vMaskPosMinOpe[0][0]); j++)
{
m_vMaskPosMinOpe[i][j] = INT_MAX;
}
}
for (int i = 0; i < m_c; i++)
{
Add(1 << i, i, 0);
}
for (int i = 0; i < m_vQue.size(); i++)
{
const int iMask = m_vQue[i].first;
const int iPos = m_vQue[i].second;
for (auto& next : graph[iPos])
{
int iNewMask = iMask | (1 << next);
Add(iNewMask, next, m_vMaskPosMinOpe[iMask][iPos] + 1);
}
}
int iMin = INT_MAX;
for (int i = 0; i < sizeof(m_vMaskPosMinOpe[0]) / sizeof(m_vMaskPosMinOpe[0][0]); i++)
{
iMin = min(iMin, m_vMaskPosMinOpe[(1 << m_c) - 1][i]);
}
return iMin;
}
vector> m_vQue;
int m_vMaskPosMinOpe[1 << 12 ][12];
int m_c;
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

相关

下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653

我想对大家说的话
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。

群中有博文配套源码
QQ群名片
注:本文转载自blog.csdn.net的闻缺陷则喜何志丹的文章"https://blog.csdn.net/he_zhidan/article/details/135681778"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top