枯木逢春不在茂,年少且惜镜边人
终是魔王梦了蝶,亦是恩静亦是劫
这周呢学习一下最小生成树的一些算法,而且离开学恐怕不远了,所以在下周准备复习这段时间学过的算法,顺便刷刷题,对,是这样的。
首先呢, 我们看看最简单的例子
题目意思:
最近小哼迷上了《龙门镖局》,从恰克图道武夷山,从张家口道老河口,从迪化道佛山,从蒙自道奉天…古代镖局的运镖,也就是现在的物流。镖局每到一个地方开展业务,都需要堆运镖途中的绿林好汉进行打点(不给钱就不让过路)。好说话的打点费就比较低,不好说话的打点费就比较高。城镇类似如下,顶点是城镇编号,边上的值表示这条道路上打点绿林好汉需要的银子数。
input
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
意思就是让找出一个树然后使这中间的打点的银子最少
也就是找出最小生成树
n个点 需要n-1条边,那么怎么找呢?
贪心法则:每次都选择最小的边
当然在选择的时候可能会遇到形成一个回路,这个时候就要用到并查集的作用
详细请看代码
#include<stdio.h>
struct f
{
int u;
int v;
int w;
};
struct f a[10];
int f[9],sum=0,count=0;
int m,n;
void quicksort(int left,int right)
{
struct f t;
int i,j;
if(left>right)
return ;
i=left;
j=right;
while(i!=j)
{
while(a[j].w>=a[left].w&&i<j)
j--;
while(a[i].w<=a[left].w&&i<j)
i++;
if(i<j)
{
t=a[j];
a[j]=a[i];
a[i]=t;
}
}
t=a[left];
a[left]=a[i];
a[i]=t;
quicksort(left,i-1);
quicksort(i+1,right);
return ;
}
int gz(int v)
{
if(f[v]==v)
return v;
else
{
f[v]=gz(f[v]);
return f[v];
}
}
int cc(int v,int u)
{
int t1,t2;
t1=gz(v);
t2=gz(u);
if(t1!=t2)
{
f[t2]=t1;
return 1;
}
return 0;
}
int main()
{
int i;
scanf("%d %d",&n,&m);
for(i=1;i<=m;i++)
scanf("%d %d %d",&a[i].u,&a[i].v,&a[i].w);
quicksort(1,m);
for(i=1;i<=n;i++)
f[i]=i;
for(i=1;i<=m;i++)
{
if(cc(a[i].u,a[i].v))
{
count++;
sum=sum+a[i].w;
}
if(count==n-1)
break;
}
printf("%d",sum);
return 0;
}
可以看出利用擒贼先擒王的算法,可以很快的判断他们的祖先,如果是同一个祖先,那么他们连接的话就会连通,所以运用并查集可以判断是否连通。
下面我再学习一下另一种解法求解最小生成数
#include<stdio.h>
int main()
{
int min,t1,t2,t3,i,j,k,m,n;
int e[7][7],dis[7],book[7]={0};
int inf=99999999;
int count=0,sum=0;
scanf("%d %d",&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;
}
for(i=1;i<=n;i++)
{
dis[i]=e[1][i];
}
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];
for(k=1;k<=n;k++)//更新这个结点的连接点到数的最近距离
{
if(book[k]==0&&dis[k]>e[j][k])
dis[k]=e[j][k];
}
}
printf("%d",sum);
return 0;
}
另外书上还介绍了一种方法,就是堆,运用堆的话就可以很好的减少时间复杂度
额,我刚看了下,太菜了,所以学习了一下邻接表,
先看看代码吧
#include<stdio.h>
int main()
{
int n,m,i;
int u[6],v[6],w[6];
int frist[5],next[5];
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++)
frist[i]=-1;
for(i=1;i<=m;i++)
{
scanf("%d %d %d",&u[i],&v[i],&w[i]);
next[i]=frist[u[i]];
frist[u[i]]=i;
}
return 0;
}
这里面第一次看可能看不懂
但是如果你能手写一边过程
并且结合代码看看就会懂了
for(i=1;i<=m;i++)
{
scanf("%d %d %d",&u[i],&v[i],&w[i]);
next[i]=frist[u[i]];
frist[u[i]]=i;
}
这里说一下frist数组存储的是这条路径的编号,他的下标就是这条路径的第一个节点
而next数组存储的是frist数组的下一条路径,通过下标可以找出这些路径
下面我们看看怎么输出这些路径
话不多说 上代码
#include<stdio.h>
int main()
{
int k;
int n,m,i;
int u[6],v[6],w[6];
int frist[5],next[5];
scanf("%d %d",&n,&m);
for(i=1;i<=n;i++)
frist[i]=-1;
for(i=1;i<=m;i++)
{
scanf("%d %d %d",&u[i],&v[i],&w[i]);
next[i]=frist[u[i]];
frist[u[i]]=i;
}
for(i=1;i<=n;i++)
{
k=frist[i];
while(k!=-1)
{
printf("%d %d %d\n",u[k],v[k],w[k]);
k=next[k];
}
}
return 0;
}
这就是代码的输出
另外
for(i=1;i<=n;i++)
{
k=frist[i];
while(k!=-1)
{
printf("%d %d %d\n",u[k],v[k],w[k]);
k=next[k];
}
}
这部分 利用k来不断搜索祖宗节点的路径,这样搜索出来刚好是输入是相反路径,这其实也很像递归,都是反着出来,有时候也有秒用
这就是输出,可能不太明显,但是事实就是相反的.
ok,解决了这个问题,在用堆解决这个问题的时候还需要复习一下堆,ok ,复习一下
#include<stdio.h>
int dis[7],book[7]={0};
int h[7],pos[7],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;
return ;
}
void siftdown(int i)
{
int t,flag=0;
while(i*2<=size&&flag==0)
{
if(dis[h[i]]>dis[h[i*2]])
{
t=i*2;
}
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;
}
return ;
}
void siftup(int i)
{
int flag=0;
if(i==1)
return ;
if(i!=1&&flag==0)
{
if(dis[h[i]]<dis[h[i/2]])
swap(i,i/2);
else
flag=1;
i=i/2;
}
return ;
}
int pop()
{
int t;
t=h[1];
h[1]=h[size];
pos[h[1]]=1;
size--;
siftdown(1);
return t;
}
int main()
{
int n,m,i,j,k;
int u[19],v[19],w[19],frist[7],next[19];
int inf=99999999;
int count=0,sum=0;
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++)
frist[i]=-1;
for(i=1;i<=2*m;i++)
{
next[i]=frist[u[i]];
frist[u[i]]=i;
}
book[1]=1;
count++;
dis[1]=0;
for(i=2;i<=n;i++)
dis[i]=inf;
k=frist[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);
}
pop();
while(count<n)
{
j=pop();
book[j]=1;
count++;
sum=sum+dis[j];
k=frist[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",sum);
return 0;
}
这段代码是我刚学习的有点长,理解的不透彻,让我再学习几天再来,说说,详细请看下周周报,以为这周就可以学完这本书,没想到,后面的挺吃力,所以就分两周来学,其实在学习后面的同时也是对前面的复习,所以预习 学习 加复习同时进行才是最好的
我们下周见