无向图中,如果它的子图是连通图,并且是一颗树,则称其为生成树。
如果生成树的各边权值之和最小,则称其为最小生成树(MST)。
Prim算法求最小生成树:
int cost[MAX_V][MAX_V]; //边
int mincost[MAX_V]; //当前集合到该点的最短距离
bool used[MAX_V]; //标记该边是否纳入集合中
int V; //节点数
int prim()
{
for(int i=0;i<V;i++) //初始化
{
mincost[i]=INF;
used[i]=flase;
}
mincost[0]=0;//一开始只有这一个是0,其余都是INF,所以这个是最小值
int res=0;
while(true)
{
int vx=-1; //每次找边开始时先对vx初始化,使得第一次时vx就等于数组中第一个未纳入集合中的节点
for(int i=0;i<V;i++)
{
//在未纳入集合中的点中找最小边权的节点。分第一次赋值和之后赋值的情况
if(!used[i]&&(vx==-1||mincost[i]<mincost[vx])) vx=i;
}
if(vx==-1) break; //所有节点全找完
used[vx]=true;
res+=mincost[vx];
for(int i=0;i<V;i++)
{
mincost[i]=min(mincost[i],cost[vx][i]); //加入新点vx后更新最小边mincost[]
}
}
}
Kruscal求最小生成树(并查集实现):
struct edge {int u,v,cost;}; //定义边
bool cmp(const edge& e1, const edge& e2) // 定义sort的规则
{
return e1.cost<e2.cost;
}
edge es[MAX_E]; //保存边信息
int V,E; //节点数和边数
int kruscal()
{
sort(es,es+E,cmp); //首先对所有边排序
int_union_find(V); //对并查集初始化,其实就是初始化V个孤立的节点
int res=0;
for(int i=0;i<E;i++)//按顺序遍历各节点
{
edge e=es[i];
if(!same(e.u,e.v)) //same函数时判断两个节点是否在一棵树(一个并查集集合)中
{
merge(e.u,e.v); //并查集的合并操作
res+=e.cost;
}
}
return res;
}
//Kruscal复杂度为O( Elog(V) )
并查集
英文 disjoint tree ,即不相交的集合,将编号为1~N的N个对象划分为不相交的集合,在集合中选择某一个代表性元素代表该集合。
在并查集创建时要初始化:用n个节点表示n个元素,最开始没有边
常见的两种操作:1.合并两个集合 2.查找某元素属于哪个集合
实现方法(from hdu lcy课件):
合并的改进(按高度合并: 为了避免树的退化,所以在合并树的时候不是简单的将编号大的合并到编号小的节点上,二十要比较两颗树的高度,将更矮的树合并到较高的中去。
void merge(a,b) //伪代码
{
fx=find(a); //对于a,b若不能确定他们是否在同一颗树,要先验证
fy=find(b);
if(fx==fy)
return ;
if(height(fx)==height(fy)) //相等时树的高度还会增加一层
{
height(fx)=height(fy)+1;
set[fy]=fx;
}
else if(height(fx)<height(fy))
set[fx]=fy;
else
set[fy]=fx;
}
//注意merge合并是 树 的合并,所以要找到 根节点 去合并
查找的改进(找到后路径压缩: 并查集中每个节点的set(i)是父节点,而查找最终结果是返回这棵树的根节点。
int find(x) //伪代码
{
r=x;
//第一步要找到x节点所属集合的根
while(set[r]!=r)
r=set[r];
i=x;
//找到后r为根节点,第二步要将上步中寻找路径上的所有节点都变成根的直接子代,实现路径压缩
while(i!=r)
{
j=set[i];
set[i]=r;
i=j;
}
return r;
}
简单起见,即使在路劲压缩后树的高度改变,也不对这棵树的高度height[i]做修改。
并查集初始化时的点都是离散的,即每一个都是set[i]=i。