一.有向图的拓扑序列
就是图的宽度优先遍历的应用。
什么是拓扑序列?如果一个点的序列满足对于图中的每条有向边xy(x、y是两个点),x都出现在y的前面(即起点都在终点前面),便称该序列为拓扑序列。
一个有向无环图一定存在拓扑序列。这种图又称拓扑图。
有向图中每个点有两个度。入度:有多少条边指向自己。出度:自己延伸出多少条边指出去。
所以所有入度为0的点都可以作为起点。把它们入队。然后进行宽搜
二.Dijkstra求最短路(一定不能存在负权边)
1.朴素版:
1.首先初始化距离dis[1]=0,其他所有的dis[i]初始化为正无穷(一个比较大的数
2.for循环i从0到n。设定一个集合S,里面存放当前所有已经确定了最短路径的点。然后找到不在S中的最短距离的点赋值给t,把t加入到S中。
3.用t来更新其他点的距离——从t出去的所有边组成的路径能否更新到其他点的距离。即比较dis[x]与dis[t]+一个权重。就是比较从1到x和从1到t再到x哪个更短一点
循环之后就可以确定每个点到起点的最短距离。
给定一个n个点m条边的有向图,图中可能存在重边和自环,所有边权均为正值。请你求出1号点到n号点的最短距离,如果无法从1到n,则输出-1
第一行包括整数n,m。接下来m行每行包括三个整数x,y,z,表示点x和点y之间存在一条有向边,边长为z。
重边计算时仅保留最短边,自环不应加入计算。
#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];//判断是否已经找到最短
int dijkstra(){
memset(dist,0x3f,sizeof dist);//距离初始化为正无穷
dist[1]=0;
for(int i=0;i<n;i++){
int t=-1;//表示一开始还没有确定最短
for(int j=1;j<=n;j++){
if(!st[j]&&(t==-1||dist[t]>dist[j]))//如果没有被判断过或者当前的路径不是最短路
t=j;
}
st[t]=true;
for(int j=1;j<=n;j++){
dist[j]=min[dist[j],dist[t]+g[t][j]];//用当前距离比较从1到t与t到j的距离
}
}
if(dist[n]==0x3f3f3f3f) return -1;//说明1到n不连通
return dist[n];//否则返回最小距离
}
int main(){
scanf("%d%d",&n,&m);
memset(g,0x3f,sizeof g);//初始化为正无穷
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a][b]=min(g[a][b],c);//ab之间可能存在多条边,仅保留长度最短边即可
}
int t=dijkstra();
printf("%d\n",t);
return 0;
}
2.堆优化版
思考如何优化朴素版。
实际上在朴素版中,最耗时的一步是找出不在S中的最短距离点。而这一步可以用堆来优化。一般我们不手写堆,而是使用c++中的优先队列。
#include<iostream>
#include<cstring>
#include <algorithm>
#iinclude<queue>
using namespace std;
typeof pair<int,int> PII;//用堆来维护的同时还需要知道节点编号
const int N = 100010;
int n,m;
int h[N],w[N],e[N],ne[N],idx;//稀疏图用邻接表,还需要存权重
int dist[N];//距离
bool st[N];//判断是否已经找到最短
void add(int a,ing 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});//把一号点放进去,距离为0,编号是1
while(heap.size()){
suto t=heap.top();//每次找到最小的点,就是堆的节点
heap.pop();
int ver=t.second,distance=t.first;//分别表示点的编号和距离
if(st[ver]) continue;//说明这个点之前出现过,跳过
for(int i=h[ver];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>distance+w[i]){//更新距离
dist[j]=distance+w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f) return -1;//说明1到n不连通
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);//用邻接表的话算法保证我们一定会选择最小边,所以无需排除重边
}
int t=dijkstra();
printf("%d\n",t);
return 0;
}
二十六.Bellman-Ford算法
基本思路:for循环n次,每次循环所有边 a,b,w——表示存在一条从a到b的边,权重是w。接下来的操作类似dijkstra算法,dist[b]=min(dist[b],dist[a]+w);(松弛操作)
该算法可以使用结构体数组来存储a,b,w。用来处理有负权边的图。
但是如果图中存在负权回路的话,算法可能在回路上转圈圈,最后得出来的是一个负无穷数。
给定一个n个点m条边的有向图,其中可能存在重边和自环,边权可能为负数。要求求出1号到n号最多经过k条边的最短距离。如果无法从1号走到n号,输出impossible。
注意图中可能存在负权回路
第一行输入三个整数n,m,k
接下来m行,每行包含三个整数x,y,z,表示点x和点y之间存在一条有向边,边长为z。
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=510,M=10010;
int n,m;
int dist[N],bakcup[N];
struct Edge{
int a,b,w;
}edges[M];
int bellman_ford(){
for(int i=0;i<k;i++){
memcpy(backup,dist,sizeof dist);//每次循环之前现将dist数组备份一下①,这样backup存的就是上一次的结果
for(int j=0;j<m;j++){
int a=edges[j].a,b=edges[j].b,w=edges[j].w;
dist[b]=min(dist[b],backup[a]+w);
}
}
if(dist[n]>0x3f3f3f/2) rteurn 0;
return dist[n];
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<m;++){
int a,b,w;
scanf("%d%d%d",&a,&b,&w);
edges[i]={a,b,w};
}
int t=bellman_ford();
if(t==0){
printf("impossible");
}
else printf("%d\n",t);
return 0;
}
①为什么需要back[a]数组进行备份 为了避免如下的串联情况, 在边数限制为一条的情况下,节点3的距离应该是3,但是由于串联情况,利用本轮更新的节点2更新了节点3的距离,所以现在节点3的距离是2。
正确做法是用上轮节点2更新的距离--无穷大,来更新节点3, 再取最小值,所以节点3离起点的距离是3。