图论 最短路 基础

图论基础 , 最短路

图的简单概念

顶点 (Vertex), 边 (Edge)

有向图 , 无向图 , 无向图是一种特殊的有向图

,有向图分为出度 和 入度,无向图的度,代表 连出去的边

顶点都可以具有属性,称为权重,顶点称为 点权,边 称为 边权

稠密图 边很多,大约是 顶点的平方

稀疏图 边很少 ,

重边(平行边),自环,

路径:从一给顶点到达另一个顶点称为一条路径

路径中边的数量称为路径长度,如果路径中的顶点均不重复,称为简单路径

如果路径中的第一个顶点 \(v_i\) 和最后一个顶点 \(v_j\) 是同一个顶点,则称这条路径为回路

连通 : 两个点连通,指 u 和 v 相互可达

图连通 :无向图 任意两个节点 相互可达 ,

强连通 : 有向图 任意 两个节点 相互可达

图的存储结构

  • 邻接矩阵 (二维数组) 对于 n 个顶点,需要 \(O(n^2)\) 空间

    • 严重浪费空间,适用于 稠密图 , 或者是 ,题目的输入 已矩阵的方式给出
    • G[N] [N]
      #include <iostream>
      #include <cstring>
      using namespace std;
      const int N = 1e3 + 233,INF = 0x3f3f3f3f; //INF 最大值
      int g[N][N];
      int main(){
          int n,m,x,y,w;
          memset(g,0x3f,sizeof g); // 初始化图,全都不联通
          cin >> n >> m;
          while(m--){
              cin >> x >> y >> w;
              g[x][y] = w; // 单向边,x to y ,权重 为 w
              g[y][x] = w; // 两条都写,等于双向边, 图中都为双向边,就成无向图了。
          }
          for(int i = 1;i <= n; ++i){
              for(int j = 1;j <= n; ++j){
                  cout << g[i][j] <<" ";
              }
              cout << endl;
          }
          return 0;
      }
  • 邻接表

    对图中的每个顶点都建立一个单链表,存储这个顶点的连出的点

    • vector Adj[N];
    • vector 自身底层函数的缺陷, 每次扩大空间,都会copy一边,然后扩大原来空间的两倍
    • vector G[]:
      优点:
      1.写起来比链式前向星快(大概
      2.每个顶点的出边都是用vector存储的,方便执行一些STL中的函数(比如排序)
      缺点
      1.STL会略慢一些
      2.浪费空间,由于vector申请空间的方式是两倍扩容,遇到卡空间的题目的时候会跪

      #include <iostream>
      #include <cstring>
      #include <vector>
      using namespace std;
      const int N = 1e3 + 233,INF = 0x3f3f3f3f;
      struct edge
      {
          int node ,weight;
          edge(int node_,int weight_)://构造函数,让我们可以直接给结构体赋值
              node(node_),weight(weight_){}
      };
      vector<edge> v[N];
      int n,m;
      int main()
      {
          cin >> n >> m;
        while(m--)
          {
              int x,y,w;
              cin >> x >> y >> w;
              v[x].push_back(edge(y,w));//模拟链表,存边
              //v[y].push_back(edge(x,w));//双向
          }
          for(int i = 1;i <= n; ++i){
              for(int j = 0;j < v[i].size(); ++j){
                  cout << i << " " << v[i][j].node <<" " << v[i][j].weight <<"  ";
              }
              cout << endl;
          }
          return 0;
      }
  • 链式前向星 (数组模拟邻接表)
    • 先学会数组模拟链表,就会数组模拟邻接表了
    • 避免了copy时间的浪费,非常快速
    • 主要使用这种存图结构

    前置知识,数组模拟链表(头插法)

    #include <iostream>
    #include <cstring>
    #include <vector>
    using namespace std;
    const int N = 1e3 + 233,INF = 0x3f3f3f3f;
    int e[N],ne[N],h,idx;
    // head 表示头结点的下标
    // e[i] 表示节点i的值
    // ne[i] 表示节点i的next指针是多少
    // idx 存储当前已经用到了哪个点
    void init(){ // 初始化为头节点 为 -1
        h = -1,idx = 0;
    }
    void add(int x){
        e[idx] = x;// 保存x的值 到 idx 这个指针的位置
        ne[idx] = h;// idx 的下一个指针 为 头节点
        h = idx++; // 头节点 = idx 指针  , idx ++
    }
    int main(){
        int n ,x;
        cin >> n;
        init();
        while(n--){
            cin >> x;
            add(x);
        }
        for(int i = h;i != -1; i = ne[i]){
            cout << e[i] << " ";
        }
        return 0;
    }
    #include <iostream>
    #include <cstring>
    #include <vector>
    using namespace std;
    const int N = 1e3 + 233,INF = 0x3f3f3f3f;
    int e[N],ne[N],h[N],w[N],idx;//数组模拟邻接表, h代表 n个头节点
    // e存每条边,ne指向下一条边,h是链表头,w是每个顶点的权重,idx是索引值
    void add(int a,int b,int c){// 采用头插法
        e[idx] = b;
        ne[idx] = h[a];
        w[b] = c;
        h[a] = idx++;
    }
    int n,m;
    int main()
    {
        memset(h,-1,sizeof h);//初始化头节点,全为-1
        int a,b,c;
        cin>>n>>m;
        while(m--)
        {
            cin >> a >> b >> c;
            add(a,b,c); // 单向边
            //add(b,a,c) 双向
        }
        for(int i = 0;i < n; ++i){
            for(int j = h[i];j != -1;j = ne[j]){
                int v = e[j];
                cout << i <<" " <<v<<" "<<w[v]<<" ";
            }
            cout << endl;
        }
        return 0;
    }

图的遍历

DFS (求连通块),求欧拉回路 与 哈密顿回路

例题 : https://vjudge.net/problem/UVA-572

  • 用 dfs 朝 八个方向 搜 ,每找到一个 ,做一次标记,最后输出连通块的数目
#include <iostream>
#include <cstring>
using namespace std;
const int N = 233;
char g[N][N];
int n,m,ans;
int dx[8] = {0, 0,1,1, 1,-1,-1,-1}; // 方向数组
int dy[8] = {1,-1,0,1,-1, 0, 1,-1};
bool pd(int x,int y){ // 判断是否越界
    if(x >= 1 && x <= m && y >= 1 && y <= n) return 1;
    else return 0;
}
void dfs(int x,int y){ // dfs求连通块
    for(int i = 0;i < 8; ++i){
        int nx = x + dx[i];
        int ny = y + dy[i];
        if(pd(nx,ny) && g[nx][ny] == '@'){
            g[nx][ny] = '*';
            dfs(nx,ny);
        }
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    while(cin >> m >> n){
        if(m == 0 && n == 0) break;
        memset(g,0,sizeof g);
        ans = 0;
        for(int i = 1;i <= m; ++i){
            for(int j = 1;j <= n; ++j)
                cin >> g[i][j];
        }
        for(int i = 1;i <= m; ++i){
            for(int j = 1;j <= n; ++j){
                if(g[i][j] == '@'){
                    ans++; // 连通块个数++
                    g[i][j] = '*';
                    dfs(i,j);
                }
            }
        }
        cout << ans << endl;
    }
    return 0;
}

BFS (边权为 1 的最短路算法 ,队列实现 )

例题 : https://vjudge.net/problem/OpenJ_Bailian-3752

  • 用队列 扩展 ,如果找到 出口,直接跳出即可
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 50;
char g[N][N];
int r,c,ans;
int dx[4] = {0,0,1,-1};
int dy[4] = {1,-1,0,0};
bool vis[N][N]; // 判断是否访问过 点
struct Ponit{
    int x,y,t; // x,y 坐标, t 代表当前的步数
    Ponit(int x,int y,int t):x(x),y(y),t(t){} // 构造函数
};
bool pd(int x,int y){
    if(x >= 1 && x <= r && y >= 1 && y <= c) return 1;
    else return 0;
}
void bfs(){
    queue<Ponit> q;
    q.push({1,1,1}); // 初始化 队列
    vis[1][1] = 1;
    while(q.size()){
        Ponit t = q.front();
        q.pop();
        for(int i = 0;i < 4; ++i){
            int nx = t.x + dx[i];
            int ny = t.y + dy[i];
            if(nx == r && ny == c) {
                ans = t.t + 1;
                return ;
            }
            if(pd(nx,ny) && g[nx][ny] == '.' && !vis[nx][ny]){
                vis[nx][ny] = 1;
                q.push({nx,ny,t.t + 1});
            }
        }
    }
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin >> r >> c;
    for(int i = 1;i <= r; ++i){
        for(int j = 1;j <= c; ++j)
            cin >> g[i][j];
    }
    bfs();
    cout << ans;

    return 0;
}

最短路径算法

由BFS启发 得到的最短路算法

因为,BFS采用的是队列的思想,因此,可以想出一种基于队列的算法来处理,边权为任意值的算法

单源最短路

Dijkstra O(n^2) 只能处理正权边

算法主要特点:以起点为核心,逐层向外扩展,每次都会取一个最近点继续扩展,直到取完所有点为止。

算法步骤

初始化距离 dist[1] = 0, dist[i] = +INF,从起点开始。

循环迭代 n 次,

集合 s 代表,已经确定最短路的点

for i 0 ~ n { // 每次循环,可以确定一个 点的距离,n次 ,确定n个点

? 在dist 中 找到不在 s 中的距离源点最近的点 , 设为 t // 基于贪心实现

? 把 t 加到 s里面去

? 用 t 来更新 ,其他所有点的距离 ( 更新 t 的出边 ) dist [x] > dist[t] + w;

}

例题 :

HDU 2544

acwing 849. Dijkstra求最短路 I

习题推荐:

HDU 1874,2066,2112,2680,

POJ 1797

#include <bits/stdc++.h>
using namespace std;
const int N = 510;
int g[N][N],n,m,dist[N];
bool st[N];
// dist[i] 代表 1 到 i的最短路
int dijkstra(){
    memset(dist,0x3f,sizeof dist);
    dist[1] = 0;
    // 循环 n 次,每次确定一个最短路的值
    for(int i = 0;i < n; ++i){
        int t = -1;
        // 在所有 st 为 false的点中,找到 dist最小的点
        for(int j = 1;j <= n; ++j){
            if(!st[j] && (t == -1 || dist[t] > dist[j])){
                t = j;
            }
        }
        st[t] = 1;
        for(int j = 1;j <= n; ++j){
            dist[j] = min(dist[j],dist[t] + g[t][j]);
            // dist[t] + g[t][j] 表示, 1 - t的最短路 加上 t 到 j的距离,
            // 等价于 1 - j 的距离
        }
    }
    if(dist[n] == 0x3f3f3f3f) return -1;
    else return dist[n];

}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin >> n >> m;
    memset(g,0x3f,sizeof g);
    while(m--){
        int a,b,c;
        cin >> a >> b >> c;
        g[a][b] = min(g[a][b],c); // a 和 b之间可能有重边,取最小的边为权值
    }

    cout << dijkstra();

    return 0;
}

堆优化dij

#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 1e5 + 233;
typedef pair<int,int> PII;
int dist[N],e[N],w[N],ne[N],h[N],idx,n,m,x,y,z;
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 dij(){
    memset(dist,0x3f,sizeof dist);
    priority_queue<PII,vector<PII>,greater<PII>> heap; // 初始化堆
    dist[1] = 0;
    heap.push({0,1});
    while(heap.size()){
        auto t = heap.top();// log n 找到最小的距离
        heap.pop();
        int distance = t.first,ver = t.second;
        if(st[ver]) continue;
        st[ver] = true;
        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;
    else return dist[n];
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin >> n >> m;
    memset(h,-1,sizeof h);
    while(m--){
        cin >> x >> y >> z;
        add(x,y,z);
    }
    cout << dij();
    return 0;
}

Bell-Ford O(ne) 可以处理负权边,不能处理负权回路,负权回路没有最短路

// 只能存边

算法步骤:

进行n-1次松弛操作

每次循环 m 次 ,更新每条边的距离

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 23;
int dist[510],st[510];
int n , m;
struct edge{
    int from,to,w;
}e[N];
void bf(){
    memset(dist,0x3f,sizeof dist);
    dist[1] = 0;
    for(int i = 0;i < n-1; ++i){
        for(int j = 0;j < m; ++j){
            int a = e[i].from,b = e[i].to, t = e[i].w;
            dist[b] = min(dist[b],dist[a] + t);
        }
    }
}
int main(){
    cin >> n >> m;
    for(int i = 0;i < m; ++i){
        cin >> e[i].from >> e[i].to >> e[i].w;
    }
    bf();
    for(int i = 1;i <= n; ++i){
        cout << dist[i] <<" ";
    }
    return 0;;
}

SPFA (队列优化版本的 Bell-Ford) 可以判断负环,容易被网格图卡,

dist[b] = min(dist[b],dist[a] + t);  // 优化 dist a
while(队列不空 )  , {
    取出队头  t
    删除队头
    删掉队头的标记
    更新遍历  t 的所有出边 {
        更新每个点的距离,如果更新成功,并且当前的点没有入队,
            那么就加入队列
            一个点只能入队一次,不能多次入队。
    }

}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 23;
int dist[510],st[510];
int e[N],ne[N],w[N],h[N],idx,n,m;
int a, b,c;
void add(int a,int b,int c){
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}
void spfa(){
    memset(dist,0x3f,sizeof dist);
    dist[1] = 0;
    st[1] = 1;
    queue<int> q; // 存所有变小了的节点
    q.push(1);
    while(q.size()){
        int t = q.front();
        q.pop();
        st[t] = 0;
        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] == 0){
                    st[j] = 1;
                    q.push(j);
                }
            }
        }
    }

}
int main(){
    cin >> n >> m;
    memset(h,-1,sizeof h);
    for(int i = 0;i < m; ++i){
        cin >> a >> b >> c;
        add(a,b,c);
    }
    spfa();
    for(int i = 1;i <= n; ++i){
        cout << dist[i] <<" ";
    }
    return 0;;
}

spfa https://blog.csdn.net/u011644423/article/details/38345631

? https://skywt.cn/posts/spfasummary/

POJ 1511,3259

多源汇最短路 DP思想,暴力 ,只能用 邻接矩阵存图

Floyed O(n^3), 还可以用来判断,图中的两个点是否相连

void floyd(){
    for(int k = 1;k <= n; ++i){
        for(int i = 1;i <= n; ++i){
            for(int j = 1;j <= n; ++j){
                g[i][j] = min(g[i][j],g[i][k] + g[k][j]);
            }
        }
    }
}

习题推荐:

HDU 1869,3665,1596,1734

POJ 1125

最短路的应用 : 差分约束系统(不等式求解)

习题:

kuangbin最短路专题 ,https://vjudge.net/contest/324762

推荐阅读:

  • 洛谷日报 :

https://www.luogu.org/blog/wym483739/xue-tu-lun-ni-zhen-di-liao-xie-zui-duan-lu-ma-post

https://studyingfather.blog.luogu.org/some-coding-tips-for-oiers some tips for oiers

https://www.luogu.org/blog/chengni5673/tu-lun-di-xiao-ji-qiao-yi-ji-kuo-zhan 图论小技巧

https://www.luogu.org/blog/little-sun/dijkstra dij详解

https://www.luogu.org/blog/encore/io-you-hua-nei-suo-shi oi 优化

https://oi-wiki.org/graph oi-wiki-图论专题

原文地址:https://www.cnblogs.com/317zhang/p/11485681.html

时间: 2024-10-28 22:23:54

图论 最短路 基础的相关文章

bzoj4144 [AMPPZ2014]Petrol 图论 最短路 并查集

bzoj4144 [AMPPZ2014]Petrol 图论 最短路 并查集 1.这道题我们主要就是要求出距离一个油站的最近的油站 首先我们dijkstra 求出任意一个点到 离他最近的油站的距离 2.然后会发现 如果一条边的两个端点 的最近油站不同的话 那么这条边就会在这两个油站的最短路上 3.然后对于每一条边 我们将他的权值 变为 dis[ u ] + dis[ v ] + e[ i ][ j ] 如果u与v最近油站相同 那么这个无意义 如果不同 那么久表示 u 最近油站 到 v 最近油站的最

POJ 3259 Wormholes (图论---最短路 Bellman-Ford || SPFA)

链接:http://poj.org/problem?id=3259 Description While exploring his many farms, Farmer John has discovered a number of amazing wormholes. A wormhole is very peculiar because it is a one-way path that delivers you to its destination at a time that is BE

HDU 5521 [图论][最短路][建图灵感]

/* 思前想后 还是决定坚持写博客吧... 题意: n个点,m个集合.每个集合里边的点是联通的且任意两点之间有一条dis[i]的边(每个集合一个dis[i]) 求同时从第1个点和第n个点出发的两个人相遇的最短时间,并输出相遇的地点,如果有多个按编号大小顺序输出. 输入: 测试数据 t n m 以下m行每行 dis[i] 该集合点的数量 ...每个点的标号 数据范围: n 2-1e5 所有集合的元素的数量和 1e6 思路: 如果直接跑最短路,边会存爆的. 考虑换种思路建边. 每个集合看作两个点,一

hdu 2680 最短路基础题 spfa实现 算法总结

题目链接http://acm.hdu.edu.cn/showproblem.php?pid=2680 题目大意,就是一个人可以从多个起点开始出发,看到终点的最短路是多少..只有可以运用和hdu2066一样的思想,对于每个起点可以看成是跟最最开始的点之间有一条权值为0的边.可以把最开始的点记做0点.那这样就可以使用单源最短路了.之前都没有用过spfa,今天来运用了一下. 算法大致流程是用一个队列来进行维护.初始时将源加入队列.每次从队列中取出一个元素,并对所有与他相邻的点进行松弛,若某个相邻的点松

图论——最短路①

RT 找工就业 Bessie is running out of money and is searching for jobs. Farmer John knows this and wants the cows to travel around so he has imposed a rule that his cows can only make D (1 <= D <= 1,000) dollars in a city before they must work in another

洛谷P1027 Car的旅行路线 计算几何 图论最短路

题意 求某城到某城的最小花费 一个城中有四个机场,一个城中的机场相互可达,用公路到达,但是不同城的公路的单位路程的费不同,两个不同城的机场(我不知道相同城可不可以)可以通过机场到达,且飞机单位路程价格一定,问从 a 城到b城的最小花费,可从a的任一机场出发,从 b 的任一机场结束 . 题解 这道题思路还算容易,就是求最短路,只是建图比较麻烦, 总体思路 1.建图(1) 相同城 的四个机场两两连线 求距离, [1]但是他只给出了三个点,也就是说这第四个点要我们自己求 首先他给出三个点,这三个点一定

图论——最短路

Tyvj 1221 微子危机——战略 背景 №.3Summer联盟战前兵力战略转移. 描述 Summer的兵力分布在各个星球上,现在需要把他们全部转移到某个星球上.Summer一共拥有N个星球(1-N),你要把这N个星球上的兵力转到第M个星球上.本来每个星球之间都有星际轨道连接,但Guiolk监视了某些轨道,我们一但走上这些轨道,有可能受到他的攻击.为了安全,Summer不会走被监视的轨道.于是,只有L个星际轨道是被批准通过的.Summer的国防部想统计一下所需的最短路程(即每个星球到第M星球的

图论——最短路②

RT 最短路计数 给出一个N个顶点M条边的无向无权图,顶点编号为1-N.问从顶点1开始,到其他每个点的最短路有几条. 输入输出格式 输入格式: 输入第一行包含2个正整数N,M,为图的顶点数与边数. 接下来M行,每行两个正整数x, y,表示有一条顶点x连向顶点y的边,请注意可能有自环与重边. 输出格式: 输出包括N行,每行一个非负整数,第i行输出从顶点1到顶点i有多少条不同的最短路,由于答案有可能会很大,你只需要输出mod 100003后的结果即可.如果无法到达顶点i则输出0. 样例 输入: 输出

[图论][最短路]步行

题目描述 ftiasch又开发了一个奇怪的游戏,这个游戏是这样的:有N个格子排成一列,每个格子上有一个数字,第i个格子的数字记为Ai.这个游戏有2种操作:1.如果现在在第i个格子,则可以跳到第Ai个格子.2.把某个Ai增加或减少1.nm开始在第1个格子,他需要走到第N个格子才能通关.现在他已经头昏脑涨啦,需要你帮助他求出,从起点到终点最少需要多少次操作. 输入 第1行,1个整数N.第2行,N个整数Ai. 输出 第1行,1个整数,表示最少的操作次数. 样例输入 5 3 4 2 5 3 样例输出 3