最小生成树

枯木逢春不在茂,年少且惜镜边人
终是魔王梦了蝶,亦是恩静亦是劫

这周呢学习一下最小生成树的一些算法,而且离开学恐怕不远了,所以在下周准备复习这段时间学过的算法,顺便刷刷题,对,是这样的。

首先呢, 我们看看最简单的例子
题目意思:
最近小哼迷上了《龙门镖局》,从恰克图道武夷山,从张家口道老河口,从迪化道佛山,从蒙自道奉天…古代镖局的运镖,也就是现在的物流。镖局每到一个地方开展业务,都需要堆运镖途中的绿林好汉进行打点(不给钱就不让过路)。好说话的打点费就比较低,不好说话的打点费就比较高。城镇类似如下,顶点是城镇编号,边上的值表示这条道路上打点绿林好汉需要的银子数。

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;
 } 

在这里插入图片描述
这段代码是我刚学习的有点长,理解的不透彻,让我再学习几天再来,说说,详细请看下周周报,以为这周就可以学完这本书,没想到,后面的挺吃力,所以就分两周来学,其实在学习后面的同时也是对前面的复习,所以预习 学习 加复习同时进行才是最好的
我们下周见

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值