第九周作业
存图方式
邻接矩阵
通过一个二维数组方式存储图的边,第一维表示起点,第二维表示终点,数组内容表示权。若两点不存在边,则通常在数组中存一个极大的数表示无穷大。
缺点:空间占用大,很多空间被浪费。
int e[MAXN][MAXN];
memset(e, 0x3f, sizeof(e));
for (int i = 0; i < m; i++)
{
int from, to, w;
cin >> from >> to >> w;
e[from][to] = min(w, e[from][to]);
e[to][from] = min(w, e[to][from]); //无向边时
}
for (int i = 1; i <= n; i++)
{
e[i][i] = 0;
}
邻接表
将二维数组改为动态数组(如vector),节省不存在的边的空间。如果需要存储权,则可以创建一个结构体。
struct edge
{
int to, w;
}
vector<edge> e[MAXN];
for (int i = 0; i < m; i++)
{
int from, to, w
cin >> from >> to >> w;
e[from].push_back({to, w});
e[to].push_back({from, w});
}
链式前向星
与动态数组不同的是,链式前向星类似于链表。有一个存储第一条边的数组,存储所有起始点的第一条边,后面的边通过地址逐个连接。每次添加新边时,插入在第一条边前,使新边成为第一条边。
struct edge
{
int to, w, next;
}
int head[MAXN];
edge e[MAXM];
int edgeCount = 0;
memset(head, 0, sizeof(head));
for (int i = 0; i < m; i++)
{
int from, to, w
cin >> from >> to >> w;
e[++edgeCount].to = to;
e[++edgeCount].w = w;
e[edgeCount].next = head[from];
head[from] = edgeCount;
}
求最短路径的算法
Floyd
运用了动态规划的思想。
从A到B的最短距离的计算方法:逐个将所有点(记作K点)作为间断点,A到B的最短距离是A点到K点距离+B点到K点之和的最小值。
状态转移方程:d[i, j] = min{d[i, k] + d[k, j], d[i, j]}
优点:能计算任意两点最短距离;可以计算负权值。
缺点:时间复杂度高。
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
d[i][j] = min(d[i][k] + d[k][j], d[i][j]);
}
}
}
Dijkstra
运用了贪心的思想。
定义一个集合,将起点放入集合。从起点开始,选择与起点相连的最近的点,将其加入集合。从这个点开始,更新相连的不在集合的点的距离为 这个点的距离加两点距离 和 相连的点距离 的较小值,选择不在集合的最近的点加入集合,并再从这个点开始重复操作,直到所有点都加入集合,即可得到起点到所有点的最小距离。
struct edge
{
int to, w, next;
}
bool vis[MAXN];
int d[MAXN];
memset(vis, 0, sizeof(vis));
memset(d, 0x3f, sizeof(d));
vis[s] = true;
dis[s] = 0;
for (int i = 1; i <= n; i++)
{
int next = 0;
for (int j = 1; j <= n; j++)
{
if (vis[e[i].to])
{
continue;
}
if (next == 0 || d[j] < d[next])
{
next = i;
}
}
vis[next] = true;
for (int j = head[next]; j != 0; j = e[j].next)
{
d[e[j].to] = min(d[next] + e[j].w, d[e[j].to]);
}
}
优化:找到最小值的过程时间复杂度较高。可以用优先队列来优化。每次将所有相连的不在集合的点加入队列,按距离排序,下一次进行时选择队列顶部(也就是距离最小的点)进行下一次操作。
struct edge
{
int to, w, next;
bool operator<(const edge& e) const
{
return w > e.w;
}
}
prioriy_queue<edge> q;
bool vis[MAXN];
int d[MAXN];
memset(vis, 0, sizeof(vis));
memset(d, 0x3f, sizeof(d));
q.push({s, 0, 0});
while (!q.empty())
{
int k = q.top().to;
q.pop();
if (vis[k])
{
continue;
}
vis[k] = true;
for (int i = head[k]; i != 0; i = e[k].next)
{
d[e[i].to] = min(e[i].w + d[k], de[e[i].to])
if (!vis[e[i].to])
{
q.push({e[i].to, d[e[i].to], 0});
}
}
}
缺点:只能求解非负权值的问题。
第一题
题目1
分别用dfs和bfs遍历图
思路1
题目要求遍历顺序按照序号大小,因此可以使用邻接表,然后对其排序。
代码1
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
#define MAXN 100001
#define MAXM 1000001
//邻接表
vector<int> e[MAXN];
//dfs和bfs标记是否访问过的数组
bool dfsvis[MAXN], bfsvis[MAXN];
//dfs
void dfs(int p)
{
for (int i = 0; i < (int)e[p].size(); i++)
{
if (!dfsvis[e[p][i]])
{
dfsvis[e[p][i]] = true;
cout << e[p][i] << " ";
dfs(e[p][i]);
}
}
}
//bfs的队列
queue<int> q;
//bfs
void bfs(int p)
{
while (!q.empty())
{
int p = q.front();
q.pop();
for (int i = 0; i < (int)e[p].size(); i++)
{
if (!bfsvis[e[p][i]])
{
bfsvis[e[p][i]] = true;
cout << e[p][i] << " ";
q.push(e[p][i]);
}
}
}
}
int main()
{
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int from, to;
cin >> from >> to;
//有向图
e[from].push_back(to);
}
//对邻接表排序,保证每次访问的顺序都是从小到大
for (int i = 1; i <= n; i++)
{
sort(e[i].begin(), e[i].end());
}
//dfs
dfsvis[1] = true;
cout << 1 << " ";
dfs(1);
cout << endl;
//bfs
bfsvis[1] = true;
cout << 1 << " ";
q.push(1);
bfs(1);
return 0;
}
第二题
题目2
求一个无向图中所有点到其他点的距离之和。
思路2
由于需要求解所有点之间最短距离,可以使用Floyd算法。
代码2
#include<iostream>
#include<cstring>
using namespace std;
#define MAXN 501
#define MAXM 10001
#define MOD 998244354
int e[MAXN][MAXN];
int main()
{
int n, m;
cin >> n >> m;
//使用邻接矩阵存储
memset(e, 0x3f, sizeof(e));
for (int i = 0; i < m; i++)
{
int from, to, l;
cin >> from >> to >> l;
e[from][to] = min(l, e[from][to]);
//无向图
e[to][from] = min(l, e[to][from]);
}
for (int i = 1; i <=n; i++)
{
e[i][i] = 0;
}
//Floyd模板
for (int k = 1; k <= n; k++)
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
e[i][j] = min(e[i][k] + e[k][j], e[i][j]);
}
}
}
//计算和并输出
for (int i = 1; i <= n; i++)
{
int dis = 0;
for (int j = 1; j <= n; j++)
{
dis += e[i][j];
dis %= MOD;
}
cout << dis << endl;
}
return 0;
}
第三题
题目3
给一个有向边的带非负权图和一个起点,求到图中其他点的距离。
思路3
由于是非负权,可以使用Dijkstra算法。
代码3
#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
#define MAXN 100001
#define MAXM 200002
//使用链式前向星
//边的结构体
struct edge
{
//下一条边、这条边的另一个端点、距离
int next, to, d;
//用于优先队列排序
bool operator<(const edge& another) const
{
return d > another.d;
}
};
//所有边
edge e[MAXM];
//链式前向星头、边计数(用于添加)
int head[MAXN], edgeCount = 0;
//Dijkstra的是否在集合中
bool vis[MAXN];
//存储距离
int dis[MAXN];
//添加边
//参数:起点、终点、距离
void add(int from, int to, int d)
{
e[++edgeCount].to = to;
e[edgeCount].next = head[from];
e[edgeCount].d = d;
head[from] = edgeCount;
}
//优先队列(Dijkstra的优化)
priority_queue<edge> q;
int main()
{
int n, m, s;
cin >> n >> m >> s;
for (int i = 1; i <= m; i++)
{
int from, to, d;
cin >> from >> to >> d;
add(from, to, d);
}
//初始化所有距离无穷远
memset(dis, 0x3f, sizeof(dis));
//起点距离为0
dis[s] = 0;
//把起点放入优先队列
q.push({0, s, 0});
//Dijkstra
while (!q.empty())
{
//点
int k = q.top().to;
q.pop();
//在集合中就不再处理
if (vis[k])
{
continue;
}
//标记再集合中
vis[k] = true;
//遍历所有相连的点
for (int i = head[k]; i != 0; i = e[i].next)
{
//更新距离
int to = e[i].to;
dis[to] = min(dis[k] + e[i].d, dis[to]);
//放入队列
if (!vis[to])
{
q.push({0, to, dis[to]});
}
}
}
for (int i = 1; i <= n; i++)
{
cout << dis[i] << ' ';
}
return 0;
}