最小生成树
最小生成树简称MST(Minimum Spanning Tree),最小生成树算法有Prim算法和Kruskal算法,prim算法其实就是在dijkstra算法上面稍微修改一哈,当然也可以加heap优化,而Kruskal就有点意思了,因为这个算法在解决判断环的问题
的时候用了并查集。还是先来书上的描述
书上的代码:
说实话这个并查集用的是真的溜~
poj2421 poj1287 poj2031
这两道题比较基础,用prim和kruskal都可以做出来,但是这两个算法在效率上有点小区别,kruskal要稍微快一些
就拿poj2421 来说,直接用图说话
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef struct Edge{
int sour, dest, value;
}Edge;
Edge edge[105*105];
int length, k = 0, shuzu[105][105], answer = 0;
int parent[105], rank[105];
bool cmp(Edge a, Edge b){
return a.value < b.value;
}
void addedge(int sour, int dest, int value){
edge[k].sour = sour;
edge[k].dest = dest;
edge[k].value = value;
k ++;
}
int find(int input){
if(input == parent[input])
return input;
else
return parent[input] = find(parent[input]);
}
void unite(int x, int y){
x = find(x);
y = find(y);
if(x == y) return;
if(rank[x] < rank[y]){
parent[x] = y;
}else{
parent[y] = x;
if(rank[x] == rank[y]) rank[x] ++;
}
}
void kruskal(){
sort(edge, edge+k, cmp);
for(int i = 0; i < k; i++){
int sour = edge[i].sour, dest = edge[i].dest, value = edge[i].value;
if(find(sour) == find(dest)) continue;
unite(sour, dest);
answer += value;
}
cout << answer << endl;
}
int main(){
scanf("%d", &length);
for(int i = 1; i <= length; i++){
for(int j = 1; j <= length; j++){
scanf("%d", shuzu[i] + j);
}
}
int jishu, x, y;
scanf("%d", &jishu);
for(int i = 0; i <= length; i++) parent[i] = i, rank[i] = 0;
for(int i = 0; i < jishu; i++){
scanf("%d%d", &x, &y);
shuzu[x][y] = shuzu[y][x] = 0;
unite(x, y);
}
for(int i = 1; i <= length; i++){
for(int j = 1; j <= length; j++){
if(shuzu[i][j]) addedge(i, j, shuzu[i][j]);
}
}
kruskal();
return 0;
}
poj2349 poj3241这两道题要稍微绕一哈,第一道题还好,但是第二道题有点小东西
这就主要说第二题,第二题确实就是要求MST,然后根据要分成几组,找最小生成树中的第几个大的数。就跟poj2349一样,而且人家poj2349一哈就跑出来了,但是poj3241就没得那么顺利了,我甚至都有点怀疑是不是不该用MST,然后我又想是不是输入出了问题,就像前面最短路径有道题一样,那道Ferry(poj3377)就是我输入long long
出的问题,但是这道题我就是用的int输入的嘛,那到底是啥子问题喃? 说出来你可能不得行,只是因为数组的问题,因为我储存a和b的值的时候用的是一个二维数组,你肯定会想用二维数组又咋了嘛,对的三~,错的!因为寻址的问题,二维数组的本质就是一维数组,如果频繁的用二维数组的话,就要出事了。我这就有证据的,因为如果我把这个二维数组变成用结构体存储的话就过了,而且是险过。
但是现在又出了一个问题,那就是我偶然发现了一个效率更高的方法,那就是拿到所有的a和b,把他们全部排序,然后效率更是截然不同
关键是按理来说拍了序应该没得啥子区别才对三,因为循环那些都是要走的三,又不可能说排了序就要少些循环,我还在研究。。。
POJ1751
这道题其实和上面的题也很像,也是求MST,而且也是会提前给出一些边已经连接了,然后喊你输出要连接哪些边,我第一反应就是kruskal,因为并查集是肯定要用的,因为已经有一些边连接了,在后面求MST的过程中很有可能连接到边会和之前题目给出的变形成环,所以并查集是必须的,然后我用kruskal就超内存了,我举得这道题就是故意不然你用kruskal,可能故意就是要让你用prim来解决,而且用prim的时候还有个小问题。
prim再求MST的时候和dijkstra是一样的,只不过prim只用比单独的一条边而dijkstra要是把前面的边都加起来,而基本流程就是先根据上一次找的那个最小的点A
来更新其他的边,然后再从中找出最小的那个点B
(在这道题中我为了效率就合并了),然后就通过并查集判断点A 和 B
是否会形成环,如果不会就打印。 乍一看好像就是那么回事,但是这就有个致命问题,虽然点B
是在点A
更新边的过程找到的,但是有个能这次更新中点A
没有更新到点B
,而点B
确实有前面某个早就确定的点更新的。
解决方法可以通过最短路径中学到的path来记录,我在这道题中是也是这么做的只不过我用的名字的是from数组
还有个问题就是关于Double类型初始化为inf
,如果这样来:
#define inf = 0x7F7F7F7F;(初始化double的时候用0x3F3F3F3F就不好了)
double temp1 = inf;
int a = inf;
printf("%d\n%f", a, temp1);
double temp2;
memset(&temp2, 0x7F, sizeof(temp2));
cout << temp2 << endl;
double
是8个字节,long
是4个字节,long long
也是8个字节,那为啥子这里 不传0x7F7F7F7F7F7F7F7F
喃?首先double的存储并不像long long
直接就可以通过十六进制转成十进制,double之所以可以存那么大的数,是因为他是的结构比较特殊,可以参考这篇博客
Double_Float的存储结构,所以上面两个例子就是因为编译器没有把0x7F7F7F7F
直接送到double
存储的的那八个字节中,而是根据double的特殊结构是的最后转化为十进制的值为0x7F7F7F7F
#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
using namespace std;
#define maxn 753
typedef struct Node{
double x, y;
}Node;
Node input[maxn];
double inf ;
double shuzu[maxn][maxn];
int length;
int parent[maxn], rank[maxn], from[maxn];
double tag[maxn];
bool flag[maxn];
int find(int input){
if(input == parent[input])
return input;
else
return parent[input] = find(parent[input]);
}
void unite(int x, int y){
x = find(x);
y = find(y);
if(x == y) return ;
if(rank[x] < rank[y]){
parent[x] = y;
}else{
parent[y] = x;
if(rank[x] == rank[y]) rank[x] ++;
}
}
void prim(){
memset(tag, 0x7f, sizeof(tag));
memset(flag, 0, sizeof(flag));
memset(from, 0, sizeof(from));
int father, biaoji = 0, dangqian, index = 1;
double zuixiao;
tag[1] = 0; flag[1] = 1;
for(int cishu = 0; cishu < length; cishu ++){
zuixiao = inf, dangqian = -1;
for(int i = 1; i <= length; i++){
if(flag[i]) continue;
//better
if(shuzu[i][index] < tag[i]) tag[i] = shuzu[i][index], from[i] = index;
if(zuixiao > tag[i]) zuixiao = tag[i], dangqian = i, father = from[i];
}
if(dangqian == -1) return ;
if(find(father) != find(dangqian)){
printf("%d %d\n", father, dangqian);
biaoji = 1;
}
unite(father, dangqian);
index = dangqian;
flag[index] = 1;
}
if(biaoji == 0) cout << "" ;
}
int main(){
memset(shuzu, 0x7f, sizeof(shuzu));
inf = shuzu[0][0];
scanf("%d", &length);
double r;
for(int i = 1; i <= length; i++){
scanf("%lf%lf", &input[i].x, &input[i].y);
for(int j = 1; j < i; j++){
r = sqrt(pow(input[i].x-input[j].x,2) + pow(input[i].y-input[j].y,2));
if(shuzu[i][j] > r) shuzu[i][j] = shuzu[j][i] = r;
}
}
int m, x, y;
scanf("%d", &m);
for(int i = 1; i <= length; i++) parent[i] = i, rank[i] = 0;
for(int i = 0; i < m; i++){
scanf("%d%d", &x, &y);
shuzu[x][y] = shuzu[y][x] = 0;
unite(x, y);
}
prim();
return 0;
}
poj1679
这道题我第一反应就是这应该是一道上档次的题,因为老实讲MST的时候也说过最小生成树是不唯一的,这道题就是来判断这个东西,我的第一感觉就是之所以MST不是唯一的及时因为有环的情况而且起码有两条边相同的情况,所以我就想用kruskal,然后再用并查集判断是否存在环的时候做文章,但是越想越复杂,因为这个环中可能还有其他的小环,而且还有可能一个点连了几个环,这就有点恼火了,因为还要区别是哪个环中得边,确实最后就放弃这种高大上的办法了,然后只有用最暴力的枚举,也就是每个点挨到挨到的用prim,然后判断最小生成树的边有没有不同的,我一直有点担心这会超时,但是还好。。
#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
#define maxn 102
typedef struct Node{
int sour, dest;
long long value;
Node(int s, int d, long long v){sour = s; dest = d; value = v;}
bool operator < (Node const &a) const{
return value > a.value;
}
}Node;
int v, length;
long long answer;
long long shuzu[maxn][maxn];
long long tag[maxn];
bool flag[maxn], compare[maxn][maxn], zhanshi[maxn][maxn];
bool prim(bool panduan, int start){
memset(tag, 0x3f, sizeof(tag));
memset(flag, 0, sizeof(flag));
memset(zhanshi, 0, sizeof(zhanshi));
priority_queue<Node> que;
que.push(Node(0, start, 0));
while(!que.empty()){
Node node = que.top(); que.pop();
int index = node.dest, sour = node.sour;
long long temp = node.value;
if(flag[index]) continue;
flag[index] = 1;
tag[index] = temp;
zhanshi[sour][index] = zhanshi[index][sour] = 1;
answer += temp;
for(int i = 1; i <= v; i++){
if(flag[i]) continue;
if(tag[i] > shuzu[index][i]) que.push(Node(index, i, shuzu[index][i]));
}
}
if(panduan){
for(int i = 1; i <= v; i++){
for(int j = 1; j <= v; j++){
compare[i][j] = zhanshi[i][j];
}
}
return true;
}else{
for(int i = 1; i <= v; i++){
for(int j = 1; j < i; j ++){
if(compare[i][j] != zhanshi[i][j]) return false;
}
}
return true;
}
}
int main(){
int jishu, x, y;
long long vv;
scanf("%d", &jishu);
while(jishu --){
memset(shuzu, 0x3f, sizeof(shuzu));
memset(compare, 0, sizeof(compare));
scanf("%d%d", &v, &length);
for(int i = 0; i < length; i++){
scanf("%d%d%I64d", &x, &y, &vv);
shuzu[x][y] = shuzu[y][x] = vv;
}
answer = 0;
prim(true, 1);
int biaoji = 0;
for(int i = 2; i <= v; i ++){
answer = 0;
if(!prim(false, i)) {biaoji = 1; cout << "Not Unique!" << endl; break;}
}
if(biaoji == 0) cout << answer << endl;
}
return 0;
}