关于图论的一点心得

图的定义

简单来说,图 就是一个结点的集合 和边的集合 ,其中任意一条边都可以表示为两个结点之间的关系。
有向边与无向边
如果边对结点的关系是双向的,那么这条边是无向边;如果是单向的,那么这条边是有向边。
图是怎么存的,get到以下几种做法。

1.临接矩阵

(二维的数组,s[i][j]表示i点与j点的距离。)
但显然需要占用很大多余的空间。对于一个稀疏的图(边相对于点数的平方比较少)来说,用邻接矩阵来存的话,成本偏高。
2.邻接表
采用邻接表就可以很大程度的节约空间
常见的两种的写法

s[i][1]=j;
s[i][2]=p; 表示i与j,p之间分别存在一条边。

vector adj[i]用来存以i为始点的边
但用vector无法科学的删除,因此常用 list。
它的特点是可以用来按顺序访问一个结点的出边(或者入边)??

一些跟图有关的定义
路径
path,是指一个边的序列,其中的边首尾相连。

简单路径
simple path,是每条边只经过了一次的路径。

回路
cycle,也称为 环 ,是起点和终点相同的路径。
简单回路
图的定点序列中,除了起点和终点相同外,其余顶点不重复的回路。

连通
两个点连通
无向图中点 和 连通是指存在一条 到 的路径。

图连通
如果无向图 中任意两个节点连通,称其为是连通的。

路径的长度
一般来说,路径的长度在数值上等于路径的边数,或者如果边是带权的,则是路径的边权和。
由此引出我们常见的图论问题。
《最短路径》问题
两个节点之间最短的路径;
注:不一定存在,不一定唯一。
解决最短路径问题介绍两种 算法:
Floyd 算法
是用来求任意两个结点之间的最短路的。

复杂度比较高,但是常数小,容易实现。但采用了三个for循环,当数据达到1e3时已经超时

适用于任何图,不管有向无向,边权正负,但是最短路必须存在。(不能有个负环)
代码实现

  for (k = 1; k <= n; k++) {
      for (i = 1; i <= n; i++) {
        for (j = 1; j <= n; j++) {
          f[i][j] = min(f[i][j], f[i][k] + f[k][j]);
        }
      }
    }

另一种是 Bellman-Ford 算法(更多的为国内的SPFA)优化
一种基于松弛(relax)操作的最短路算法。
实现,假设节点为S
先定义dist(u)为S到 u(当前)的最短路径长度。
relax(u,x) :dis(u)=min(dis(u),dis(u)+edge_len(u,x));
附代码

 relax(u, v)
        {
            dist[v] = min(dist[v], dist[u] + edge_len(u, v));
        }
        for (i = 1; i <= n; i++) {
            dist[i] = edge_len(S, i);
        }
        for (i = 1; i < n; i++) 
       {
            for each edge(u, v)//伪代码,开二维数组遍历edge(u,x)
            {
                relax(u, v);
            }
       }

考虑最短路存在的时候,relax是环环相扣的,所以一个点的relax最多循环n-1次。
应用
给一张有向图,问是否存在负权环。

做法很简单,跑SPFA算法,如果有个点被relax成功了n次,那么就一定存在。
如果n-1次之内算法结束了,就一定不存在。
(队列优化)
SPFA

很多时候我们并不需要那么多无用的 操作,下一个relax必然来自上一个已经relax的节点(即上一个被relax过的点连接的点才能引起下一次relax
那么我们用队列来维护“哪些结点可能会引起relax”,就能只访问必要的边了。(每个被relax过的点都进队)

  q = new queue();
    q.push(S);
    in_queue[S] = true;
    while (!q.empty()) {
        u = q.pop();
        in_queue[u] = false;
        for each edge(u, v) {
            if (relax(u, v) && !in_queue[v]) {
                q.push(v);
                in_queue[v] = true;
            }
        }
    }

精心设计的稠密图可以随便卡掉 SPFA,所以考试时谨慎使用(NOI 2018 卡 SPFA)
SPFA 的优化之 SLF
即 Small Label First。

即在新元素加入队列时,如果队首元素权值大于新元素权值,那么就把新元素加入队首,否则依然加入队尾。
该优化在确实在一些图上有显著效果,其复杂度也有保证,但是如果有负权边的话,可以直接卡到指数级。
Dijkstra 算法/ˈdikstrɑ/迪克斯特拉
这种算法只适用于非负权图,但是时间复杂度非常优秀。

也是用来求单源最短路径的算法。
实现
主要思想是,将结点分成两个集合:已确定最短路长度的,未确定的。

一开始第一个集合里只有 。

然后重复这些操作:

(1) relax那些刚刚被加入第一个集合的结点的所有出边。

(2)从第二个集合中,选取一个最短路长度最小的结点,移到第一个集合中。

直到第二个集合为空,算法结束。

 H = new heap();
    H.insert(S, 0);
    dist[S] = 0;
    for (i = 1; i <= n; i++) {
        u = H.delete_min();
        for each edge(u, v) {
            if (relax(u, v)) {
                H.decrease_key(v, dist[v]);
            }
        }
    }

《输出方案》
开一个 pre 数组,在更新距离的时候记录下来后面的点是如何转移过去的,算法结束前再递归地输出路径即可。

比如 Floyd 就要记录 pre[i][j] = k; ,Bellman-Ford 和 Dijkstra 一般记录 pre[v] = u 。

发布了3 篇原创文章 · 获赞 1 · 访问量 681

猜你喜欢

转载自blog.csdn.net/weixin_43941332/article/details/86524066
今日推荐