一、最小生成树——prim算法
prim算法和之前的Dijkstra算法几乎一样
主要差别在于:1. d[ ]数组的含义不同:在prim算法中指的是一点到集合S的距离
2. 看情况要加一个ans来存放最小生成树的边权之和
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
const int INF = 100000;
int n, m;
bool vis[maxn] = {false};
int G[maxn][maxn];
int d[maxn];
int prim() //默认0号为初始点,且返回最小生成树的边权之和
{
fill(d, d + maxn, INF);
d[0] = 0;
int ans = 0;
for (int i = 0; i < n; i++)
{
int u = -1, min = INF;
for (int j = 0; j < n; j++)
{
if (d[j] < min && vis[j] == false)
{
u = j;
min = d[j];
}
}
if (u == -1)
return;
vis[u] = true;
ans += d[u];
for (int v = 0; v < n; v++)
{
if (vis[v] == false && G[u][v] != INF && G[u][v] < d[v])
{
d[v] = G[u][v];
}
}
return ans;
}
}
struct Node
{
int v;
int dis;
};
vector<Node> Adj[maxn];
int prim2()
{
fill(d, d + maxn, INF);
d[0] = 0;
int ans = 0;
for (int i = 0; i < n; i++)
{
int u = -1, min = INF;
for (int j = 0; j < n; j++)
{
if (vis[j] == false && d[j] < min)
{
u = j;
min = d[j];
}
}
if (u == -1)
return;
vis[u] = true;
ans += d[u];
for (int j = 0; j < Adj[u].size(); j++)
{
int v = Adj[u][j].v;
int dis = Adj[u][j].dis;
if (vis[v] == false && dis < d[v])
{
d[v] = dis;
}
}
}
return ans;
}
二、最小生成树——kruskal算法
伪代码如下:
int kruskal()
{
令最小生成树的边权之和为ans、最小生成树的当前边数Num_Edge;
将所有边按边权从小到大排序
for(从小到大枚举所有边)
{
if(当前测试边的两个端点在不同的连通块中)
{
将该测试边加入最小生成树中;
ans+=测试边的边权;
num_edge++;
当边数num_edge等于顶点数-1时 结束循环;
}
}
return ans;
}
当前测试边的两个端点在不同的连通块中:可以使用并查集
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
const int INF = 1000000;
int father[maxn];
struct Edge
{
int u, v;
int cost;
} E[maxn];
bool cmp(Edge a, Edge b)
{
return a.cost < b.cost;
}
int findFather(int x) //路径压缩!!
{
int a = x;
while (x != father[x])
{
x = father[x];
}
while (a != father[a])
{
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
int kruskal(int n, int m)
{
int ans = 0, numEdge = 0;
for (int i = 1; i <= n; i++)
{
father[i] = i;
}
sort(E, E + m, cmp);
for (int i = 0; i < m; i++)
{
int faU = findFather(E[i].u);
int faV = findFather(E[i].v);
if (faU != faV)
{
father[faU] = faV;
ans += E[i].cost;
numEdge++;
if (numEdge == n - 1)
break;
}
}
if(numEdge!=n-1) return -1;
else return ans;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<m;i++)
{
scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].cost);
}
int ans=kruskal(n,m);
printf("%d",ans);
}
三、拓扑排序
有向无环图 DAG:如果一个有向图的任意顶点都无法通过一些有向边回到自身,那么称这个有向图为有向无环图
拓扑排序的步骤:
- 定义一个队列Q,并把所有入度为0的结点加入队列
- 取出队首结点,输出。然后删除所有从它出发的边,并令这些边到达的顶点入度减一
- 反复进行2操作,直到队列为空。如果队列为空且入过队的结点数目恰好为N,说明拓扑排序成功,图G为有向无环图(设置num为加入拓扑序列的节点个数)
AOV网络(Activity on vertex)如果有合理的拓扑序,则必定是有向无环图
利用这个性质,可以用拓扑排序来判断一个图是否为有向无环图。
#include<bits/stdc++.h>
using namespace std;
const int maxn=100;
vector<int> Adj[maxn];
int inDegree[maxn];
int n,m;
bool topLogicalSort()
{
queue<int> q;
int num=0;
for(int i=0;i<n;i++)
{
if(inDegree[i]==0)
{
q.push(i);
num++;
}
}
while(!q.empty())
{
int u=q.front();
q.pop();
num++;
for(int i=0;i<Adj[u].size();i++)
{
int v=Adj[u][i];
inDegree[v]--;
if(inDegree[v]==0) q.push(v);
}
}
if(num==n) return true;
else return false;
}
四、关键路径(AOE网)
1. 概念
AOV网
AOE网:用来表示一个工程的进行过程,而工程常常可以分为若干个子工程
AOE网中的最长路径被称为关键路径,而把关键路径上的活动称为关键活动(why? 一个工程的完成时间取决于最长的路径!)
2. AOV网转AOE网
将AOV网中每个顶点都拆成两个顶点,分别表示活动的起点和终点,而两个顶点之间用有向边链接,该有向边表示原顶点的活动,边权给定。
3. 求最长路径
求最长路径(最长简单路径):
没有正环的图,可以把所有边权乘以-1,然后用Bellman-Ford算法或者SPFA算法求最短路径长度,再将所得结果取反 有正环的图,不存在最长路径
如果求有向无环图的最长路径长度,则以下关键路径求法更快:
#include<bits/stdc++.h>
using namespace std;
const int maxn=100;//最大顶点数
int ve[maxn],vl[maxn];
int inDrgree[maxn];
int n,m;
struct Node
{
int u,v;
int w;
int e,l; //事件的最早开始时间和最迟开始时间
};
vector<Node> G[maxn];
stack<int> topOrder;
bool toplogicalSort()
{
fill(ve,ve+n,0);
fill(vl,vl+n,ve[n-1]);
queue<int> q;
for(int i=0;i<n;i++)
{
if(inDrgree[i]==0)
q.push(i);
}
while(!q.empty())
{
int u=q.front();
q.pop();
topOrder.push(u);
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i].v;
inDrgree[v]--;
if(inDrgree[v]==0) q.push(v);
if(ve[v]<ve[u]+G[u][v].w)
{
ve[v]=ve[u]+G[u][v].w;
}
}
}
if(topOrder.size()==n) return true;
else return false;
}
//已知汇点编号为 n-1 返回ve[n-1]
int CriticalPath()
{
memset(ve,0,sizeof(ve));
if(toplogicalSort()==false)
{
return -1;
}
fill(vl,vl+n,ve[n-1]);
while(!topOrder.empty())
{
int u=topOrder.top();
topOrder.pop();
for(int i=0;i<G[u].size();i++)
{
int v=G[u][i].v;
if(vl[v]-G[u][v].w<vl[u])
{
vl[u]=vl[v]-G[u][v].w;
}
}
}
for(int u=0;u<n;u++)
{
for(int i=0;i<G[u].size();i++)
{
int v=G[u][v].v;
int w=G[u][v].w;
int e=ve[u],l=vl[u]-w;
if(e==l)
{
printf("%d->%d\n",u,v);
}
}
}
return ve[n-1]; //返回关键路径长度
}
//未知汇点编号:则ve最大的一定是最后一个事件 那么改一下初始化
// int maxLength=0;
// for(int i=0;i<n;i++)
// {
// if(ve[i]>maxLength)
// {
// maxLength=ve[i];
// }
// }
// fill(vl,vl+n,maxLength);