前言
最近的学习,深深地感受到了算法分享的重要性。
应队友的要求,写一篇简单易懂的算法讲解(希望我写的出来),深入浅出(磨磨唧唧)的讲解一下朱刘算法。
什么是朱刘算法
大家应该都知道,如何求解最小生成树,可以使用Prim, 也可以使用Kruskal, 总而言之,就是寻找使得所有节点连通的权值最小的路径。
当然,这是基于无向图而言的,而对于有向图,我们要求“最小生成树”的话,就可以使用朱刘算法。
简而言之,朱刘算法就是求有向图的最小生成树。
大概想法:
用贪心的思想每次进行选择,缩点,最终求得。
重点内容
1,选入边集——找到除root点之外,每一个点的所有入边中权值最小的,用数组in[]记录下这个最小权值,用pre[]记录到达该点的前驱;(若图中存在独立点,最小树形图是不存在的,所以在该步骤结束后,要判断一下)
2,找有向环,并用数组id[]记录节点所属环的编号。
3,找到环后,缩点,并更新权值。(感觉和SCC缩点差不多)
4,以环数为下一次查找的点数,继续执行上述操作,直到没有环 或者 判定出不存在最小树形图为止。
建边:
const int INF = 0x3f3f3f3f;
const int MAXM = 40010;
const int MAXN = 1010;
struct Edge
{
int u, v, cost;//u->v,边权值为w
};
Edge edge[MAXM];
int pre[MAXN];//存储父节点
int id[MAXN];//id【i】记录节点i所在的环的编号
int visit[MAXN];//标记作用
int in[MAXN];//in【i】记录i入边中最小的权值
int zhuliu(int root, int n, int m)//朱刘算法, root是起始根节点,n是最大的点,m是边数
{
int res = 0;//权值
int v;
while(1)
{
for(int i=0; i<n; i++)//遍历每一个点
in[i] = INF;//初始化in数组,将每一个入度的权值清为INF
for(int i=0; i<m; i++)//遍历每一条边
{
if(Edge[i].u!=Edge[i].v&&Edge[i].cost<in[Edge[i].v])//如果是一条边并且边权值小于入度的权值
{
pre[Edge[i].v] = Edge[i].u;//将前一个节点记录
in[Edge[i].v] = Edge[i].cost;//更新in数组
}
}
for(int i=0; i<n; i++)//遍历每一个点
{
if(i!=root&&in[i]==INF)//如果找到了一个入度的权值为INF,并且不是根节点
return -1;//则是没有找到最小树形图,则跳出循环
}
memset(id, -1, sizeof(id));//
memset(visit, -1, sizeof(visit));//
int tn = 0;
in[root] = 0;
for(int i=0; i<n; i++)//遍历每一个节点
{
res += in[i];//加入每一个点的最小入度的权值
int v = i;
while(visit[v]!=i&&id[v]==-1&&v!=root)//当加入的点不在环上,不是根节点
{
visit[v] = i;//标记
v = pre[v];//更新前一个节点
}
if(id[v]==-1&&v!=root)//如果当前节点都已经访问过了
{
for(int u=pre[v]; u!=v; u=pre[u])//找到之前的点
{
id[u] = tn;//对id赋值
}
id[v] = tn++;//更新此值
}
}
if(tn==0)//没有可以构成的树
break;//跳出
for(int i=0; i<n; i++)//当结束了while循环,
{
if(id[i]==-1)//如果没有找到环的标号
{
id[i] = tn++;//每一个点都可以当成一个环
}
}
for(int i=0; i<m;)//遍历每一条边,展开环
{
v = Edge[i].v;
// int u = Edge[i].u;
Edge[i].u = id[Edge[i].u];
Edge[i].v = id[Edge[i].v];
if(Edge[i].u!=Edge[i].v)
Edge[i++].cost -= in[v];
else
swap(Edge[i], Edge[--m]);
}
n = tn;
root = id[root];
}
return res;
}