连同n个点最少需要n - 1条边,但是它给你的边数大于n - 1 呢你怎么才能连出一个最短的路呢,这就是最小生成树可以解决的问题,你要获得所有边的关系以及边的长度,接下来可以有两种思路一种事kruskal算法
是从最小的便开始连点,连完两个点后用并查集将这两个点视为连同状态,接着去找下一条最短的边,判断两个人不是一个老大的话,就再连接起来。这样最终就能找到一个最短的生成树了,且绝对没有环(因为你的并查集)
先来看看常用的并查集代码(找老大,判老大,连老大)
int Find(int x)
{
if(x == father[x])return x;
return father[x] = Find(father[x]);//一定得带回去
}
void join(int x,int y)
{
int u = Find(x);
int v = Find(y);
if(u == v)return ;
father[u] = v;
}
bool same(int x,int y)
{
return Find(x) == Find(y);
}
接下来长贵的输之后开始调用kruskal算法
long long res = kruscar(len);
进入kruscal算法首先第一步是不是要排序(因为我的结构体李存放了所有的边,而kruscal是从找最小边为开始的,所以排序后,我会一条一条的找和连)
sort(edge + 1, edge + 1 + len,cmp);
接下来,对于每一条边的起点和终点我都会进行是否在一个集合中的判断(是不是同一个老大)
只有不是同一个老大,也就是这两个点并没有互通互联,才能够被连接
if( !same(edge[i].from,edge[i].to))
{
//cout<<"cs"<<endl;
join(edge[i].from,edge[i].to);
//cout<<edge[i].from<<" "<<edge[i].to<<endl;
res += edge[i].cost;
}
至于在循环之中还要做什么,就得看相应的题目要求来进行优化了,比如有的题目会要求输出最短路,那就定义一个pre[u] = v
数值表示u的点前连v这个点
一下是全码
#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <algorithm>
using namespace std;
#define Max 1000
struct node{
int from,to,cost;
}edge[Max * Max];
int father[Max];
bool cmp(node a,node b)
{
return a.cost < b.cost;
}
void init()
{
for(int i = 1;i <= Max;i++)
{
father[i] = i;
}
}
int Find(int x)
{
if(x == father[x])return x;
return father[x] = Find(father[x]);//一定得带回去
}
void join(int x,int y)
{
int u = Find(x);
int v = Find(y);
if(u == v)return ;
father[u] = v;
}
bool same(int x,int y)
{
return Find(x) == Find(y);
}
long long kruscar(int len)
{
long long res = 0;
sort(edge + 1, edge + 1 + len,cmp);
for(int i = 1;i <= len;i++)
{
if( !same(edge[i].from,edge[i].to))
{
//cout<<"cs"<<endl;
join(edge[i].from,edge[i].to);
//cout<<edge[i].from<<" "<<edge[i].to<<endl;
res += edge[i].cost;
}
}
return res;
}
int main()
{
int t;
while(scanf("%d",&t) != EOF)
{
getchar();
if(t == 0)break;
int len = (t * t - t) / 2;
init();
for(int i = 1; i <= len;i++)
{
scanf("%d %d %d",&edge[i].from,&edge[i].to,&edge[i].cost);
getchar();
}
long long res = kruscar(len);
cout << res <<endl;
}
return 0;
}
然后Prim算法是针对的点,因为是对点寻边,那么,将一个点所外连的边联系起来,就有了选择,比较好理解的就是用二维动态结构体数组vector<node> v[Max];
v[i][j] ——对于第i点一共有v[i].size()条边,这样for遍历一下,第i点所有的边都会进入优先队列之中
struct edge{
int to;
int cost;
edge (int ito,int icost) : to(ito),cost(icost){}
edge(){};
friend bool operator < (edge a,edge b)
{
return a.cost > b.cost;//小的优先级高
}
};
vector<edge> E[Max];
这里提一下优先队列,虽然树对点寻边,但是出队列的肯定还是最短的边,因为我们要做的是最小生成树啊~~,所以优先对列就很好用了
然后对于结构体内置的edge 函数,这个我目前也不大明白,所知道的就是便于对动态二维结构体数组赋值
for(int i = 1;i <= len;i++)
{
int from,to,cost;
scanf("%d %d %d",&from,&to,&cost);
E[from].push_back(edge(to,cost));//利用结构体内部的函数赋值;否则只能用E[i].push_back(结构体来赋值)
E[to].push_back(edge(from,cost));
}
不用你去新定义一个结构体遍量,然后赋初值,在push_back()到结构体数组中去了
那么就开始prim算法啦
先找第一个你想找的随机点spoint
for(int i = 0;i < E[spoint].size();i++)
{
//cout<<E[spoint][i].to<<" "<<E[spoint][i].cost<<endl;
q.push(E[spoint][i]);
}
把与第一个点spoint有关的边全push到优先队列中去,然后出队列,入队列 ……
while(!q.empty())
{
edge edgenow = q.top();
q.pop();
if(!vis[edgenow.to])
{
vis[edgenow.to] = 1;
//cout<<edgenow.to <<" cost = "<<edgenow.cost<<endl;
n++;
res += edgenow.cost;
if(n == t)break;
for(int i = 0;i < E[edgenow.to].size();i++)
{
//cout<<E[edgenow.to][i].to<<" "<<E[edgenow.to][i].cost<<endl;
q.push(E[edgenow.to][i]);
}
}
}
这之间不要忘记优化你的vis数组,这个vis[]就相当于kruskal算法中并查集判断有没有连通的方法
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <queue>
#include <vector>
using namespace std;
#define Max 1000
int t;
struct edge{
int to;
int cost;
edge (int ito,int icost) : to(ito),cost(icost){}
edge(){};
friend bool operator < (edge a,edge b)
{
return a.cost > b.cost;//小的优先级高
}
};
vector<edge> E[Max];
int vis[Max];
priority_queue<edge> q;
void init()
{
memset(vis,0,sizeof(vis));
for(int i = 1;i <= t;i++)
{
E[i].clear();
}
while(q.size())q.pop();
}
long long prim(int spoint)
{
long long res = 0;
for(int i = 0;i < E[spoint].size();i++)
{
//cout<<E[spoint][i].to<<" "<<E[spoint][i].cost<<endl;
q.push(E[spoint][i]);
}
vis[spoint] = 1;
int n = 1;
while(!q.empty())
{
edge edgenow = q.top();
q.pop();
if(!vis[edgenow.to])
{
vis[edgenow.to] = 1;
//cout<<edgenow.to <<" cost = "<<edgenow.cost<<endl;
n++;
res += edgenow.cost;
if(n == t)break;
for(int i = 0;i < E[edgenow.to].size();i++)
{
//cout<<E[edgenow.to][i].to<<" "<<E[edgenow.to][i].cost<<endl;
q.push(E[edgenow.to][i]);
}
}
}
return res;
}
int main()
{
while(scanf("%d",&t) != EOF && t)
{
int len = (t * t - t) / 2;
init();
for(int i = 1;i <= len;i++)
{
int from,to,cost;
scanf("%d %d %d",&from,&to,&cost);
E[from].push_back(edge(to,cost));//利用结构体内部的函数赋值;否则只能用E[i].push_back(结构体来赋值)
E[to].push_back(edge(from,cost));
}
long long res = prim(1);
printf("%lld\n",res);
}
return 0;
}
第二个方法是用两个一位数组实现二维动态结构体数组——链式前向星
别管这个可怕的名字了
先来看看第一个一维结构体数组
struct edge{
int to;
int cost;
int reid;
friend bool operator < (edge a,edge b)
{
return a.cost > b.cost;
}
}e[Max];
可以看出来,prim算法是要用优先队列的(因为这个很方便);这是一维结构体数组
里面定义的to和cost都很好说,那么reid是干什么用的呢,不如先来看看第二个一位数组
int cnt,id[Max];
很明显,这两个之间有关系,在赋值的时候就能体现出来
int init(int len)
{
cnt = 0;
for(int i = 0;i < Max;i++)
{
id[i] = -1;
}
while(q.size())q.pop();
memset(vis,0,sizeof(vis));
memset(e,0,sizeof(e));
}
void add(int from,int to,int cost)
{
e[cnt].cost = cost;
e[cnt].to = to;
e[cnt].reid = id[from];
id[from] = cnt++;
}
for(int i = 0;i < len ;i++)
{
int from,to,cost;
scanf("%d %d %d",&from,&to,&cost);
add(from,to,cost);
add(to,from,cost);
}
初始化的时候一位数组id中的各个项都被赋值为-1——这是一个结束标志
然后cnt赋值为0,在add函数中你可以看出,我输入的每一条边的下标都是cnt且是递增的,那么我怎么才能把起点都为1的边联系起来的,那就是id数组的作用了
add函数中id数组的下标都是from也就是根据起始点来进行分类id[from] = cnt++;如果这一个不好懂的话,那就拆开来看id[from] = cnt ;
cnt++;
你可以做一个假设,输入了很多from都是1的边,这是最后一条了,后面还会有输入,但是from都不是1了,所以当前这最后一条起始点为1的边的下标就被保存到了id[from]数组中了,也就是id[i]是一个目录索引,通过id你可以找到有关1这个点的边的链表的head坐标,其实我觉得链式前向行真的就类似多个链表~~
id[i]的作用可以帮你找到起点,那么中间各个边的关系链就是结构体数组中的reid的作用了
e[cnt].reid = id[from];
这里的id【from】还没有更新,所以代表的是这个点的链表还没有往下加结点前的head下标,记录了这个下标,连上了节点,然后更新了id[from]的head…………
总之我是这么去理解链式前向星的(链式),肯定会有更多更好的理解方法,也欢迎大家交流互进
接下来开始找第一个点的所有相关边
for(int i = id[point];~i;i = e[i].reid)//-1取反是0就结束了
{
q.push(e[i]);
}
先得到head下标 i = id[point] 然后push进去,再通过head往下找i = e[i].reid,e[i].reid是中间的链子,最后结束的时候就是连向-1的时候了
连完第一个点的后续push入队出队也就不难了
while(sum < n)
{
while(q.size())
{
edge now = q.top();
q.pop();
if(!vis[now.to])
{
vis[now.to] = 1;
res += now.cost;
sum++;
for(int i = id[now.to]; ~i;i = e[i].reid)
{
q.push(e[i]);
}
}
}
}
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
int t;
const int Max = 1000;
int vis[Max];
struct edge{
int to;
int cost;
int reid;
friend bool operator < (edge a,edge b)
{
return a.cost > b.cost;
}
}e[Max * Max];
priority_queue<edge>q;
int cnt,id[Max];
int init(int len)
{
cnt = 0;
for(int i = 0;i < Max;i++)//更新的话要确保全部!!!
{
id[i] = -1;
}
while(q.size())q.pop();
memset(vis,0,sizeof(vis));
memset(e,0,sizeof(e));
}
void add(int from,int to,int cost)
{
e[cnt].cost = cost;
e[cnt].to = to;
e[cnt].reid = id[from];
id[from] = cnt++;
}
long long prim(int point,int n)
{
int sum = 1;
long long res = 0;
for(int i = id[point]; ~i ;i = e[i].reid)//-1取反是0就结束了
{
q.push(e[i]);
}
vis[point] = 1;
while(sum < n)
{
while(q.size())
{
edge now = q.top();
q.pop();
if(!vis[now.to])
{
vis[now.to] = 1;
res += now.cost;
sum++;
for(int i = id[now.to]; i != -1;i = e[i].reid)
{
q.push(e[i]);
}
}
}
}
return res;
}
int main()
{
while(scanf("%d",&t) != EOF && t)
{
getchar();
int len = (t * t - t) / 2;
init(t);
for(int i = 0;i < len ;i++)
{
int from,to,cost;
scanf("%d %d %d",&from,&to,&cost);
getchar();
add(from,to,cost);
add(to,from,cost);
}
long long res = prim(1,t);
printf("%lld\n",res);
}
return 0;//这个竟然忘了!!!terminate called after throwing an instance of 'std::bad_alloc
}