您的位置:首页 > 其它

最小生成树 《啊哈算法》读书笔记

2016-07-08 14:18 295 查看
最小生成树:任何只由G的边构成,并包含G的所有顶点的树称为G的生成树(G连通). 加权无向图G的生成树的代价是该生成树的所有边的代码(权)的和. 最小代价生成树是其所有生成树中代价最小的生成树。

  假设WN=(V,{E})是一个含有n个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含n个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有n棵树的一个森(摘自  nocow)         

  求最小生成树的主要算法:Kruskal算法     Prim算法

Kruskal算法(克鲁斯卡尔算法):(如果想要边的总长度之和最短,我们自然可以想到首先先选最短的边)将所有的边排序,从最小的边开始选,每次连通最小的边,不能形成回路,所以就要求判断两点间是否已经连通。为了优化操作,我们这里用并查集优化,判断其是否在一个树上。如果不在一个树上,就加进去,继续添加。

  时间复杂度为O(MlogM)

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;

struct edge
{
int u;
int v;
int w;
}e[10];
int n,m;
int f[7]={0},sum=0,counter=0;
void quicksort(int left,int right)
{
int i,j;
struct edge t;
if(left > right)
return;

i = left;
j = right;
while(i!=j)
{
//注意顺序
//先从右边找
while(e[j].w >= e[left].w && i < j)
j--;
//从左边找
while(e[i].w <= e[left].w && i < j)
i++;
if(i<j)
{
t = e[i];
e[i]= e[j];
e[j] = t;
//swap(e[i],e[j]);
}
}
//基准归位
t = e[left];
e[left]= e[i];
e[i] = t;
//swap(e[left],e[i]);
quicksort(left, i-1);
quicksort(i+1, right);
return;
}

int getf(int v)
{
if(f[v]==v)
return v;
else
{
//路径压缩,找到每个人的祖宗
f[v] = getf(f[v]);
return f[v];
}
}

bool merge(int v,int u)
{
int t1,t2;//t1,t2分别为v和u的boss,每次都是用boss解决
t1=getf(v);
t2=getf(u);
if(t1!=t2)
{
f[t2] = t1;//靠左原则
return 1;
//路径压缩后,将f[u]的根值夜赋值为v的祖先f[t1]
}
return 0;
}
int main()
{
scanf("%d%d",&n,&m);

for(int i=1;i<=m;i++)
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);

quicksort(1,m);

//并查集优化
for(int i=1;i<=n;i++)
f[i]=i;

//Kruskal算法核心部分
for(int i=1;i<=m;i++)//枚举
{
//判断一条边的两个顶点是否连通,即是否在一个集合中
if(merge(e[i].u,e[i].v))
{
counter++;
sum+=e[i].w;
}
if(counter == n-1)
break;
}
printf("%d\n",sum);
return 0;
}
/*
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
*/


然后看到NOCOW上的超级精简代码,顺便贴上吧



/*使用Union-Find判断是否在一个集合,代码比较STL-style
Author:YangZX*/
#include <iostream>
#include<algorithm>
using namespace std; const
int MAXV = 1024, MAXE = 100001;
int n, m, f[MAXV], ans, cnt;
struct edge
{
int f, t, w;
}es[MAXE];
bool cmp(const edge &a, const edge &b)
{
return a.w < b.w;
}
void Fill(int &a)
{
static int cnt = 0; a = ++cnt;
}
int get(int x)
{
return x == f[x] ? x : f[x] = get(f[x]);
}
void Kruskal(const edge &e)
{ if(get(e.f) != get(e.t))
{
f[get(e.f)] = get(e.t); ans += e.w; cnt++;
}
}
void Read(edge &e)
{
cin>>e.f>>e.t>>e.w;
}
int main()
{
cin>>n>>m;
for_each(es+1, es+m+1, Read);
make_heap(es+1, es+m+1, cmp);
sort_heap(es+1, es+m+1, cmp);
for_each(f+1, f+n+1, Fill);
for_each(es+1, es+m+1, Kruskal);
cout<<(cnt < n-1 ? -1: ans)<<endl;
return 0;
}
/*
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
*/


Prim算法(普里姆算法) :选中任意一个顶点,将其加入到生成树中去(这里假设为顶点1)。用数组记录生成树到各个顶点的距离,每次都是这样。从数组中选出离生成树最近的顶点加入到生成树中(这里用的dijkstra的思想),用dis数组更新为生成树到每一个不在生成树中的顶点的距离(松弛),重复直到有了n个顶点为止。

代码:

#include<iostream>
#include<cstdio>
using namespace std;

const int INF = 99999999;
int e[7][7],dis[7],book[7]={0};
int main()
{
int n,m;
int counter = 0,sum = 0;
scanf("%d%d",&n,&m);

//初始化
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
if(i == j)  e[i][j] = 0;
else    e[i][j] = INF;

int t1,t2,t3;
for(int i = 1;i <= m;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
e[t1][t2] = e[t2][t1] = t3;//无向图
}

//初始化dis数组
for(int i = 1;i <= n;i++)
dis[i] = e[1][i];

//Prim算法核心
//将1号顶点加入生成树
book[1] = 1;
counter++;
int u,v,minn;
while(counter < n)
{
minn = INF;
for(int i = 1;i <= n;i++)
{
if(!book[i] && dis[i] < minn)
{
minn = dis[i];
u = i;
}
}

book[u] = 1;
counter++;
sum += dis[u];
for(int v = 1;v <= n;v++)
{
if(!book[v] && dis[v] > e[u][v])
dis[v] = e[u][v];
}
}
printf("%d\n",sum);
return 0;
}
/*
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
*/


堆优化后的代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int INF = 99999999;
const int N = 100;
int dis
,book
={0};
int h
,pos
,size;

void swap(int x,int y)
{
int t;
t = h[x];
h[x] = h[y];
h[y] = t;

t = pos[h[x]];
pos[h[x]] = pos[h[y]];
pos[h[y]] = t;
}

void siftdown(int i)
{
int t,flag = 0;
while(i*2 <= size &&flag == 0)
{
if(dis[h[i]] > dis[h[i*2]])
t=2*i;
else
t = i;

//如果它有左儿子,再对右儿子进行讨论
if(i*2+1 <= size)
{
if(dis[h[t]] > dis[h[i*2+1]])
t = i*2+1;
}
//如果发现最小的节点编号不是自己,说明子节点中有更小的
if(t!=i)
{
swap(t,i);
i = t;
}
else
flag = 1;
}
}

void siftup(int i)
{
int flag = 0;
if(i == 1)
return;
while(i!=1 && flag == 0)
{
if(dis[h[i]] < dis[h[i/2]])
swap(i,i/2);
else
flag = 1;
i/=2;
}
}

int pop()
{
int t;
t = h[1];
pos[t] = 0;
h[1] = h[size];
pos[h[1]] = 1;
size--;
siftdown(1);
return t;
}
int main()
{
int n,m,k;
int u
,v
,w
,first
,next
;
int counter = 0,sum = 0;
scanf("%d%d",&n,&m);

//读入边
for(int i = 1;i <= m;i++)
scanf("%d%d%d",&u[i],&v[i],&w[i]);

//无向图
for(int i = m+1;i <= 2*m;i++)
{
u[i] = v[i-m];
v[i] = u[i-m];
w[i] = w[i -m];
}

//邻接表储存边
for(int i = 1;i <= m;i++)
first[i] = -1;
for(int i = 1;i <= 2*m;i++)
{
next[i] = first[u[i]];
first[u[i]] = i;
}

//Prim算法核心
//讲1号顶点加入生成树
counter++;
book[1] = 1;

//初始化dis数组,这里是1号顶点到其余各顶点的初始距离
dis[1] = 0;
for(int i = 2;i <= n;i++)
dis[i] = INF;
k = first[1];
while(k != -1)
{
dis[v[k]] = w[k];
k = next[k];
}

//初始化堆
size = n;
for(int i = 1;i <= size;i++)
{
h[i] = i;
pos[i] = i;
}
for(int i = size/2;i >= 1;i--)
{
siftdown(i);
}
pop();//先弹出一个堆顶的元素,因为此时堆顶是1号顶点
int j;
while(counter < n)
{
j = pop();
book[j] = 1;
counter++;
sum += dis[j];

//扫描当前顶点j所有的边,再以j为中间节点,进行松弛
k = first[j];
while(k != -1)
{
if(book[v[k]]==0&&dis[v[k]] > w[k])
{
dis[v[k]] = w[k];
siftup(pos[v[k]]);//对该点再堆中进行向上调整
}
k = next[k];
}
}
printf("%d\n",sum);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: