在正常求次小生成树的时候相信大家都喜欢kruskal,毕竟因为太大的图,尤其是稀疏图,prim算法并不实用,而且占内存太多,有可能不让开。但是在解决次小生成树和限制度生成树时候prim算法不失为一种很好的方法,因为这两种情况空间复杂度,尤其是图的大小都并不太大。
从一开始学了prim算法,再到拿prim算法解决次小生成树的变形,直到今天彻底搞懂prim算法求解限制k度最小生成树的问题。我感觉无论如何边,唯一不变的一点就是:
把贪心贯彻到底!!!
最小生成树:朴素prim 与dijkstra相同,一直找最小,一直更新插入
次小生成树:在求解完最小生成树后,找一个未被使用的最小边进行判断
限制K度生成树: 在分块求解完最小生成树后,进行每一次从所有树中找一个差值最大的,重新建树,松弛更新
一:
先说一下普通的最小生成树的题,prim算法 的不优化和优化的两种算法。
1.不优化-详细请点http://blog.csdn.net/qq_33951440/article/details/52640783
void prim(int cur)
{
int sum=0;
for(int i=1;i<=n;i++)
{
dis[i]=mp[cur][i];
}
vis[cur]=1;
for(int i=1;i<n;i++)
{
int index=-1;
int minn=inf;
for(int j=1;j<=n;j++)
{
if(vis[j]==0&&dis[j]<=minn)
{
minn=dis[j];
index=j;
}
}
vis[index]=1;
sum+=minn;
for(int j=1;j<=n;j++)
{
if(vis[j]==0)
{
if(dis[j]>=mp[index][j])
{
dis[j]=mp[index][j];
}
}
}
}
之后就是优化后的prim算法了,用优先队列来搞定,复杂度降低成为了(nlogn),然而空间复杂度仍然是一个问题
priority_queue<node>q;
while(!q.empty())
q.pop();
node fir;
fir.v=cur,fir.val=0;
q.push(fir);
while(!q.empty())
{
node tp=q.top();
q.pop();
int u=tp.v;
if(!tree[u])
{
ans+=tp.val;
tree[u]=id;
for(int i=1;i<n;i++)
{
if(!tree[i]&&mp[u][i]!=0&&dis[i]>mp[u][i])
{
pre[i]=u;
dis[i]=mp[u][i];
node temp;
temp.val=dis[i];
temp.v=i;
q.push(temp);
}
}
}
}
return ans;
二.次小生成树
题意:在最小生成树的基础上,询问是否可以不经过最小生成树构造时候经过的边,重新更改一条或者多条边,使得权值和仍然与最小生成树的权值和相同。
思路:在构造最小生成树时候,把用到的边进行全部标记掉,然后再从未被标记的边中找出一条边进行更改。
注意:这个时候prim算法就会看出好处了,在空间允许的情况下我们可以直接用二维数组 类似于mp[i][j]=0 的形式直接把边标记点,而不非要同kruskal算法一样重新构造
具体的点击
次小生成树模板-prim算法
三.限制K度最小生成树
题意:在普通最小生成树的基础上,题意允许你再多加K条边,构造一个 K度的最小生成树。
大体意思就是:
当把最小生成树都构造好(注意有可能不只是一棵树)之后再进行加入m条边,或者少于m条边,使得权值更小。我不感觉我可以讲理论讲得很好,所以具体的请看大神写得国家队论文,http://wenku.baidu.com/view/8abefb175f0e7cd1842536aa.html
思路:在不加入总目标的那个点之前,把所有的点都尽可能能连起来,也就是构造尽量少的最小生成树,之后再把每一棵树中找到一个最短的边,把树与总目标点相连,如果还有多余的度可以让我们插入,我们就在所有点中找到一个可以松弛最大的,注意不是最小的,而是相比之下松弛更大的。
我在这里只是把代码一步步的注释,记录下自己一步步调试的发现Orz
以题POJ1639为例
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#include<climits>
#include<queue>
using namespace std;
const int N=30;
struct node
{
int v,cap;
bool friend operator < ( node a, node b)
{
return a.cap>b.cap;
}
};
map<string,int> mp;
int g[N][N],dis[N],clo[N],pre[N],fst[N],max_side[N];
int n,m,k;
int Prim(int cur,int id)
{
int ans=0;
priority_queue<node>q;
node fir;
fir.v=cur,fir.cap=0;
q.push(fir);
while(!q.empty())
{
node tp=q.top();
q.pop();
int u=tp.v;
if(!clo[u])
{
ans+=tp.cap;
clo[u]=id;
for(int i=1;i<n;i++)
{
if(!clo[i]&&g[u][i]!=0&&dis[i]>g[u][i])
{
pre[i]=u; //在point.v( u ) 起点的基础上进行了松弛,被松弛的边的父亲进行修改
dis[i]=g[u][i]; //修改当前树到达i点的最小权值
node temp;
temp.cap=dis[i];
temp.v=i;
q.push(temp);
}
}
}
}
return ans;
}
void update(int cur,int last,int maxside) //这一步的DFS是一个关键,因为之前我们已经知道了当前树如果与总根0相连,可以花费的最小权值,以及花费最小全知道的那个点fst[i]
{
//因此,这个函数的根本意义是在于把当前树所有的点与fst[i]相连成为最小生成树的花费更新出来
max_side[cur]=maxside>g[cur][last]?maxside:g[cur][last]; //max_side在这棵树上,从cur点开始他自己一直到最上层0中的最大的一条权值边的记录
for(int i=1; i<n; i++)
if(i!=last && g[cur][i]!=0 && (pre[cur]==i || pre[i]==cur))// i!=last 防止来回遍历, g[cur][i] 对于cur 与i 这个点是否有还未使用的边,pre[cur]==i||pre[i]==cur 是否cur i存在直接相连的关系
{
update(i,cur,max_side[cur]);
}
}
void solve() //这一个函数是解决限制度生成树的关键
{
for(int i=0; i<n; i++)
{
dis[i]=INT_MAX;
clo[i]=pre[i]=fst[i]=0; //clo属于第几棵树,加入的标记, pre父节点,fst 在i所在的树中最大边fst[i]
}
int res=0,cnt=0;
for(int i=1; i<n; i++) //对于未被访问过的点进行prim初步构造
{
if(!clo[i])
res+=Prim(i,++cnt);
}
for(int i=1; i<n; i++) //进行最大边的更改
{
int id=clo[i]; //第N棵树
if(g[0][i]!=0&&(!fst[id]||g[0][i]<g[0][fst[id]]))
fst[id]=i; //当前树最小的与0连的权值的i节点
}
for(int i=1; i<=cnt; i++) //加入所有边与 根节点(0)所连时花费的权值
{
res+=g[0][fst[i]];
g[0][fst[i]]=g[fst[i]][0]=0;
update(fst[i],0,0); //每次都重新更新一次树i的fst数组
}
k=k-cnt; //接下来重复操作,直到度数满足条件
while(k--) //每次多加一条边,看看是否有一个在树中的点,直接到0,比原来在自己的生成树中的值还小(因为我们当初把所有和0连通的边都删除了,这次依次回代)
{
int tmp=0;
for(int i=1; i<n; i++) //找一个差值最大的
{
if(g[0][i]!=0&&(tmp==0||max_side[tmp]-g[0][tmp]<max_side[i]-g[0][i]))
tmp=i;
}
if(max_side[tmp]<=g[0][tmp]) //比较一下是否比原来短,如果不行,那一定都没有了跳出就行了
break;
res=res-max_side[tmp]+g[0][tmp];//重新更新图和树
g[0][tmp]=g[tmp][0]=0;
int p=0;
for(int i=tmp; pre[i]!=0; i=pre[i]) //对于已经找到的可以连接到总根的一个点,进行从原来的树上拆分,并且访问他的上一个节点,判断是否上一个节点可以通过这个点使用更小的权值,间接的与总根相连
if(p==0||g[p][pre[p]]<g[i][pre[i]])
p=i;
pre[p]=0;
update(tmp,0,0);
}
printf("Total miles driven: %d\n",res);
}
int main()
{
char s1[15],s2[15];
cin>>m;
mp.clear();
mp["Park"]=0;
n=1;
while(m--)
{
int d;
cin>>s1>>s2>>d;
if(mp.count(s1)==0)
{
mp[s1]=n++;
}
if(mp.count(s2)==0)
{
mp[s2]=n++;
}
int u=mp[s1],v=mp[s2];
if(g[u][v]==0||g[u][v]>d)
{
g[u][v]=d;
g[v][u]=d;
}
}
cin>>k;
solve();
return 0;
}
/*.
10
Alphonzo Bernardo 32
Alphonzo Park 57
Alphonzo Eduardo 45
Bernardo Park 19
Bernardo Clemenzi 89
Clemenzi Park 65
Clemenzi Herb 90
Clemenzi Eduardo 30
Park Herb 24
Herb Eduardo 79
3
*/