一、最小生成树---prim算法实现
思想:
1、从任意一个顶点开始构造生成树,假设就从1号顶点吧, 首先将顶点1加入生成树中,用一个一维数组book来标记 哪些顶点已经加入了生成树。
2、用数组dis记录生成树到各个顶点的距离,最初生成树中之后1号 顶点,有直连边时,数组dis中存储的就是1号顶点到该顶点 的边的权值,没有直连边的时候就是无穷大,即初始化dis数组。
3、从数组dis中选出离生成树最近的顶点(假设这个顶点为j) 加入到生成树中(即在数组dis中找到最小值)。再以j为中间点, 更新生成树到每一个非树顶点的距离(就是松弛啦), 即如果dis[k]>e[j][k]则更新dis[k]=e[j][k]。
4、重复第三步,直到生成树中有n个顶点为止。
代码实现:
<span style="font-size:18px;">#include<stdio.h>
int main(void)
{
int n,m,i,j;
int k,min;
int t1,t2,t3;
int e[7][7],dis[7];
int book[7]={0};//book数组初始化
int inf=99999999;
int count=0,sum=0;
//count用来记录生成树中顶点的个数,sum用来存储路径之和
scanf("%d %d",&n,&m);
//读入n,m,n表示顶点的个数,m表示边的条数。
//初始化
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
{
if(i==j)
{
e[i][j]=0;
}
else
{
e[i][j]=inf;
}
}
}
//开始读入边
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&t1,&t2,&t3);
//注意无向图,需要将边反向在存储一遍
e[t1][t2]=t3;
e[t2][t1]=t3;
}
//初始化dis数组,这里是1号顶点到各个顶点的初始距离,因为当前生成树中只有1号顶点
for(i=1;i<=n;i++)
{
dis[i]=e[1][i];
}
//将1号顶点加入生成树,这里book来标记一个顶点是否已经加入生成树
book[1]=1;
count++;
while(count<n)
{
min=inf;
for(i=1;i<=n;i++)
{
if(book[i]==0&&dis[i]<min)
{
min=dis[i];
j=i;
}
}
book[j]=1;
count++;
sum=sum+dis[j];
//扫描当前顶点j所有的边,再以j为中间点,更新生成树到每一个非树顶点的距离;
for(k=1;k<=n;k++)
{
if(book[k]==0&&dis[k]>e[j][k])
{
dis[k]=e[j][k];
}
}
}
printf("%d\n",sum);
return 0;
}
</span>
二、最小生成树—prim算法的堆优化
需要用到三个数组
1、数组dis用来记录生成树到各个顶点的距离
2、数组h是一个最小堆,堆里面存储的是顶点编号。(请注意,这里并不是按照顶点编号的大小来建立最小堆的,而是按照顶点在数组dis中所对应的值来建立这个最小堆的)
3、pos数组来记录每个顶点在最小堆中的位置
例如,下图中
下面最小堆中的圆圈中存储的是顶点编号,圆圈右下角的数是该顶点(圆圈里面的数)到生成树的最短距离,即数组dis中存储的值
代码实现:
<span style="font-size:18px;">#include<stdio.h>
int dis[7],book[7]={0};
//book数组用来记录哪些顶点已经放入生成树中
int h[7],pos[7],size;
//h数组用来保存堆,pos数组用来存储每个顶点在堆中的位置,size为堆的大小
//交换函数,用来交换堆中的两个元素的值
void swap(int x,int y)
{
int t;
t=h[x];
h[x]=h[y];
h[y]=t;
//同步更新pos
t=pos[h[x]];
pos[h[x]]=pos[h[y]];
pos[h[y]]=t;
}
//向下调整函数,传入一个需要向下调整的结点编号
void siftdown(int i)
{
int t,flag=0;
//flag用来标记是否需要继续向下调整
while(i*2<=size&&flag==0)
{
//比较i和它左儿子i*2在dis中的值,并用t记录值较小的结点编号
if(dis[h[i]]>dis[h[i*2]])
{
t=i*2;
}
else
{
t=i;
}
//如果它有右儿子,再对右儿子进行讨论
if(t*2+1<=size)
{
//如果右儿子的值更小,更新较小的结点编号
if(dis[h[t]]>dis[h[i*2+1]])
{
t=i*2+1;
}
}
//如果发现最小的结点编号不是自己,说明子结点中有比父结点更小的
if(t!=i)
{
//交换它们
swap(t,i);
//更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整
i=t;
}
else
{
//否则说明当前的父结点已经比两个子结点都要小了,不需要再进行调整了
flag=1;
}
}
}
//传入一个需要向上调整的结点编号i
void siftup(int i)
{
int flag=0;
//flag用来标记是否需要继续向上调整
if(i==1)
{
//如果是堆顶,就返回,不需要调整
return ;
}
//不在堆顶,并且当前结点i的值比父结点小的时候继续向上调整
while(i!=1&&flag==0)
{
//判断是否比父结点的小
if(dis[h[i]]<dis[h[i/2]])
{
//交换它和它父亲的位置
swap(i,i/2);
}
else
{
//表示已经不需要调整了,当前结点的值比父结点的值要大
flag=1;
}
//更新编号i为它父结点的编号,从而便于下一次继续向上调整
i=i/2;
}
}
//从堆顶取出一个元素
int pop()
{
int t;
t=h[1];//用一个临时变量记录堆顶点的值
pos[t]=0;//其实这句话可以不要
h[1]=h[size];//将堆的最后一个点赋值到堆顶
pos[h[1]]=1;
size--;//堆的元素减少1
siftdown(1);//向下调整
return t;//返回之前记录的堆顶点
}
int main(void)
{
int n,m,i,j,k;
//u,v,w和next的数组大小要根据实际情况来设置,此图是无向图,要比2*m的最大值要大1
//first要比n的最大值要大1,要比2*m的最大值要大1
int u[19],v[19],w[19],first[7],next[19];
int inf=99999999;
//count用来记录生成树中顶点的个数,sum用来记录存储路径之和
int count=0,sum=0;
//读入n和m,n表示顶点个数,m表示边的条数
scanf("%d %d",&n,&m);
//开始读入边
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&u[i],&v[i],&w[i]);
}
//这里是无向图,所以需要将所有的边再反向存储一次
for(i=m+1;i<=2*m;i++)
{
u[i]=v[i-m];
v[i]=u[i-m];
w[i]=w[i-m];
}
//开始使用邻接表存储边
for(i=1;i<=n;i++)
{
first[i]=-1;
}
for(i=1;i<=2*m;i++)
{
next[i]=first[u[i]];
first[u[i]]=i;
}
//将1号顶点加入生成树,book数组来标记一个顶点是否已经加入生成树
book[1]=1;
count++;
//初始化dis数组,这里是1号顶点到其余各个顶点的初始距离
dis[1]=0;
for(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(i=1;i<=size;i++)
{
h[i]=i;
pos[i]=i;
}
for(i=size/2;i>=1;i--)
{
siftdown(i);
}
//先弹出一个堆顶元素,因为此时堆顶是1号顶点
pop();
while(count<n)
{
j=pop();
book[j]=1;
count++;
sum=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];
//对该点在堆中进行向上调整
//pos[v[k]]存储的是顶点v[k]在堆中的位置
siftup(pos[v[k]]);
}
k=next[k];
}
}
printf("%d\n",sum);
return 0;
}</span>