一、最小生成树
算法
最小生成树(Minimum Spanning Tree,MST)是在一个给定的无向图G(V,E)中求一棵树T,使得这棵树拥有图G中的所有顶点,且所有边都是来自图G中的边,并且满足整棵树的边权之和最小。
求解最小生成树一般有两种算法,即prim算法与kruskal算法。这两个算法都是采用了贪心法的思想,只是贪心的策略不太一样。
prim算法
其基本思想是对图G(V,E)设置集合S,存放已被访问的顶点,然后每次从集合V-S中选择与集合S的最短距离最小的一个顶点(记为u),访问并加入集合S。之后,令顶点u为中介点,优化所有从u能到达的顶点v与集合S之间的最短距离。这样的操作执行n次(n为顶点个数),直到集合S已包含所有顶点。可以发现,prim 算法的思想与最短路径中Djkstra算法的思想几乎完全相同,只是在涉及最短距离时使用了集合S代替Dijkstra算法中的起点s。
代码如下。
//最小生成树-prim算法
#include<iostream>
#define N 105
#define INF 0x3fffffff
using namespace std;
int g[N][N],dis[N];
bool vis[N];
void init(int n)
{
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j]=INF;
}
g[i][i]=0;
vis[i]=false;
dis[i]=INF;
}
}
int prim(int s,int n)
{
int ans=0;
dis[s]=0;
for(int i=1;i<=n;i++){
int u=-1,MIN=INF;
for(int j=1;j<=n;j++){
if(vis[j]==false&&dis[j]<MIN){
MIN=dis[j];
u=j;
}
}
if(u==-1) return -1;
vis[u]=true;
ans+=dis[u];
for(int v=1;v<=n;v++){
if(vis[v]==false&&g[u][v]!=INF&&g[u][v]<dis[v]){
dis[v]=g[u][v];
}
}
}
return ans;
}
int main()
{
int m,n;
while(cin>>m&&m!=0){
cin>>n;
init(n);
while(m--){
int a,b,c;
cin>>a>>b>>c;
if(g[a][b]>c) g[a][b]=g[b][a]=c;
}
int ans=prim(1,n);
if(ans==-1) cout<<"?\n";
else cout<<ans<<"\n";
}
return 0;
}
kruskal算法
从图中不断的选择路径最小的边进行归并,知道包含所有的节点。
其基本思想为:在初始状态时隐去图中的所有边,这样图中每个顶点都自成一个连通块。之后执行下面的步骤:
- 对所有边按边权从小到大进行排序。
- 按边权从小到大测试所有边,如果当前测试边所连接的两个顶点不在同一个连通块中,则把这条测试边加入当前最小生成树中;否则,将边舍弃。
- 执行步骤2,直到最小生成树中的边数等于总顶点数减1或是测试完所有边时结束。而当结束时如果最小生成树的边数小于总顶点数减1,说明该图不连通。
代码如下。
//最小生成树-kruskal算法
#include<iostream>
#include<string.h>
#include<algorithm>
#define M 105
#define INF 0x3fffffff
using namespace std;
int father[M];
bool isin[M][M];
struct edge{
int u,v;//边的两个断点
int cost=0;//花费
}Edge[M];
bool cmp(edge e1,edge e2)
{
return e1.cost<e2.cost;
}
int find(int x)
{
if(x==father[x]) return x;
else{
return find(father[x]);
}
}
int Kruskal(int n,int cnt)//n为顶点个数,cnt为边的个数
{
int ans=0,edge_cnt=0;
for(int i=1;i<=n;i++) father[i]=i;
sort(Edge,Edge+cnt,cmp);//从小到大排序
for(int i=0;i<cnt;i++){
int fu=find(Edge[i].u);
int fv=find(Edge[i].v);
if(fu!=fv){
father[fv]=fu;
ans+=Edge[i].cost;
edge_cnt++;
if(edge_cnt==n-1) break;
}
}
if(edge_cnt!=n-1) return -1;
else return ans;
}
int main()
{
int m,n;
while(cin>>m&&m!=0){
cin>>n;
for(int i=0;i<M;i++)
for(int j=0;j<M;j++)
isin[i][j]=false;
int cnt=0;
while(m--){
int a,b,c;
cin>>a>>b>>c;
if(isin[a][b]==false){
isin[a][b]=isin[b][a]=true;
Edge[cnt].u=a;
Edge[cnt].v=b;
Edge[cnt].cost=c;
cnt++;
}else{
for(int j=0;j<cnt;j++){
if(((Edge[j].u==a&&Edge[j].v==b)||(Edge[j].u==b&&Edge[j].v==a))&&Edge[j].cost>c){
Edge[j].cost=c;
break;
}
}
}
}
int ans=Kruskal(n,cnt);
if(ans==-1) cout<<"?\n";
else cout<<ans<<"\n";
}
return 0;
}
刷题(牛客网-考研复试机试题)
-
畅通工程
直接使用prim算法/kruskal算法模板即可。 -
继续畅通工程
同样可以直接使用算法模板,需要注意的是,已修建好的道路将其费用置为0。 -
Freckles
直接使用算法模板;需要计算每个节点间的距离;同时需要注意数据的类型。
二、欧拉回路
欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路。(不需要考虑孤立的节点)
- 无向图存在欧拉回路:连通,所有节点均为偶度点
- 有向图存在欧拉回路:连通,节点的入度之和=节点的出度之和
- 题目:欧拉回路
代码如下。
#include<iostream>
#define N 1005
using namespace std;
bool g[N][N];
int fa[N],d[N];
void init(int n)
{
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
g[i][j]=false;
}
fa[i]=i;
g[i][i]=true;
d[i]=0;
}
}
int find(int x)
{
if(x==fa[x]) return x;
else return find(fa[x]);
}
bool solute(int n)
{
//判断是否是连通,使用并查集
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(g[i][j]){
int f1=find(i);
int f2=find(j);
if(f1!=f2){
fa[f1]=f2;
}
}
}
}
for(int i=1;i<=n;i++)
if(fa[i]==i&&d[i]) //不考虑孤立的点
cnt++;
if(cnt!=1) return false;
//判断所有节点是否为偶度节点
for(int i=1;i<=n;i++){
if(d[i]%2) return false;
}
return true;
}
int main()
{
int n,m;
while(cin>>n&&n!=0){
cin>>m;
init(n);
while(m-->0){
int a,b;
cin>>a>>b;
g[a][b]=g[b][a]=true;
if(a!=b){
d[a]++;
d[b]++;
}
}
bool f=solute(n);
if(f) cout<<"1\n";
else cout<<"0\n";
}
return 0;
}
三、树的判断
什么是树?在无环的情况下,根节点的入度为0,其余节点的入度为1。
这道题的关键在于如何判断是否存在环,可以使用并查集进行判断。
代码如下。
#include<iostream>
#define N 10005
using namespace std;
bool isnode[N];
int fa[N];
void init()
{
for(int i=0;i<N;i++){
isnode[i]=false;
fa[i]=i;
}
}
int find(int x)
{
if(x==fa[x]) return x;
else return find(fa[x]);
}
//只有根结点的入度为0,其余为1;
int main()
{
int s,e,k=0;
bool flag=true;
init();
while(cin>>s>>e){
if(s==-1&&e==-1) break;
if(s==0&&e==0){//输入处理
k++;
bool f=false;
int cnt=0;
for(int i=0;i<N;i++){
if(isnode[i]) f=true;
if(isnode[i]&&fa[i]==i) cnt++;
}
if(f==false) cout<<"Case "<<k<<" is a tree.\n";
else if(cnt==1&&flag) cout<<"Case "<<k<<" is a tree.\n";
else cout<<"Case "<<k<<" is not a tree.\n";
//初始化
flag=true;
init();
}else{
isnode[s]=true;
isnode[e]=true;
int f1=find(s);
int f2=find(e);
if(fa[e]!=e&&fa[e]!=s) {
flag=false; //说明入度大于1
continue;
}
if(f1==f2) flag=false; //有环
else fa[e]=s;
}
}
return 0;
}
文章参考胡凡的《算法笔记》。