图的遍历
邻接矩阵
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include <bits/stdc++.h> using namespace std;int n,m;const int maxn = 100 ; int mp[maxn][maxn];int vis[maxn];void init () { for (int i = 0 ;i<maxn;i++) { vis[i] = 0 ; } }void dfs (int x) { cout << x << " " ; vis[x] = 1 ; for (int i = 0 ;i<n;i++) { if (mp[x][i]!=0 && vis[i]==0 ) { dfs (i); } } }void bfs (int root) { init (); queue<int >q; q.push (root); vis[root] = 1 ; int d[maxn]; d[root] = 0 ; while (!q.empty ()) { int front = q.front (); q.pop (); cout << front <<" " << d[front] <<"\n" ; for (int i = 0 ;i<maxn;i++) { if (mp[front][i] != 0 && vis[i] == 0 ) { q.push (i); d[i] = d[front] + 1 ; vis[i] = 1 ; } } } }int main () { cin >> n >> m; for (int i = 0 ;i<m;i++) { int a,b,x; cin >> a >> b >> x; mp[a][b] = x; mp[b][a] = x; } init (); dfs (0 ); bfs (0 ); return 0 ; }
邻接表
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 #include <bits/stdc++.h> using namespace std;int n,m;const int inf = 0x3fffffff ;const int maxn = 100 ; int vis[maxn];struct Node { int id; int val; Node* next; Node (int _id,int _val) : id (_id),val (_val),next (NULL ){} }; Node *head[maxn];void add (int a,int b,int x) { Node * p = new Node (b,x); p->next = head[a]; head[a] = p; Node *q = new Node (a,x); q->next = head[b]; head[b] = q; }void DFS (int v) { cout << v; vis[v] = 1 ; Node *p = head[v]; while (p!=NULL ){ int nw = p->id; if (vis[nw]==0 ){ DFS (nw); } p = p->next; } }int main () { cin >> n >> m; for (int i = 0 ;i<m;i++) { int a,b,x; cin >> a >> b >> x; add (a,b,x); } DFS (0 ); return 0 ; }
计算强连通分量的个数
Kosaraju算法步骤 :
第一步 :对原图 G 进行深度优先搜索(DFS),并记录节点的完成时间(即每个节点访问结束的时间)。
第二步 :构造图 G 的转置 ,即将所有边的方向反转。
第三步 :按照第一步中记录的完成时间从大到小的顺序,对 进行DFS,每次DFS生成的一棵树就是一个强连通分量。
最小生成树
prim
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 int prim (int root) { fill (d,d+maxn,inf); d[root] = 0 ; int ans = 0 ; for (int i = 0 ;i<n;i++) { int u = -1 ; int mx = inf; for (int j = 0 ;j<maxn;j++) { if (mx > d[j] && vis[j]==0 ) { mx = d[j]; u = j; } } if (u == -1 ) return -1 ; vis[u] = 1 ; ans += d[u]; cout << u <<" " ; for (int v = 0 ;v<n;v++) { if (vis[v] == 0 && mp[u][v] != inf && mp[u][v] < d[v]) { d[v] = mp[u][v]; } } } return ans; }
kruskal
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 55 56 57 58 59 60 struct edge { int u,v; int cost; }Edge[maxn];bool cmp (edge a,edge b) { return a.cost < b.cost; }int fa[maxn];int Find (int x) { if (x==fa[x]) return x; int t = Find (fa[x]); fa[x] = t; return t; }int Union (int a,int b) { int Fa = Find (a); int Fb = Find (b); fa[Fa] = Fb; }int kruskal () { sort (Edge,Edge+m,cmp); for (int i = 0 ;i<n;i++){ fa[i] = i; } int ans = 0 ,num = 0 ; for (int i = 0 ;i<m;i++) { edge temp = Edge[i]; int u = temp.u; int v = temp.v; int cost = temp.cost; if (Find (u)!=Find (v)){ Union (u,v); num ++; ans += cost; } } if (num != n-1 ) return -1 ; return ans; }
最大生成树
顾名思义,生成的权值和要求最大的生成树,只需要在kruskal里面的cmp排序,按照权值大小进行降序即可.
加入一条新边,求最新的最小生成树
假设图G,已知最小生成树T,现在给定一条新加入G的边e,要求求出G+e新图的最小生成树算法.
思路:加入一条新的边,肯定会形成一个环,只需要在新的环中找到最长的一条边就可以,如果该环中存在比新边权值大的边,那新最小生成树的权值一定变小
次小生成树
思路:先求最小生成树,然后枚举图中未加入最小生成树的各边,转换成求解加入一条新边,求最新的最小生成树问题,然后在根据,是严格次小还是次小进行统计.
判断最小生成树是否唯一
原理:假如最小生成树不唯一,只有可能是存在权值相同且为最小的边连通两个集合.换句话说,只需要在kruskal里面判断,假如有两条边权值相同,且都可以将两个相同的连通块进行合并,那生成树一定不是唯一的.
什么样的边会影响到最小生成树的唯一性呢?
kruskal 求最小生成树 是将所有边权从小到大排序,然后判断当前边的两个端点所在连通块是否连通。如果没有连通,那么这条边就需要拿。 而此时如果有另外一条边,虽然也可以将这两个连通块合并,但是其权值比较大,那么这条边是不会影响到最小生成树的。 所以,只有两个边的权值相同,并且都能将端点的两个连通块合并 ,这么这两个边选择哪个都行,那么最小生成树就不唯一了。
最短路径
最短路径的求解依据的一个重要性质为:两个节点之间的一条最短路径包含着其他的最短路径,这是一个最优子结构问题,最优子结构可以使用动态规划和贪心政策.
最短路径的子路径也是最短路径,最短路径经过的路径都是节点间最短的路径
最后的最短路径也不能包含权值为正的的环路,因此可认为最后都是简单路径
Dijkstra
1 2 3 4 5 6 7 8 9 for (int j = 0 ;j<n;j++) { if (vis[v]==0 && mp[u][j] != 0 && dist[j] > dist[u]+mp[u][j]) { dist[j] = dist[u] + mp[u][j]; } }
朴素邻接矩阵做法
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 void Dij (int root) { dist[root] = 0 ; for (int i = 0 ;i<n;i++) { int u = -1 ; int mx = inf; for (int j = 0 ;j<maxn;j++) { if (mx > dist[j] && vis[j]==0 ) { mx = dist[j]; u = j; } } if (u == -1 ) return ; vis[u] = 1 ; for (int j = 0 ;j<n;j++) { if (vis[v]==0 && mp[u][j] != 0 && dist[j] > dist[u]+mp[u][j]) { dist[j] = dist[u] + mp[u][j]; } } } }
实现路径打印
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 55 56 57 58 int pre[maxn];void Dij (int root) { dist[root] = 0 ; for (int i = 0 ;i<n;i++) { int u = -1 ; int mx = inf; for (int j = 0 ;j<maxn;j++) { if (mx > dist[j] && vis[j]==0 ) { mx = dist[j]; u = j; } } if (u == -1 ) return ; vis[u] = 1 ; for (int v = 0 ;v<n;v++) { if (mp[u][v] != 0 && dist[v] > dist[u]+mp[u][v]) { dist[v] = dist[u] + mp[u][v]; pre[v] = u; } } } }void printRoad (int s,int v) { if (s==v) { cout << s <<" " ; return ; } printRoad (s,pre[v]); cout << v <<" " ; }
新增边权
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 int cost[maxn][maxn],c[maxn];for (int v = 0 ;v<n;v++) { if (mp[u][v] != 0 && vis[v]==0 ){ if (&& dist[v] > dist[u]+mp[u][v]){ dist[v] = dist[u] + mp[u][v]; c[v] = c[u] + cost[u][v]; }else if (dist[v] == dist[u] + mp[u][v] && c[u] + cost[u][v] < c[v]){ c[v] = c[u] + cost[u][v]; } } }
新增点权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int weight[maxn],w[maxn];for (int v = 0 ;v<n;v++) { if (mp[u][v] != 0 && vis[v]==0 ){ if (dist[v] > dist[u]+mp[u][v]){ dist[v] = dist[u] + mp[u][v]; w[v] = w[u] + weight[v]; }else if (dist[v] == dist[u] + mp[u][v] && w[u] + weight[v] > w[v]){ w[v] = w[u] + weight[v]; } } }
求最短路径条数
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 int num[maxn];for (int v = 0 ;v<n;v++) { if (mp[u][v] != 0 && vis[v]==0 ){ if (dist[v] > dist[u]+mp[u][v]){ dist[v] = dist[u] + mp[u][v]; num [v] = num[u]; }else if (dist[v] == dist[u] + mp[u][v]){ num[v] += num[u]; } } }
尽管有上述的更新操作,但是其中是存在局限性的,假如求解的内容不满足最优子结构,就不可以使用上述修改思路,因此可以使用Dij+DFS
Dij+DFS
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 for (int v = 0 ;v<n;v++) { if (mp[u][v] != 0 && vis[v]==0 ){ if (dist[v] > dist[u]+mp[u][v]) { dist[v] = dist[u] + mp[u][v]; pre[v].clear (); pre[v].push_back (u); }else if (dist[v] == dist[u]+mp[u][v]){ pre[v].push_back (u); } } }int optValue;int st = 0 ; vector <int > path,temp;void printRoad (int v) { if (v==st){ temp.push_back (st); for (auto i : temp){ cout << i <<" " ; } temp.pop_back (); return ; } temp.push_back (v); for (int i = 0 ;i < pre[v].size ();i++){ printRoad (pre[v][i]); } temp.pop_back (); }
Bell-Ford
伪代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 for (int i = 0 ;i<n-1 ;i++) { for (each edge u->v){ if (d[u] + length[u->v] < d[v]){ d[v] = d[u] + length[u->v]; } } }
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 bool Ford (int s) { fill (d,d+n,inf); d[s] = 0 ; for (int i = 0 ;i<n-1 ;i++) { for (int u = 0 ;u<n;u++) { Node *p = head[u]; while (p!=NULL ){ int v = p->id; int dis = p->val; if (d[u] + dis < d[v]){ d[v] = dis + d[u]; } p = p->next; } } } for (int u = 0 ;u<n;u++) { Node *p = head[u]; while (p!=NULL ){ int v = p->id; int dis = p->val; if (d[u] + dis < d[v]){ return false ; } p = p->next; } } return true ; }
SPFA
SPFA是Bell-Ford的一个优化,优化的方面在,首先遍历n-1轮操作肯定变不了,在每次想要遍历所有边的时候,实际上是很浪费的,因为每次更新的只是d中的部分值,因此当只有某个顶点u的d[u]发生了改变,从他出发的边的邻接点v的d[v]值才有可能改变,因此在每次的松弛操作中,假如被松弛了,就放入队列中,而将每次遍历所有边的操作改为遍历这个节点的所有邻居节点,但是带来的一个问题就是,无法像last数组那样进行距离的限制,
时间方面非常的优秀,可以达到O(kE),经常优于优化Dij,最坏情况退化成O(VE)和Bell-Ford一样
统计最短路径条数
和Dij不同的是,同一个节点会访问多次,因此不能依据Dij中写,需要设置前驱数组setpre[MAXN],当遇到一条和已有最短路长度相同的时候就重新进行计算最短路径条数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 for (int i = 0 ;i<n-1 ;i++){ for (int u = 0 ;u<n;u++){ for (int j = 0 ;j<Adj[u].size ();j++){ int v = Adj[u][j].v; int val = Adj[u][j].val; if (d[u] + val < d[v]){ d[v] = d[u] + val; num[v] = num[u]; pre[v].clear (); pre[v].insert (u); }else if (d[u] + val == d[v]){ pre[v].insert (u); num[v] = 0 ; for (auto it = pre[v].begin ();it!=pre[v].end ();it++){ num[v] += num[*it]; } } } } }
Floyd
有向无环图描述表达式
拓扑排序
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <bits/stdc++.h> using namespace std; map<int , vector<int > >mp;int in[1000 ];int out[1000 ]; vector<int > ans; vector<int > tail;int n,m;void topsort () { stack<int >st; for (int i = 1 ;i<=n;i++) { if (in[i]==0 ) { st.push (i); ans.push_back (i); } } while (!st.empty ()) { int top = st.top (); st.pop (); for (int i = 0 ;i<mp[top].size ();i++) { int tem = mp[top][i]; in[tem]--; if (in[tem]==0 ) { st.push (tem); ans.push_back (tem); } } } }int main () { cin >> n >> m; for (int i = 0 ;i<m;i++) { int u,v; cin >> u >> v; mp[u].push_back (v); in[v]++; } topsort (); for (int i = 0 ;i<ans.size ();i++) { cout << ans[i] <<" " ; } return 0 ; }
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 void topsort () { int num = 0 ; queue<int >q; for (int i = 0 ;i<n;i++) { if (degree[i]==0 ) { q.push (i); num ++; } } while (!q.empty ()){ int front = q.front (); cout << front <<" " ; q.pop (); for (int i = 0 ;i<n;i++){ if (mp[front][i]!=0 ){ degree[i]--; if (degree[i]==0 ) { q.push (i); num++; } } } } if (num == n) cout << "\n拓扑排序成功!\n" ; else cout << "\n拓扑排序失败\n" ; }
关键路径
最长的一个路径是关键路径,需要求解每个活动的最早发生时间和最晚发生时间,只有当两者是相等的才是关键活动,所有由关键活动组成的路径为关键路径