边的存储方式
1、vector储存
const int N = 1e5 + 7; //节点数
const int M = 5e5 + 7; //路径数
const ll INF = 1e18;
int u[M], v[M], w[M];
ll d1[N], d2[N];//d1正向图,d2反向图
struct Node {
int v;
ll cost;
bool operator < (Node b)const {
return cost > b.cost;
}
};
vector<Node> e[N];
int n, m;
int main() {
n = read(), m = read();
for (int i = 1; i <= m; ++i) {
u[i] = read(), v[i] = read(), w[i] = read();
e[u[i]].push_back(Node{ v[i], w[i] });
e[v[i]].push_back(Node{ u[i], w[i] });
}
return 0;
} 2、前向星保存
const int N = 5e3 + 7; //节点数
const int M = 5e3 + 7; //路径数
const int INF = 0x3f3f3f3f;
int head[N], tot = 0;//前向星变量
struct Node {
//int u;
int v, w, next;
} edge[M << 1];
void add(int u, int v, int w) {
tot++;
//edge[tot].u = u;
edge[tot].v = v;
edge[tot].w = w;
edge[tot].next = head[u];
head[u] = tot;
} dijkstra算法
单源最短路径(不存在负权边),从任意固定起点,前往另一个终点的最短路径算法。这里的是最小堆优化
时间复杂度
#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;
inline int read() {
int s = 0, w = 1; char ch = getchar();
while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
const int N = 1e5 + 7; //节点数
const int M = 5e5 + 7; //路径数
const ll INF = 1e18;
int u[M], v[M], w[M];
ll d1[N]; //d1正向图
struct Node {
int v;
ll cost;
bool operator < (Node b)const {
return cost > b.cost;
}
};
vector<Node> e[N];
int n, m, st, ed;
void dijkstra(int s, ll d[]) {
priority_queue<Node> pq;
fill(d, d + N, INF);
d[s] = 0;
pq.push(Node{ s,0 });
while (!pq.empty()) {
Node x = pq.top();
pq.pop();
if (d[x.v] < x.cost) continue; //原先这个节点花费更少 跳过
for (auto it : e[x.v]) {
if (d[it.v] > x.cost + it.cost) {
d[it.v] = x.cost + it.cost;
pq.push(Node{ it.v,d[it.v] });
}
}
}
}
int main() {
n = read(), m = read();
for (int i = 1; i <= m; ++i) {
u[i] = read(), v[i] = read(), w[i] = read();
e[u[i]].push_back(Node{ v[i], w[i] });
e[v[i]].push_back(Node{ u[i], w[i] });
}
st = 1;
dijkstra(st, d1);
for (int i = 1; i <= n; ++i) {
printf("%d->%d : %lld\n", st, i, d1[i]);
}
return 0;
} spfa算法
如果边权为负值, 算法不能得到正确的结果,这个时候只能选择
算法,其他边权为正的图不要使用
,容易被卡。例题
用途一:带负权值的单源最短路径
用途二:负环的判断,是否存在一个环,边权之和为负数
//判断负环
#include <bits/stdc++.h>
using namespace std;
inline int read() {
int s = 0, w = 1; char ch = getchar();
while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
const int N = 2e3 + 5;
const int INF = 0x3f3f3f3f;
struct Node {
int v, w;
};
int n, m, w, st, ed;
vector<Node>e[N];
int d[N];
bool vis[N];
queue<int>q;
int times[N];//记录松弛次数
int spfa(int s) {
memset(vis, 0, sizeof(vis));
memset(d, 0x3f, sizeof(d));
memset(times, 0, sizeof(times));
while (!q.empty()) q.pop();//多组数据,处理前要全部清空
q.push(s);
vis[s] = 1;
d[s] = 0;
times[s] = 1;
while (!q.empty()) {
int now = q.front();
q.pop();
vis[now] = 0;
for (auto it : e[now])
if (d[it.v] > d[now] + it.w) {
d[it.v] = d[now] + it.w;
++times[it.v];//松弛次数加1
if (times[it.v] == n) //如果已经有点松弛次数大于n
return 0; //说明有负环,返回,不需要继续遍历
if (!vis[it.v]) {
q.push(it.v);
vis[it.v] = 1;
}
}
}
return 1;//永远没有点松弛次数大于n,说明无负环
}
int main() {
int T = read();
while (T--) {
n = read(), m = read(), w = read();
for (int i = 1; i <= m; i++) {
int u = read(), v = read(), w = read();
e[u].push_back(Node{ v, w });
e[v].push_back(Node{ u, w });//大于等于0时双向边
}
for (int i = 1; i <= w; ++i) {
int u = read(), v = read(), w = read();
e[u].push_back(Node{ v,-w });
}
st = 1;
bool flag = spfa(st); //调用函数,起点为1
if (flag) { //不存在负环
puts("NO");
//输出各点最短路
for (int i = 1; i <= n; ++i)
printf("%d->%d : %d\n", st, i, d[i]);
}
else puts("YES");
for (int j = 1; j <= n; j++)
e[j].clear();//清空
}
return 0;
} floyd算法
上面给出的都是单源最短路径算法,如果需要知道任意两点之间的最短路径,需要使用floyd算法
// Floyd算法 O(N^3) 任意两点最短路
#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;
inline int read() {
int s = 0, w = 1; char ch = getchar();
while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
const int N = 310; //节点数
int dp[N][N]; //表示从i->j的最短路径
int n, m;
void floyd() {
memset(dp, 0x3f, sizeof(dp));
for (int i = 1; i <= n; ++i) dp[i][i] = 0;
//dp初始化为邻接矩阵
for (int i = 1; i <= m; ++i) {
int u = read(), v = read(), w = read();
dp[u][v] = min(dp[u][v], w);
}
for (int k = 1; k <= n; ++k) //经过编号不超过k的从i->k的最短路径
//dp[k,i,j]=min(dp[k-1][i][j],dp[k-1][i][k] + dp[k-1][k][j]),
//k是阶段,i,j是附加状态,再和背包问题一样利用滚动数组节省第一维
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
}
int main() {
n = read(), m = read();
floyd();
//输出
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j)
printf("%d ", dp[i][j]);
puts("");
}
return 0;
}
最小生成树,kruskal算法和prim算法
算法适用于稠密图,建立一颗最小生成树,而
算法适用于稀疏图建立最小生成树,特别是完全图的最小生成树建立。两种算法都是基于贪心的思想,一个无向图中权值最小的边一定在最小生成树中。
//kruskal 适用于稀疏图 O(n+m*log2(m))
#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;
inline int read() {
int s = 0, w = 1; char ch = getchar();
while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
const int N = 6e3 + 7; //节点
const int M = 6e3 + 7; //路径数
int n, m;
struct Edge {
int u, v, w;
bool operator <(const Edge tmp) const {
return w < tmp.w;
}
}edge[M];
int father[N];
ll ans = 0; //最小生成树的花费和
int find(int root) {
int son = root;
while (root != father[root])
root = father[root];
while (son != root) { //路径压缩
int temp = father[son];
father[son] = root;
son = temp;
}
return root;
}
void kruskal() {
for (int i = 1; i <= m; i++) {
int u = edge[i].u, v = edge[i].v;
int x = find(u), y = find(v);
if (x != y) {
father[x] = y;
ans += edge[i].w;
}
}
}
int main() {
int T = read();
while (T--) {
n = read(), m = read();
for (int i = 1; i <= n; ++i)
father[i] = i;
for (int i = 1; i <= m; ++i)
edge[i].u = read(), edge[i].v = read(), edge[i].w = read();
sort(edge + 1, edge + 1 + m);
kruskal();
}
return 0;
} //无向图最小生成树
#include <bits/stdc++.h>
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;
inline int read() {
int s = 0, w = 1; char ch = getchar();
while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
const int N = 3e3 + 7;
const int INF = 0x3f3f3f3f;
int e[N][N], vis[N], dis[N];
int n, m;
ll ans;
//prim适合稠密图 O(n^2),特别是完全图最小生成树的求解
void prim() {
memset(vis, 0, sizeof(vis));
memset(dis, 0x3f, sizeof(dis));
dis[1] = 0;
for (int i = 1; i < n; ++i) {
int x = 0;
for (int j = 1; j <= n; ++j)
if (!vis[j] && (x == 0 || dis[j] < dis[x]))
x = j;
vis[x] = 1;
for (int j = 1; j <= n; ++j)//用u点更新 刚刚新拓展的边
if (!vis[j]) dis[j] = min(dis[j], e[x][j]);
}
}
int main() {
n = read(), m = read();
//构建邻接矩阵
memset(e, 0x3f, sizeof(e));
for (int i = 0; i <= n; ++i) e[i][i] = 0;
for (int i = 1; i <= m; ++i) {
int x = read(), y = read(), z = read();
e[x][y] = e[y][x] = min(e[x][y], z);
}
//求最小生成树
prim();
for (int i = 2; i <= n; ++i) ans += dis[i];
printf("%lld\n", ans);
return 0;
} 拓扑排序
拓扑排序是用于有向图判断不存在有向环。入队点数小于总节点数,存在环。
而Kruskal跑并查集是判断无向图存在环。并查集连通块父节点不统一,存在环。
实现思路,首先入度为0的节点入队。
再去跑这个队列,遍历全部入度为0的节点,把他们连接的边去掉,就是终点入度-1,如果终点入度为0了,终点入队。
#include <bits/stdc++.h>
#pragma GCC optimize(2)
#pragma GCC optimize(3)
using namespace std;
#define js ios::sync_with_stdio(false);cin.tie(0); cout.tie(0)
typedef long long ll;
inline int read() {
int s = 0, w = 1; char ch = getchar();
while (ch < 48 || ch > 57) { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= 48 && ch <= 57) s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
const int N = 1e3 + 7;
int in[N];
int n, m; // 节点数,边数
vector<int>edge[N];
void topsort() {
queue<int>q; //如果要按字典序,使用最小堆即可
for (int i = 1; i <= n; i++) //n 节点的总数
if (in[i] == 0) q.push(i); //将入度为0的点入队列
vector<int>ans; //ans 为拓扑序列
while (!q.empty()) {
int p = q.front(); q.pop(); // 选一个入度为0的点,出队列
ans.push_back(p);
for (int i = 0; i < edge[p].size(); i++) {
int y = edge[p][i];
in[y]--;
if (in[y] == 0)
q.push(y);
}
}
if (ans.size() == n) {
for (int i = 0; i < ans.size(); i++)
printf("%d ", ans[i]);
printf("\n");
}
else printf("No Answer!\n"); // ans 中的长度与n不相等,就说明无拓扑序列
}
int main() {
n = read(), m = read();
for (int i = 1; i <= m; ++i) {
int u = read(), v = read();
edge[u].push_back(v);
++in[v];
}
topsort();
return 0;
}


京公网安备 11010502036488号