图算法(数组版)
1.1最短路径Dijkstra算法:
假设顶点是$V_0到V_5$ 六个点,开始时候是没有连线的,但是已知能互相到达的顶点之间的边权。
步骤是每次从顶点0开始查找,找出距离顶点最短的点 ,然后标记该点为true,再查询该点能直达的其他点加上边权会不会比原先记录的距离值小—->即更新最短距离;遍历完了所有从该点能到的点后再次回到起点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #include <iostream> #include <algorithm> using namespace std;const int MAXV=1000 ;const int INF = 1000000000 ;int n,m,s,G[MAXV][MAXV];int d[MAXV];bool vis[MAXV]={false }; void Dijkstra (int s) { fill (d,d+MAXV,INF); d[s]=0 ; for (int i=0 ;i<n;i++){ int u=-1 ,MIN=INF; for (int j=0 ;j<n-1 ;j++){ if (vis[j]==false && d[j]<MIN){ u = j; MIN = d[j]; } } if (u == -1 ) return ; vis[u]= true ; for (int v=0 ;v<n;v++){ if (vis[v]==false && G[u][v]!=INF && d[u]+G[u][v]<d[v]){ d[v]=d[u]+G[u][v]; } } } } int main () { int u,v,w; cin>>n>>m>>s; fill (G[0 ],G[0 ]+MAXV*MAXV,INF); for (int i=0 ;i<m;i++){ cin>>u>>v>>w; G[u][v]=w; } Dijkstra (s); for (int i=0 ;i<n;i++){ cout<<d[i]; } return 0 ; }
1.2基本模板 1 2 3 4 5 6 7 8 for (循环n次){ u = 使得d[u]最小且还未被访问的顶点的标号; 记u已被访问; for (从u出发能到达的所有顶点v){ if (v未被访问 && 以u为中介点使s到顶点v 的最短距离d[v]更优){ 优化d[v]; }
2.1图的存储 树与图的存储
树是一种特殊的图,与图的存储方式相同。 对于无向图中的边ab,存储两条有向边a->b, b->a。 因此我们可以只考虑有向图的存储。
(1) 邻接矩阵:g[a][b] 存储边a->b
(2) 邻接表:
// 对于每个点k,开一个单链表,存储k所有可以走到的点。h[k]存储这个单链表的头结点
1 2 3 4 5 6 7 8 9 10 11 int h[N], e[N], ne[N], idx;void add (int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } idx = 0 ; memset (h, -1 , sizeof h);
2.2树与图的遍历 时间复杂度O(n+m),n表示点数,m表示边数
(1)深度优先遍历
1 2 3 4 5 6 7 8 9 int dfs (int u) { st[u] = true ; for (int i = h[u];i!= - 1 ;i = ne[i]) { int j = e[i]; if (!st[j]) dfs (j); } }
(2)宽度优先遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 queue<int > q; st[1 ] = true ; q.push (1 ); while (q.size ()){ int t = q.front (); q.pop (); for (int i = h[t];h!=-1 ;i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true ; q.push (j); } } }
3.朴素$dijkstra$算法 时间复杂是 $O(n^2+m)$, n 表示点数,m 表示边数
算法设计:贪心
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 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 - 1 ; i ++ ) { int t = -1 ; for (int j = 1 ; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; for (int j = 1 ; j <= n; j ++ ) dist[j] = min (dist[j], dist[t] + g[t][j]); st[t] = true ; } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; }
4.$Bellman-Ford$算法 单源最短路径算法
对于带权有向图 G = (V, E),Dijkstra 算法要求图 G 中边的权值均为非负 ,而 Bellman-Ford 算法 能适应一般的情况(即存在负权边 的情况)。一个实现的很好的 Dijkstra 算法比 Bellman-Ford 算法的运行时间要低。
设计:动态规划
时间复杂度:$O(V*E)$ 顶点数 边数, $n顶点数,m边数$
理解:对所有边进行$n-1$次松弛操作,因为在一个含有n个顶点的图中,任意两点之间的最短路径最多包含n-1条边 ,
换句话说,第1轮在所有边进行松弛后,得到的是源点最多经过1条边到达其他顶点的最短距离;
第2轮在对所有的边进行松弛后,得到的是源点最多经过2条边到达其他顶点的最短距离;
算法描述:
1、$dist[N]$数组表示源顶点到所有顶点的距离,初始化为$infinte$,$dist[1][1]=0$,
2、计算最短路径,执行$V-1$次遍历
对于图中的每条边:如果起点u的距离d加上权值w小于终点v的距离,更新终点v的距离值d
$if(dist[b]>dist[a]+w) dist[b]=dist[a]+w$
例如以下加上一个拷贝数组就可以求最多经过k条边的最短距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int n, m; int dist[N]; int backup[N]; struct Edge // 边,a 表示出点,b 表示入点,w 表示边的权重{ int a, b, w; }edges[M]; int bellman_ford () { memset (dist, 0x3f , sizeof dist); dist[1 ] = 0 ; for (int i = 0 ; i < n; i ++ ) { memcpy (backup,dist,sizeof dist); for (int j = 0 ; j < m; j ++ ) { int a = edges[j].a, b = edges[j].b, w = edges[j].w; if (dist[b] > backup[a] + w) dist[b] = backup[a] + w; } } if (dist[n] > 0x3f3f3f3f / 2 ) return -1 ; return dist[n]; }
判断是否有负权环 ,再对边进行一次额外的遍历,如果还能更新说明仍然存在一条边使得两点距离更短,事实上再更新多次还是有更新的情况。
5、$SPFA$算法 时间复杂度 平均情况下 $O(m)$,最坏情况下 $O(nm)$, n 表示点数,m 表示边数
$SPFA算法$是对上面的$bellman_ford$算法的队列优化
算法描述:首先建立一个队列,初始队列里只有起始点,建立一个表格记录起始点到所有点的最短路径(初始值赋为极大值) ,然后进行松弛操作,依次用队列中的点去刷新起始点到所有点的最短路 ,如果刷新成功且被刷新点不在队列中 则把其加入到队列中。
求负环:如果某个点进入队列的次数超过N次则存在负环(N为图的顶点数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 int n; int h[N],w[N],e[N],ne[N],idx; int dist[N];bool st[N];int spfa () { memset (dist,0x3f ,sizeof dist); dist[1 ] = 0 ; queue<int > q; q.push (1 ); st[1 ] = true ; while (q.size ()) { auto 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]; if (!st[j]) { q.pusj (j); st[j] = true ; } } } } if (dist[n] == 0x3f3f3f3f ) return -1 ; return dist[n]; }
5、$Floyd$算法 $Floyd$算法属于暴力求解,时间复杂度$O(n^3)$,$n$表示点数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for (int i = 1 ;i <= n;i++) for (int j = 1 ;j <= n;j++) { d[i][j] = (i == j ? 0 : INF); } void floyd () { for (int k = 1 ; k <= n; k ++ ) for (int i = 1 ; i <= n; i ++ ) for (int j = 1 ; j <= n; j ++ ) d[i][j] = min (d[i][j], d[i][k] + d[k][j]); }