算法学习——图论(二)

目录

一、沉默孤岛(广搜)

二、太平洋大西洋水流问题

1、暴力深搜

2、逆流而上

三、最大人工岛(标记地图,深搜)

四、单词接龙(广搜)

五、有向图的完全可达性(深搜)

六、岛屿的周长(避免惯性思维)


一、沉默孤岛(广搜)

        原题卡码102        102. 沉没孤岛

        思路:先遍历四周,把陆地改为2;再遍历一遍整图,把孤岛改为0,同时把2改回1

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

int dirs[4][2] = { {0,1},{1,0},{0,-1},{-1,0} };
void bfs(vector<vector<int>>& graph, int x, int y, int n, int m)
{
	queue<pair<int, int>> que;
	que.push({ x,y });
	graph[x][y] = 2;
	while (!que.empty())
	{
		pair<int, int> curNode = que.front();
		que.pop();
		for (const auto& dir : dirs)
		{
			int nextX = curNode.first + dir[0];
			int nextY = curNode.second + dir[1];
			if (nextX >= n || nextX < 0 || nextY >= m || nextY < 0)
			{
				continue;
			}
			if (graph[nextX][nextY] == 0 || graph[nextX][nextY] == 2)
			{
				continue;
			}
			que.push({ nextX,nextY });
			graph[nextX][nextY] = 2;
		}
	}
}
int main()
{
	int N, M;
	cin >> N >> M;
	vector<vector<int>> graph(N, vector<int>(M, 0));
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < M; j++)
		{
			cin >> graph[i][j];
		}
	}
	for (int i = 0; i < N; i++)
	{
		if (graph[i][0] == 1)
		{
			bfs(graph, i, 0, N, M);
		}
		if (graph[i][M - 1] == 1)
		{
			bfs(graph, i, M - 1, N, M);
		}
	}
	for (int j = 0; j < M; j++)
	{
		if (graph[0][j] == 1)
		{
			bfs(graph, 0, j, N, M);
		}
		if (graph[N - 1][j] == 1)
		{
			bfs(graph, N - 1, j, N, M);
		}
	}
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < M; j++)
		{
			if (graph[i][j] == 1)
			{
				graph[i][j] = 0;
			}
			if (graph[i][j] == 2)
			{
				graph[i][j] = 1;
			}
		}
	}
	for (int i = 0; i < N; i++)
	{
		for (int j = 0; j < M; j++)
		{
			cout << graph[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

二、太平洋大西洋水流问题

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

1、暴力深搜

       思路是对每一个点执行一次深搜,得到一个 visited 数组结果,再判断这个结果是否符合要求

       时间复杂度 O(N^2*M^2),超时

int dirs[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} };
void dfs(vector<vector<int>>& heights, vector<vector<bool>>& visited, int x, int y, int n, int m)
{
	visited[x][y] = true;
	for (const auto& dir : dirs)
	{
		int nextX = x + dir[0];
		int nextY = y + dir[1];
		if (nextX >= n || nextX < 0 || nextY >= m || nextY < 0)
		{
			continue;
		}
		if (visited[nextX][nextY] || heights[nextX][nextY] > heights[x][y])
		{
			continue;
		}
		dfs(heights, visited, nextX, nextY, n, m);
	}
}
bool isResult(vector<vector<bool>>& visited, int n, int m)
{
	bool pacific = false;
	bool atlantic = false;
	for (int i = 0; i < n; i++)
	{
		if (visited[i][0])
		{
			pacific = true;
			break;
		}
	}
	for (int i = 0; i < n; i++)
	{
		if (visited[i][m - 1])
		{
			atlantic = true;
			break;
		}
	}
	for (int j = 0; j < m; j++)
	{
		if (pacific)
		{
			break;
		}
		if (visited[0][j])
		{
			pacific = true;
			break;
		}
	}
	for (int j = 0; j < m; j++)
	{
		if (atlantic)
		{
			break;
		}
		if (visited[n - 1][j])
		{
			atlantic = true;
			break;
		}
	}
	return pacific && atlantic;
}
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights)
{
	int n = heights.size();
	int m = heights[0].size();
	vector<vector<int>> result;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			vector<vector<bool>> visited(n, vector<bool>(m, false));
			dfs(heights, visited, i, j, n, m);
			if (isResult(visited, n, m))
			{
				result.push_back({ i, j });
			}
		}
	}
	return result;
}

2、逆流而上

        优化思路,分别从太平洋和大西洋逆流而上,只有两个大洋同时到达的地块才是要找的结果。这样的好处是不需要重复建立和修改 visited 数组,而是一次遍历,全程有效。优化了时间复杂度,为O(N*M)       

int dirs[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} };
void dfs(vector<vector<int>>& heights, vector<vector<bool>>& visited, int x, int y, int n, int m)
{
	if (visited[x][y])
	{
		return;
	}
	visited[x][y] = true;
	for (const auto& dir : dirs)
	{
		int nextX = x + dir[0];
		int nextY = y + dir[1];
		if (nextX >= n || nextX < 0 || nextY >= m || nextY < 0)
		{
			continue;
		}
		if (heights[nextX][nextY] < heights[x][y]) // 要逆流而上
		{
			continue;
		}
		dfs(heights, visited, nextX, nextY, n, m);
	}
}
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights)
{
	int n = heights.size();
	int m = heights[0].size();
	vector<vector<bool>> pacific(n, vector<bool>(m, false)); // 从太平洋出发
	vector<vector<bool>> atlantic(n, vector<bool>(m, false)); // 从大西洋出发
	for (int i = 0; i < n; i++)
	{
		dfs(heights, atlantic, i, 0, n, m);
		dfs(heights, atlantic, i, m - 1, n, m);
	}
	for (int j = 0; j < m; j++)
	{
		dfs(heights, pacific, 0, j, n, m);
		dfs(heights, pacific, n - 1, j, n, m);
	}
	vector<vector<int>> result;
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			if (pacific[i][j] && atlantic[i][j]) // 只有太平洋和大西洋同时能到的,才加入结果
			{
				result.push_back({ i, j });
			}
		}
	}
	return result;
}

三、最大人工岛(标记地图,深搜)

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

        主要思路为先遍历地图,记录每个岛屿各自的面积;

        再修改地图中的海洋格并比较修改后的最大面积

int count; // 记录每个岛屿的面积
int dirs[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} };
void dfs(vector<vector<int>>& grid, vector<vector<bool>>& visited, int x, int y, int n, int m, int mark)
{
	if (visited[x][y] || grid[x][y] == 0)
	{
		return;
	}
	visited[x][y] = true;
	grid[x][y] = mark; // 标记岛屿编号
	count++;
	for (const auto& dir : dirs)
	{
		int nextX = x + dir[0];
		int nextY = y + dir[1];
		if (nextX >= n || nextX < 0 || nextY >= m || nextY < 0)
		{
			continue;
		}
		dfs(grid, visited, nextX, nextY, n, m, mark);
	}
}
int largestIsland(vector<vector<int>>& grid)
{
	int n = grid.size();
	int m = grid[0].size();
	vector<vector<bool>> visited(n, vector<bool>(m, false));
	unordered_map<int, int> areas; // 岛屿编号——面积对照表
	int mark = 2;                  // 初始岛屿编号
	bool isAllGrid = true;         // 是否全是陆地
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			if (grid[i][j] == 0)
			{
				isAllGrid = false;
			}
			if (!visited[i][j] && grid[i][j] == 1)
			{
				count = 0; // 新的岛屿,计数器重置
				dfs(grid, visited, i, j, n, m, mark);
				areas[mark] = count; // 记录岛屿面积
				mark++;              // 编号递增
			}
		}
	}
	if (isAllGrid) // 全是陆地,直接返回面积
	{
		return n * m;
	}
	int ans = 0;
	unordered_set<int> st; // 记录访问过的岛屿
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < m; j++)
		{
			count = 1; // 重置面积记录
			st.clear(); // 重置岛屿记录
			if (grid[i][j] == 0) // 找到了海洋格
			{
				for (const auto& dir : dirs) // 计算四周是否是岛屿
				{
					int nextX = i + dir[0];
					int nextY = j + dir[1];
					if (nextX >= n || nextX < 0 || nextY >= m || nextY < 0)
					{
						continue;
					}
					if (st.count(grid[nextX][nextY])) // 已经找到的岛屿,跳过
					{
						continue;
					}
					count += areas[grid[nextX][nextY]]; // 面积加起来
					st.insert(grid[nextX][nextY]); // 标记访问过的岛屿
				}
			}
			ans = max(ans, count);
		}
	}
	return ans;
}

四、单词接龙(广搜)

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

        广度优先搜索只要找到了终点,就必是最短路径

int ladderLength(string beginWord, string endWord, vector<string>& wordList)
{
	unordered_set<string> st(wordList.begin(), wordList.end()); // 记录字典
	if (st.find(endWord) == st.end())
	{
		return 0;
	}
	unordered_map<string, int> visited; // <访问过的字符串,路径长度>
	queue<string> que;
	que.push(beginWord);
	visited[beginWord] = 1;
	while (!que.empty())
	{
		string curWord = que.front();
		que.pop();
		int curLength = visited[curWord]; // 当前路径长度
		// 依次用26个字母替换当前字符串,符合条件的入队
		for (int i = 0; i < curWord.size(); i++)
		{
			string newWord = curWord;
			for (int j = 0; j < 26; j++)
			{
				newWord[i] = j + 'a';
				if (newWord == endWord)
				{
					return curLength + 1;
				}
				if (st.find(newWord) != st.end() && !visited.count(newWord))
				{
					que.push(newWord);
					visited[newWord] = curLength + 1;
				}
			}
		}
	}
	return visited[endWord];
}

五、有向图的完全可达性(深搜)

        原题卡码105        105. 有向图的完全可达性

        不需要回溯,因为不需要输出可行路径,只需要标记可达即可

vector<int> path;
void dfs(vector<list<int>>& graph, vector<bool>& visited, int cur)
{
	if (visited[cur])
	{
		return;
	}
	visited[cur] = true;
	for (const auto& next : graph[cur])
	{
		dfs(graph, visited, next);
	}
}
int main()
{
	int N, K;
	cin >> N >> K;
	vector<list<int>> graph(N + 1);
	int s, t;
	for (int i = 0; i < K; i++)
	{
		cin >> s >> t;
		graph[s].push_back(t);
	}
	vector<bool> visited(N + 1, false);
	dfs(graph, visited, 1);
	for (int i = 1; i <= N; i++)
	{
		if (visited[i] == false)
		{
			cout << -1 << endl;
			return 0;
		}
	}
	cout << 1 << endl;
	return 0;
}

六、岛屿的周长(避免惯性思维)

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

        不要一看到岛屿就采用深搜或者广搜,有些题简单分析计算即可

int islandPerimeter(vector<vector<int>>& grid)
{
	int total = 0; // 总路地块数量
	int cover = 0; // 相邻陆地数量
	for (int i = 0; i < grid.size(); i++)
	{
		for (int j = 0; j < grid[0].size(); j++)
		{
			if (grid[i][j] == 1)
			{
				total++;
				if (i - 1 >= 0 && grid[i - 1][j] == 1)
				{
					cover++;
				}
				if (j - 1 >= 0 && grid[i][j - 1] == 1)
				{
					cover++;
				}
			}
		}
	}
	return total * 4 - cover * 2;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值