算法学习——图论(五)

一、哈密尔顿(Hamiltonian)问题概述

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

        在此基础上,延伸出了旅行商问题,即给定一系列城市和每对城市之间的距离,求解访问每一座城市⼀次并回到起始城市的最短回路。

        至今为止,该问题仍旧是NP-Hard问题,即只有指数级别的解,没有多项式级别的解。通常来说,有如下解法:

  • 回溯(暴力搜索)
  • 记忆化搜索
  • 状态压缩动态规划

二、不同路径III

        原题力扣980        . - 力扣(LeetCode)

1、回溯

        经典回溯写法,时间复杂度:O(4^(n*m)),其中 n 和 m 分别是地图的行列数;

        空间复杂度:O(n*m),回溯深度;若算上 result 数组,则为 O(n*m*P),其中 P 是路径数

int dirs[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
vector<vector<pair<int, int>>> result;
vector<pair<int, int>> path;
void backtracking(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y, int n, int m, int total)
{
	// 终止条件:到达终点而且路径长度等于总路径长度(注意要加上终点的1)
	if (grid[x][y] == 2 && path.size() == total + 1)
	{
		result.push_back(path);
		return;
	}
	for (const auto& dir : dirs)
	{
		int newx = x + dir[0];
		int newy = y + dir[1];
		if (newx < 0 || newx >= n || newy < 0 || newy >= m)
		{
			continue;
		}
		if (visited[newx][newy] == true || grid[newx][newy] == -1)
		{
			continue;
		}
		// 添加路径和标记
		path.push_back({newx, newy});
		visited[newx][newy] = true;
		// 递归
		backtracking(grid, visited, newx, newy, n, m, total);
		// 回溯
		path.pop_back();
		visited[newx][newy] = false;
	}
}
int uniquePathsIII(vector<vector<int>>& grid)
{
	int n = grid.size();
	int m = grid[0].size();
	int startx, starty, total = 0;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			// 找到起点和空格点的个数
			if (grid[i][j] == 1)
			{
				startx = i;
				starty = j;
				total++;
			}
			if (grid[i][j] == 0)
			{
				total++;
			}
		}
	}
	vector<vector<bool>> visited(n, vector<bool>(m, false));
	path.push_back({startx, starty});
	visited[startx][starty] = true;
	backtracking(grid, visited, startx, starty, n, m, total);
	return result.size();
}

2、记忆化搜索+状态压缩

        核心:利用二进制法保存每条路径的状态,当遇到相同的状态时,可以直接得出结果

        注意:memo 哈希表保存每个状态对应的最短路径信息,当重复搜索到某个状态时,直接调用即可;使用二进制位掩码替代 visited 数组来保存格子的访问信息

        状态压缩常用公式:

判断第 i 位是否为 1visited & (1<<i) != 0
如果第 i 位为0,设为 1visited + (1<<i)
如果第 i 位为1,设为 0visited - (1<<i)

        经过优化后,时间复杂度:O(n*m*2^(n*m)),其中 n 和 m 分别是地图的行数和列数,而n*m*2^(n*m)是状态数,每个状态最多只会被计算一次。因此,空间复杂度也为 O(n*m*2^(n*m))

int dirs[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
// 动态规划函数,参数为(地图,当前横坐标,当前纵坐标,剩余的可以访问的位置数量,地图行数,地图列数,记忆哈希表)
int dp(vector<vector<int>>& grid, int x, int y, int remaining, int n, int m, unordered_map<int, int>& memo)
{
	// 找到了终点,如果访问完了全部需要访问的位置,返回1,否则返回0
	if (grid[x][y] == 2)
	{
		if (remaining == 0)
		{
			return 1;
		}
		else
		{
			return 0;
		}
	}
	// 当前位置的状态键值
	int key = ((x * m + y) << (n * m)) + remaining;
	// 如果当前状态不在哈希表中
	if (!memo.count(key))
	{
		int res = 0; // 路径数量
		for (const auto& dir : dirs)
		{
			int newx = x + dir[0];
			int newy = y + dir[1];
			if (newx < 0 || newx >= n || newy < 0 || newy >= m)
			{
				continue;
			}
			// 如果移动后的位置可以访问
			if (remaining & (1 << (newx * m + newy)))
			{
				// 递归计算从新位置开始的路径数量,并累加到结果中
				res += dp(grid, newx, newy, remaining ^ (1 << (newx * m + newy)), n, m, memo);
			}
		}
		memo[key] = res; // 记忆结果
	}
	return memo[key]; // 返回当前位置的路径数量
}
int uniquePathsIII(vector<vector<int>>& grid)
{
	int n = grid.size();
	int m = grid[0].size();
	int startx, starty, initialRemaining = 0;
	unordered_map<int, int> memo; // <当前的状态信息,不同的路径数量>
	// 遍历地图,初始化各变量
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			// 如果当前位置是空地或终点,标记为可访问
			if (grid[i][j] == 0 || grid[i][j] == 2)
			{
				initialRemaining |= (1 << (i * m + j));
			}
			else if (grid[i][j] == 1)
			{
				startx = i;
				starty = j;
			}
		}
	}
	return dp(grid, startx, starty, initialRemaining, n, m, memo);
}

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值