1.朴素版dijkstra(n^2)
这个是遍历每一个点,找出距离原点最小的点,更新该点距其他点的最短距离。每个点只遍历一次
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510;
int n, m;
int g[N][N]; //稠密图一般使用邻接矩阵
int dist[N]; //记录每个节点距离起点的距离
bool st[N]; //True表示已经确定最短路 属于s集合
int dijkstra() {
//所有节点距离起点的距离初始化为无穷大
memset(dist, 0x3f, sizeof dist);
//起点距离自己的距离为零
dist[1] = 0;
//迭代n次,每次可以确定一个点到起点的最短路
for (int i = 0; i < n; ++i) {
int t = -1;
//t的作用?
for (int j = 1; j <= n; ++j) {
//不在st集合,并且
//如果没有更新过,则进行更新, 或者发现更短的路径,则进行更新(找到距起点最近的点)
if (!st[j] && (t == -1 || dist[j] < dist[t])) {
t = j;
}
}
//加入到st集合中
st[t] = true;
//找到了距离最小的点t,并用最小的点t去更新其他的点到起点的距离
for (int j = 1; j <= n; ++j) {
dist[j] = min(dist[j], dist[t] + g[t][j]);
}
}
// 如果起点到达不了n号节点,则返回-1
if (dist[n] == 0x3f3f3f3f) return -1;
// 返回起点距离n号节点的最短距离
return dist[n];
}
int main() {
cin >> n >> m;
//所有节点之间的距离初始化为无穷大
memset(g, 0x3f, sizeof g);
// 0x3f 0x3f3f3f3f 的区别?
while (m--) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
g[a][b] = min(g[a][b], c); //如果有重边,请保留权值最小的一条边
}
cout << dijkstra() << endl;
return 0;
}
一个小优化,在最后一个循环中
#include<iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510, M = 100010;
int h[N], e[M], ne[M], w[M], idx;//邻接表存储图
int state[N];//state 记录是否找到了源点到该节点的最短距离
int dist[N];//dist 数组保存源点到其余各个节点的距离
int n, m;//图的节点个数和边数
void add(int a, int b, int c)//插入边
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void Dijkstra()
{
memset(dist, 0x3f, sizeof(dist));//dist 数组的各个元素为无穷大
dist[1] = 0;//源点到源点的距离为置为 0
for (int i = 0; i < n; i++)
{
int t = -1;
for (int j = 1; j <= n; j++)//遍历 dist 数组,找到没有确定最短路径的节点中距离源点最近的点t
{
if (!state[j] && (t == -1 || dist[j] < dist[t]))
t = j;
}
state[t] = 1;//state[i] 置为 1。
for (int j = h[t]; j != -1; j = ne[j])//遍历 t 所有可以到达的节点 i
{
int i = e[j];
dist[i] = min(dist[i], dist[t] + w[j]);//更新 dist[j]
}
}
}
int main()
{
memset(h, -1, sizeof(h));//邻接表初始化
cin >> n >> m;
while (m--)//读入 m 条边
{
int a, b, w;
cin >> a >> b >> w;
add(a, b, w);
}
Dijkstra();
if (dist[n] != 0x3f3f3f3f)//如果dist[n]被更新了,则存在路径
cout << dist[n];
else
cout << "-1";
}
2.堆优化版dijkstra(mlogn)
没了判断最小距离的循环(用了优先队列)
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>//堆的头文件
using namespace std;
typedef pair<int, int> PII;//堆里存储距离和节点编号
const int N = 1e6 + 10;
int n, m;//节点数量和边数
int h[N], w[N], e[N], ne[N], idx;//邻接矩阵存储图
int dist[N];//存储距离
bool st[N];//存储状态
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int dijkstra()
{
memset(dist, 0x3f, sizeof dist);//距离初始化为无穷大
dist[1] = 0;
priority_queue<PII, vector<PII>, greater<PII>> heap;//小根堆
heap.push({0, 1});//插入距离和节点编号
while (heap.size())
{
auto t = heap.top();//取距离源点最近的点
heap.pop();
int ver = t.second, distance = t.first;//ver:节点编号,distance:源点距离ver 的距离
if (st[ver]) continue;//如果距离已经确定,则跳过该点
st[ver] = true;
for (int i = h[ver]; i != -1; i = ne[i])//更新ver所指向的节点距离
{
int j = e[i];
if (dist[j] > dist[ver] + w[i])
{
dist[j] = dist[ver] + w[i];
heap.push({dist[j], j});//距离变小,则入堆
}
}
}
if (dist[n] == 0x3f3f3f3f) return -1;
return dist[n];
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
cout << dijkstra() << endl;
return 0;
}
3.SPFA(n)
能够判断有负环,有非负权的环的图
如果某个已经遍历过的点突然距原点的距离又变小了,那么该点继续入队再次遍历
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010;
int h[N], e[N], w[N], ne[N], idx;//邻接表,存储图
int st[N];//标记顶点是不是在队列中
int dist[N];//保存最短路径的值
int q[N], hh, tt = -1;//队列
void add(int a, int b, int c){//图中添加边和边的端点
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void spfa(){
q[++tt] = 1;//从1号顶点开始松弛,1号顶点入队
dist[1] = 0;//1号到1号的距离为 0
st[1] = 1;//1号顶点在队列中
while(tt >= hh){//不断进行松弛
int a = q[hh++];//取对头记作a,进行松弛
st[a] = 0;//取完队头后,a不在队列中了
for(int i = h[a]; i != -1; i = ne[i])//遍历所有和a相连的点
{
int b = e[i], c = w[i];//获得和a相连的点和边
if(dist[b] > dist[a] + c){//如果可以距离变得更短,则更新距离,可以解决环的问题(要求是非负环)
dist[b] = dist[a] + c;//更新距离
if(!st[b]){//如果没在队列中
q[++tt] = b;//入队
st[b] = 1;//打标记
}
}
}
}
}
int main(){
memset(h, -1, sizeof h);//初始化邻接表
memset(dist, 0x3f, sizeof dist);//初始化距离
int n, m;//保存点的数量和边的数量
cin >> n >> m;
for(int i = 0; i < m; i++){//读入每条边和边的端点
int a, b, w;
cin >> a >> b >> w;
add(a, b, w);//加入到邻接表
}
spfa();
if(dist[n] == 0x3f3f3f3f )//如果到n点的距离是无穷,则不能到达
cout << "impossible";
else cout << dist[n];//否则能到达,输出距离
return 0;
}
SPFA判断负环
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
const int N = 2010, M = 10010;
int n, m;
int h[N], w[M], e[M], ne[M], idx;
int dist[N], cnt[N];//cnt记录一条路的点数
bool st[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
bool spfa()
{
queue<int> q;
//与距离无关,因此不需要初始化距离
for (int i = 1; i <= n; i ++ )
{
st[i] = true;
q.push(i);//由于不是到负环有没有在1号点,因此我们需要加入所有点
}
while (q.size())
{
int t = q.front();
q.pop();
st[t] = false;
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (dist[j] > dist[t] + w[i])
{
dist[j] = dist[t] + w[i];
cnt[j] = cnt[t] + 1;
if (cnt[j] >= n) return true;//遍历完所有点,如果不满足时说明没负环
if (!st[j])
{
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while (m -- )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
if (spfa()) puts("Yes");
else puts("No");
return 0;
}
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/48499/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
4.拓扑排序
貌似只有一个点是入度为0
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int e[N], ne[N], idx;//邻接表存储图
int h[N];
int q[N], hh = 0, tt = -1;//队列保存入度为0的点,也就是能够输出的点,
int n, m;//保存图的点数和边数
int d[N];保存各个点的入度
void add(int a, int b){
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void topsort(){
for(int i = 1; i <= n; i++){//遍历一遍顶点的入度。
if(d[i] == 0)//如果入度为 0, 则可以入队列
q[++tt] = i;
}
while(tt >= hh){//循环处理队列中点的
int a = q[hh++];
for(int i = h[a]; i != -1; i = ne[i]){//循环删除 a 发出的边
int b = e[i];//a 有一条边指向b
d[b]--;//删除边后,b的入度减1
if(d[b] == 0)//如果b的入度减为 0,则 b 可以输出,入队列
q[++tt] = b;
}
}
if(tt == n - 1){//如果队列中的点的个数与图中点的个数相同,则可以进行拓扑排序
for(int i = 0; i < n; i++){//队列中保存了所有入度为0的点,依次输出
cout << q[i] << " ";
}
}
else//如果队列中的点的个数与图中点的个数不相同,则可以进行拓扑排序
cout << -1;//输出-1,代表错误
}
int main(){
cin >> n >> m;//保存点的个数和边的个数
memset(h, -1, sizeof h);//初始化邻接矩阵
while (m -- ){//依次读入边
int a, b;
cin >> a >> b;
d[b]++;//顶点b的入度+1
add(a, b);//添加到邻接矩阵
}
topsort();//进行拓扑排序
return 0;
}
5.prim求最小生成树(n^2)
prim和kraskal中都是先遍历最小的边,因此距离根越近,边权越小。
注意在存储边的时候是双向边,所以要g[a,b]=g[b,a](相当于存的是一个对称矩阵),区分Kraskal
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510;
int g[N][N];//存储图
int dt[N];//存储各个节点到生成树的距离
int st[N];//节点是否被加入到生成树中
int pre[N];//节点的前去节点
int n, m;//n 个节点,m 条边
void prim()
{
memset(dt,0x3f, sizeof(dt));//初始化距离数组为一个很大的数(10亿左右)
int res= 0;
dt[1] = 0;//从 1 号节点开始生成
for(int i = 0; i < n; i++)//每次循环选出一个点加入到生成树
{
int t = -1;
for(int j = 1; j <= n; j++)//每个节点一次判断
{
if(!st[j] && (t == -1 || dt[j] < dt[t]))//如果没有在树中,且到树的距离最短,则选择该点
t = j;
}
//2022.6.1 发现测试用例加强后,需要判断孤立点了
//如果孤立点,直返输出不能,然后退出
if(dt[t] == 0x3f3f3f3f) {
cout << "impossible";
return;
}
st[t] = 1;// 选择该点
res += dt[t];
for(int i = 1; i <= n; i++)//更新生成树外的点到生成树的距离
{
if(dt[i] > g[t][i] && !st[i])//从 t 到节点 i 的距离小于原来距离,则更新。
{
dt[i] = g[t][i];//更新距离
pre[i] = t;//从 t 到 i 的距离更短,i 的前驱变为 t.
}
}
}
cout << res;
}
void getPath()//输出各个边
{
for(int i = n; i > 1; i--)//n 个节点,所以有 n-1 条边。
{
cout << i <<" " << pre[i] << " "<< endl;// i 是节点编号,pre[i] 是 i 节点的前驱节点。他们构成一条边。
}
}
int main()
{
memset(g, 0x3f, sizeof(g));//各个点之间的距离初始化成很大的数
cin >> n >> m;//输入节点数和边数
while(m --)
{
int a, b, w;
cin >> a >> b >> w;//输出边的两个顶点和权重
g[a][b] = g[b][a] = min(g[a][b],w);//存储权重
}
prim();//求最下生成树
//getPath();//输出路径
return 0;
}
作者:Hasity
链接:https://www.acwing.com/solution/content/38312/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
6.Kraskal求最小生成树(mlogm)
存边的时候就是存的双向边,不是单向边
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int p[N];//保存并查集
struct E{
int a;
int b;
int w;
bool operator < (const E& rhs){//通过边长进行排序
return this->w < rhs.w;
}
}edg[N * 2];
int res = 0;
int n, m;
int cnt = 0;
int find(int a){//并查集找祖宗
if(p[a] != a) p[a] = find(p[a]);
return p[a];
}
void klskr(){
for(int i = 1; i <= m; i++)//依次尝试加入每条边
{
int pa = find(edg[i].a);// a 点所在的集合
int pb = find(edg[i].b);// b 点所在的集合
if(pa != pb){//如果 a b 不在一个集合中,同时也避免了自环,即自己连自己
res += edg[i].w;//a b 之间这条边要
p[pa] = pb;// 合并a b
cnt ++; // 保留的边数量+1
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) p[i] = i;//初始化并查集
for(int i = 1; i <= m; i++){//读入每条边
int a, b , c;
cin >> a >> b >>c;
edg[i] = {a, b, c};
}
sort(edg + 1, edg + m + 1);//按边长排序
klskr();
if(cnt < n - 1) {//如果保留的边小于点数-1,则不能连通
cout<< "impossible";
return 0;
}
cout << res;
return 0;
}
作者:Hasity
链接:https://www.acwing.com/solution/content/104383/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。