도 최소한도의 스패닝 트리 트리 최소 스패닝.

최소 스패닝 트리

최소 스패닝 트리

지도를 위해, 우리는 정리가 : n은 N-1면에 연결된 점을, 패턴은 트리를 형성 할 수있다. 우리는 이런 식으로 해석 할 수 있습니다 : 트리의 각 노드는 고유 한 아버지가, 즉, 그래서 N-1 가장자리, 최소한 n 가장자리하지만, 루트 노드를 제외하고있다. 이해가있다 : 존재하지 않는 나무 반지, 단지 N-1면과, 고리를 형성 할 수 n 개의 점을 연결하는 것이 필요하다.

그래서, n 개의 점에 접속되어있는 상기 N-1 측부를 사용하는 가중 된 그래프의 N 포인트 스패닝 트리를 들면 다음 최소 스패닝 트리가 N-1면의 우측 및 최소이며 이해할 간단한 프로그램의 종류는, 본 도면을 사용되도록 오직 N-1 측부 에지 가중치 N-1면의 합을 최소화. 아래 그림과 같이 :

최소 스패닝 트리도이를 빨간색 가장자리에, 우리는 현재 최소 스패닝 트리 감독 그래프를 논의 있습니다.


나무를 구축하기로 최소 스패닝 트리 과정, 우리는 이해 될 수있다. 각면은 가능한 한 작은 최소 스패닝 트리를 보자, 그래서 우리는이 알고리즘에 해당하는 두 개의 아이디어를 가지고 : 최소 금액의 오른쪽에, 우리는 탐욕 생각 될 수있다 생각할 수 있습니다 :

(1) 크루스 칼 :

각 모서리의 오른쪽이 작고, 및 연결 가장자리의 가장자리 가중치의 합은 작아야 것을 우리는 쉽게 욕심 전략의 한 종류 생각할 수 있습니다. 그래서 우리는 선택의 최소 기대와 선택, 우리는 N-1 모서리를 선택할 때까지 ...... 셋째, 가장 작은 다음 작은을 선택할 수 있습니다. 따라서, 우리는 올바른 종류의 이러한 측면 가장자리를 따라, 다음면을 선택하기 시작 할 수 있습니다. 왜의 측면을 선택? 작은 더 나은면, 왜 선택하지?

작은 에지 무게는 좋다, 그러나 우리는 전제를 잊지 마세요 : 우리가 나무를 구축하고자, 그것은 반지 안쪽 존재할 수 없습니다. 내가 가장자리를 볼 수 있지만, 경우 즉,이 날 내가 나무가 연결되어 내장 된 두 지점에 부착하고,이 가장자리도에 추가 할 필요가? 분명히 없습니다. 우리가 특정 작업에 대한지도를 살펴보고,이 조금 이해할 수있다 말 :

큰 (노드 접속점의 우측)에 작은 쪽의 고객 주문을 주문 후 :

①1 2 1②1 3 1③4 6 3④5 6 4⑤2 6⑥4 3 4 5 7⑦3 9⑧2 11⑨3 4 5 13

최소 스패닝 트리를 구축하려면이 성장하기 시작 :

: 다음과 같이 우리의 작업은 나무 리가, 동작의 측면에있다

지금까지, 모든 것이 잘되어 가고있다. 우리는 다섯 번째 측면 (236) A로 이동하십시오. 우리는 우리가 236에 수이 시간에 우리 나무의 가장자리에 추가 할 수없는 릴레이로로 2, 3, 연결되어 있음을 발견했다. 4 및 5가 접속되어 있기 때문에 유사하게, 동일한 수의 ⑥의 하부 가장자리는 스킵. 에 도시 된 바와 같이 다음으로, 우리는이 날 349을 추가 :

우리는 우리가 불과 5 측면으로 연결 된 트리의 각 노드에 내장 된 것을 발견, 즉, 우리는 N-1면 말 이 알고리즘은 종료됩니다.

알고리즘의 구현은 가장 어려운 두 연결 여부를 확인하는 방법은, 어려운 일이 아니다. 분리 된 세트 그러므로, 우리는 강력한 데이터 구조의 도움이 필요, 우리는 해결하기 깊이 또는 전체 검색을 검색 할 수 있지만 효율성이 매우 낮다는 것을 알 수있다. 그리고 가장 강력한 기능 세트의 조사가 신속하게 동일한 컬렉션 (조상이 같은) 두 가지 요소가 있는지 여부를 확인하는 기능입니다, 그래서 우리는 두 지점이 연결되어 있는지 여부를 확인하는 데 사용합니다.

주요 코드 :

struct edge
{
    int u,v,w;
}a[100001]; //边集数组 int boss[10001];//并查集,boss[i]表示i的祖先 int find(int x) { if(boss[x]==x)return x;//找到祖先 else { boss[x]=find(boss[x]);//路径压缩 return boss[x]; } } void Kurscal() { int i; for(i=1;i<=n;i++)boss[i]=i;//初始化 //n个结点,每个结点的祖先默认为它自己,也就是每个结点自己一个集合 stable_sort(a+1,a+1+m,cmp);//m条边,将边按照边权从小到大排序 int cnt=0;//当前最小生成树里边的数量 int len=0;//当前最小生成树边权总和 for(i=1;i<=m;i++) {); int x=find(a[i].u),y=find(a[i].v //x表示a[i].u的祖先,y表示a[i].v的祖先 if(x!=y) //说明两点不在同一集合内,即这两点不连通 { boss[x]=y;//标记祖先 cnt++;//边数增加 len+=a[i].w;//边权和增加 } if(cnt==n-1)break; //如果已经选了n-1条边,那最小生成树就建好了 } }

(2) 프림 :

면을 추가하여 성과에 더하여, 우리는 밖으로 다른 방법이 없다? Kurscal은, 우리가 우위를 추가, 우리의 고정 촬영 포인트의 경우에 이루어집니다. 그것은 우리가이 노드에 대해 걱정하지 않는다이 때, 우리는 자신의 연결면 걱정입니다. 음, 우리는 (이 노드에 대해 관심이 없다비 하강)? 이 때, 그 과정에서 우리의 성과는 가장자리를 추가하지 않고, 포인트를 추가 할 수 있습니다. 그 시작은, 우리 나무 (약간 당황) 먼 길이어야한다 :

각 노드와 마지막으로 통신이 각각 두 사람은 어느 루트로는 문제가되지 않습니다 때문에 우리는 먼저 루트를 찾아야 시작, 루트 노드는 모든 노드가 될 수 있습니다. 그래서, 우리는 지점을 선택해야 할 때마다 최소 스패닝 트리에 참여하려면 어떤 조건이 시점 그것을 충족해야? 그것은 사실, 각각의 거리 "작은 발견되는 이차 말했다 트리에서 가장 가까운 거리, 최소의 가장 가까운 노드에서 각각의 거리에서 트리의 각 노드에 현재 노드까지의 거리 있어야합니다 트리 "가장 가까운 노드입니다. 우리는 위의 예를 에뮬레이션 여전히 :

첫째, 포인트 1 뿌리, 나무 (지점 1)에서이 시간이 가장 최근의 2시입니다, 우리는 호에게 2시 트리에 참여합니다 :

가장 가까운 트리 노드에서 다음, 우리가 3 점 트리를 추가 할 예정, 3 점입니다 :

수동으로 시뮬레이션 다음 단계 리더, 그 아이디어의 진정한 의미에 수동으로 만 몰입 감정 시뮬레이션 알고리즘 (빗나가 다

加入点的顺序应该是:1->2->3->4->5->6。注意我们每次选点的时候要选的是树以外的结点,否则一开始就会出现一种非常尴尬的局面:第一轮,找结点,发现根结点到根结点距离为0,选择根结点;第二轮,发现根结点到根结点距离为0,又选根结点......这就陷入了死循环。对于已经在树里的结点,我们是没有必要再去动它了,因为我们的目的就是将所有点插入到树里,那你已经在树里的我还管你干什么?所以我们需要有一个数组来记录结点是否已经在树里。

在选点的过程中,我们需要按照上面说的一大串话那样,比较树外的每一个结点到树上的每一个结点的距离吗?我们在那下面说了:“实际上说的就是每一次要找到一个距离“最小生成树”最近的结点。”我们有没有办法来记录每一个结点到最小生成树的距离呢?当然有!我们可以开一个数组dis,有没有发现,和我们的最短路算法中的Dijkstra算法有异曲同工之妙?不懂得可以看一下最短路算法分析。这两种算法都是不断加入点进行拓展,从而得出整张图的最短路或最小生成树。Dijkstra中dis表示的是结点到源点的距离,这里就是把源点扩大成了一棵树,其思想并没有任何改变,我们仍旧可以把那棵树当作一个点来看待。那么,在加入点后,我们需要用这个点来刷新一下其它非树结点(不在树上的结点)到树的距离,这和Dijkstra的松弛是一模一样的!令人赞叹的是,这两种算法解决的问题不同,它们的过程竟然完全一样!

主要代码:

struct edge
{
    int last,to,len;
}a[100001]; int first[10001],len=0; //邻接表 bool f[10001];//记录是否在树上 int dis[10001];//记录结点到树的距离 void add......//存边 void prim() { int i; for(i=1;i<=n;i++)dis[i]=999999;//初始化 int cnt=0;//树内点的数量 int sum=0;//树内边权总和 dis[1]=0; f[1]=1; cnt=1; //先确定根结点,一般以1作为根结点 while(cnt<n)//直到n个结点均在树上 { int id,minn=1000001; //id记录找到的结点的编号,minn是它到树的距离 for(i=1;i<=n;i++) if(f[i]==0&&dis[i]<minn) { id=i; minn=dis[i]; } f[id]=1; cnt++; //将这个点加入树 sum+=dis[id]; //刷新边权总和 for(i=first[id];i;i=a[i].last) //刷新结点到树的距离 if(f[a[i].to]==0&&a[i].len<dis[a[i].to]) dis[a[i].to]=a[i].len; } }

总结一下两种算法:Kurscal算法是将森林里的树逐渐合并,prim算法是在根结点的基础上建起一棵树。

可能有的同学会误解:dis代表结点到树的距离,那这个距离一定只包含一条边吗?在这里,距离只能有一条边。为什么呢?我们每一次是要往树里加一个点的,那如果这个距离经过了不止一条边,那就不满足我们的需求了。这一点要和Dijkstra区别开,Dijkstra是单纯的距离,而prim是只经过一条边的距离。这样的话,即使在存在负边权,求得的dis不是真正意义上的最短距离,也不会影响我们最终的结果。

我们的过程实际上是每一次添加一个点,然后逐渐建起一棵树,我们并不是真的希望这个点到我们的树是最近的,我们只希望这个点加入我们的最小生成树后可以满足我们贪心的要求:局部最优导致整体最优,这个局部指的是我们最小生成树的边权,而并不是真正意义上的距离。这一点一定要好好理解!

同样,prim算法也可以堆优化,那么堆里存的就是结点的编号和它到树的距离,和Dijkstra的堆优化基本一样,希望读者自己尝试去实现。


因为Kurscal涉及大量对边的操作,所以它适用于稀疏图;普通的prim算法适用于稠密图,但堆优化的prim算法更适用于稀疏图,因为其时间复杂度是由边的数量决定的。

추천

출처www.cnblogs.com/Leo_wl/p/10993141.html