一点点图论相关(一)

一.有向图的拓扑序列

就是图的宽度优先遍历的应用。

什么是拓扑序列?如果一个点的序列满足对于图中的每条有向边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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值