探秘最小生成树&&洛谷P2126题解

我在这里就讲两种方法

PrimKruscal


Kruscal

kruscal的本质其实是 排序+并查集 ,是生成树中避圈法的推广

算法原理如下

  • (1)将连通带权图G=<n,m>的各条边按从小到大的次序排列,写成E1,E2,···Em,其中E1的权最小,Em的权最大,m为边数。//这就是排序的原因
  • (2)取权最小的两条边E1,E2,构成边的集合T,即T={E1,E2}。从E3起,按次序逐个将边加进集合T中去,若出现回路则将这条边排除(不加进去),按此法一直进行到Em,最后得到n-1条边的集合T={E1,E2,E3,···En-1},则T就是图G的最小生成树。//并查集

如果不会并查集的同学,可以点进去看看
并查集

其中大家可以看到,我的快速排序并没有写cmp,这是因为我用了重载运算符
可以看一看一大佬写的,简单易懂
CSDN 重载运算符

贴代码

#include<bits/stdc++.h>
using namespace std;
int n,m;
struct edge
{
	int to,from,next,v;
	bool operator <(const edge &n)const
	{
		return v<n.v;
	}//重载<符号,排序时要用 
}e[400000+10];
int head[2300+10],ei=0;
inline int add(int x,int y,int z)
{
	ei++;
	e[ei].to=y;
	e[ei].next=head[x];
	e[ei].v=z;
	head[x]=ei;
	e[ei].from=x;
}//前向星模板,萌新们不知道可以去百度一下 
int f[2300+10];//爸爸数组~~~ 
inline int findf(int x)
{
	if(f[x]==0)
	{
		return x;
	}
	f[x]=findf(f[x]);
	return f[x];
}
inline int uion(int x,int y)
{
	x=findf(x);
	y=findf(y);
	if(x!=y)
	{
		f[x]=y;
	}
}//并查集模板 
int ans=0;//答案 
/*int cnt=0;*/
inline int kruscal()
{
	for(int i=1;i<=m;i++)
	{
		int fx=findf(e[i].from);
		int fy=findf(e[i].to);
		if(fx==fy) continue;
		ans+=e[i].v;
		uion(e[i].from,e[i].to);
		/*cnt++; 
		if(cnt==n-1)
		{
			break;
		}
		这就是这道题与P3366的模板的第一个区别
		这道题强调了要重复的
		所以不需要判断 
		*/
	}
}//kruscal模板 
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		x++;
		y++;//这是第二个区别,楼上已经解释的很清楚了,由于题上说了,ai=0表示mzc,会有三个点WA 
		add(x,y,z);
	}
	sort(e+1,e+m+1);//STL库的快速排序,kruscal的惯例 
	kruscal();
	printf("%d",ans);//输出~~~~ 
	return 0; 
}

好,现在我们即将进入prim


Prim

prim也称 逐步短接法 (是不是有点土),本质是搜索,其实有点像最短路问题中的Dijkstra算法,先给出短接的定义:

定义:

设Vi和Vj是无向图G=<V,E>中的任意两顶点,将Vi,Vj合并成一个顶点,记做V',称V'为超点,使得与Vi,Vj关联的边均与V'关联。这种做法称为Vi,Vj的短接

Prim的算法原理如下:

  • (1)设e是G中非环带权最小的边(若带权最小的边不唯一,就任选一个作为e),将e的两端点Vi,Vj短接得起点V'。删除边e(相当于将e作为生成树的树枝)后,所得的图G'中若含有环就删除掉(相当于形成生成树的弦)。//搜索的过程
  • (2)对G'重复(1),直到最后整个图变成一个起点为止。这时共进行n-1次短接,得n-1个树枝,m-n+1条弦。

可以看到,在我的程序中出现了堆排序优化,不懂的同学请戳这里
堆排序

当然,除了我这种堆排序的写法,还有Priority_queue即优先队列的写法,但我测试过,我这种写法,至少快1/3。若果还是不懂我这种写法的,戳这里
优先队列

贴代码

#include<bits/stdc++.h>
using namespace std;
int n,m;
int ans=0;
struct edge
{
	int next,to,v;
}e[400000+10<<1]; 
int head[2300+10],ei=0;
int add(int x,int y,int v)
{
	ei++;
	e[ei].to=y;
	e[ei].next=head[x];
	head[x]=ei;
	e[ei].v=v;
}//与上一方法相同 
struct node
{
	int id,v;
	bool operator<(const node &n)const
	{
		return v>n.v;
	}//堆排序时要用,重载<,使得进去的数上小下大 
}; 
node heap[400000+10];//堆 
int heaplen = 0;  //堆的长度 
int pushHeap(int x,int v)
{
    heap[heaplen].id = x;
    heap[heaplen].v = v;
    heaplen++;
    push_heap(heap,heap+heaplen);
}//入堆 
node popHeap()
{
    pop_heap(heap,heap+heaplen);
    heaplen--;
    return heap[heaplen];
}//出堆 
int used[400000+10];//堆栈优化,不然要炸 
/*int blcnt=0;*/
int prim()
{
	pushHeap(1,0);//先把第一个数和其边权(因为没有下一节点,所以是0) 入堆 
	while(heaplen)//搜索 
	{
		node f1=popHeap(); //出堆并记录顶上的一个数 
		if(used[f1.id]==1)
		{
			continue;
		}
		used[f1.id]=1;
		ans+=f1.v;
		/*blcnt++;
		if(blcnt==n)
		{
			printf("%d",ans);
		}
		和上一个方法一样,不需要判断
		*/
		for(int i=head[f1.id];i;i=e[i].next)//遍历前向星 
		{
			if(used[e[i].to]==0)
			{
				pushHeap(e[i].to,e[i].v);//入堆 
			}
		}
	}
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		x++;
		y++;//同上一种方法 
		add(x,y,z);
		add(y,x,z);//双向存储 
	}
	prim();
	printf("%d",ans);//输出~~~ 
	return 0;
}

现在说一下这道题容易出错的地方

  1. 这道题强调了要重复的,所以不需要判断cnt==n-1时,将循环停掉。可以看一看我的错误WA

  2. 由于题上说了,ai=0表示mzc,所以标记值和此处重复。
    我的错误WA+RE。如某大佬:

但在写的时候遇到了一点bug,以为数据中的人是 有编号为0的 ,那么我的并查集的写法就会 因为标记的值和0重复了而被卡掉 ,所以就 人为的将每一个编号放大1 ,然后就A了


如果有小伙伴们不懂链式前向星这种存储方式,戳这里
链式前向星
如果大家觉得我讲的你不懂,请参考下面这位大佬的讲解
Prim和Kruscal


最后推荐几道题:

P1119 灾后重建

P3366 【模板】最小生成树

P1195 口袋的天空


最后,衷心祝愿每一个人都能实现自己的梦想,得到省一

理想的梦, 
希望的梦, 
希望中, 
那理想的梦, 
像一幅春天的画卷, 
在不懈的期盼中, 
悄悄的在梦中闪现。 

阻挠, 
蔑视, 
肆意的嘲笑, 
还有那狂妄的***难, 
这一刻, 
像这冬日的寒风, 
飘到了九霄云外。 

理想的梦, 
生命中的梦, 
生命中, 
那激情的火焰, 
像冬日燃烧的枯草, 
在寒风中熊熊的燃烧。 

燃烧中, 
我恍然站在了那泰山之巅, 
遥望起了那远方的苍海云天。 

遥望中, 
东方升起了一轮红日, 
这红日是如此的绚丽, 
如此的闪耀。 

闪耀中, 
一阵细雨, 
突然飘来。 

雨中的我, 
恍然如梦。

http://uploads.oh100.com/allimg/1707/121-1FG4163055-50.jpg

posted @ 2019-03-26 22:29  nth_element  阅读(316)  评论(0编辑  收藏  举报