了解最小生成树之前
首先什么是生成树?
生成树:所有顶点均由边连接在一起,但不存在回路的图。
ps:
一个图可以有许多棵不同的生成树;
生成树是图的极小连通子图,去掉一条则非连通;
一个有n个顶点的连通图的生成树有n-1条边;
含有n个顶点n-1条边的图不一定是生成树;
在生成树中再加一条边必然形成回路;
生成树中任意两个顶点间的路径是唯一的;
无向图的生成树:
可以用深度优先生成生成深度优先生成树;
或者是用广度优先生成广度优先生成树;
最小生成树:给定一个无向网络,在该网的所有生成树中,使得各边权值之和最小的那棵生成树称为该网的最小生成树,也叫最小代价生成树。
如何构造???
最小生成树有两种基本算法
都利用了MST(最小生成树的性质);
1.普里姆(Prim)算法
算法思想
需要三个集合,一个存储在生成树上的顶点,另一个存储不在生成树上的点,还有一个存储在最小生成树中边的集合。在生成树顶点集合中寻找与其连接权值最小的边,并将连接的顶点加入在生成树上的点集合,反复判断,扩大在生成树上的点的集合,最后在生成树上的顶点等于总顶点量,即为生成最小生成树。通过边集合求出最小权值。
维护两个数组。
一个lowcost数组,表示当前已经归入最小生成树的结点集合与未归入的集合节点之间的最小代价;
一个visited数组,表示是否已经归入最小生成树
每次从lowcost数组中找到未归入最小生成树但代价最小的节点,将之归入最小生成树,之后根据归入的新节点更新lowcost数组
直到所有节点全部归入最小生成树,算法结束;
首先是对图的创建代码实现
#include<stdio.h>
#include<stdlib.h>
#define MAX 32767
/*
图顶点之间不通那么邻接矩阵的值为MAX
如果顶点是自己本身,那么值为0
*/
typedef struct Graph
{
char* vexs;
int** arcs;//指向二维数组
int vexNum;
int arcNum;
}Graph;
Graph* initGraph(int vexNum)
{
Graph* G=(Graph*)malloc(sizeof(Graph));
G->vexs=(char*)malloc(sizeof(char)*vexNum);
G->arcs=(int**)malloc(sizeof(int*)*vexNum);
for(int i=0;i<vexNum;i++)
{
G->arcs[i]=(int*)malloc(sizeof(int)*vexNum);
}
G->vexNum=vexNum;
G->arcNum=0;
return G;
}//初始化图开辟空间
void createGraph(Graph* G,char* vexs,int *arcs)
{
for(int i=0;i<G->vexNum;i++)
{
G->vexs[i]=vexs[i];
for(int j=0;j<G->vexNum;j++)
{
G->arcs[i][j]=*(arcs+i * G->vexNum+j);//取到矩阵二维数组里面的数值
if(G->arcs[i][j]!=0&&G->arcs[i][j]!=MAX)
{
G->arcNum++;
}
}
}
G->arcNum/=2;//无向图一条边只算一次所以应该除2
}
void DFS(Graph* G,int*visited,int index)
{
printf("%c\t",G->vexs[index]);
visited[index]=1;
for(int i=0;i<G->vexNum;i++)
{
if(G->arcs[index][i]>0&&G->arcs[index][i]!=MAX&&!visited[i])
{
DFS(G,visited,i);
}
}
}
int main()
{
Graph* G=initGraph(6);
int* visited=(int*)malloc(sizeof(int)*G->vexNum);
for(int i=0;i<G->vexNum;i++)
{
visited[i]=0;
}
int arcs[6][6]={
0,6,1,5,MAX,MAX,
6,0,5,MAX,3,MAX,
1,5,0,5,6,4,
5,MAX,5,0,MAX,2,
MAX,3,6,MAX,0,6,
MAX,MAX,4,2,6,0
};
createGraph(G,"123456",(int*)arcs);
DFS(G,visited,0);
printf("\n");
return 0;
}
如何实现prim算法呢?
1.记录当前U集合的状态
2.选择最小边以及顶点加入到U集合中
typedef struct Edge
{
char vex;//记录U集合到最小边的起始节点
int weight;//记录最小边
}
以下是补全的代码
添加了获取最小边,初始化U集合
#include<stdio.h>
#include<stdlib.h>
#define MAX 32767
/*
图顶点之间不通那么邻接矩阵的值为MAX
如果顶点是自己本身,那么值为0
*/
typedef struct Graph
{
char* vexs;
int** arcs;//指向二维数组
int vexNum;
int arcNum;
}Graph;
typedef struct Edge
{
char vex;
int weight;
}Edge;
/*
当edge.weight=0时,代表顶点加入U集合中
*/
Edge* initEdge(Graph* G,int index)
{
Edge* edge=(Edge*)malloc(sizeof(Edge)*G->vexNum);
for(int i=0;i<G->vexNum;i++)
{
edge[i].vex=G->vexs[index];
edge[i].weight=G->arcs[index][i];
}
return edge;
}
int getMinEdge(Edge* edge,Graph* G)
{
int index;
int min=MAX;
for(int i=0;i<G->vexNum;i++)
{
if(edge[i].weight!=0&&min>edge[i].weight)
{
min=edge[i].weight;
index=i;
}
}
return index;
}
void prim(Graph* G,int index)
{
int min;
Edge* edge=initEdge(G,index);
for(int i=0;i<G->vexNum-1;i++)
{
min=getMinEdge(edge,G);
printf("v%c-->v%c,weight=%d\n",edge[min].vex,G->vexs[min],edge[min].weight);
edge[min].weight=0;
for(int j=0;j<G->vexNum;j++)
{
if(G->arcs[min][j]<edge[j].weight)
{
edge[j].weight=G->arcs[min][j];
edge[j].vex=G->vexs[min];
}
}
}
}
Graph* initGraph(int vexNum)
{
Graph* G=(Graph*)malloc(sizeof(Graph));
G->vexs=(char*)malloc(sizeof(char)*vexNum);
G->arcs=(int**)malloc(sizeof(int*)*vexNum);
for(int i=0;i<vexNum;i++)
{
G->arcs[i]=(int*)malloc(sizeof(int)*vexNum);
}
G->vexNum=vexNum;
G->arcNum=0;
return G;
}//初始化图开辟空间
void createGraph(Graph* G,char* vexs,int *arcs)
{
for(int i=0;i<G->vexNum;i++)
{
G->vexs[i]=vexs[i];
for(int j=0;j<G->vexNum;j++)
{
G->arcs[i][j]=*(arcs+i * G->vexNum+j);//取到矩阵二维数组里面的数值
if(G->arcs[i][j]!=0&&G->arcs[i][j]!=MAX)
{
G->arcNum++;
}
}
}
G->arcNum/=2;//无向图一条边只算一次所以应该除2
}
void DFS(Graph* G,int*visited,int index)
{
printf("%c\t",G->vexs[index]);
visited[index]=1;
for(int i=0;i<G->vexNum;i++)
{
if(G->arcs[index][i]>0&&G->arcs[index][i]!=MAX&&!visited[i])
{
DFS(G,visited,i);
}
}
}
int main()
{
Graph* G=initGraph(6);
int* visited=(int*)malloc(sizeof(int)*G->vexNum);
for(int i=0;i<G->vexNum;i++)
{
visited[i]=0;
}
int arcs[6][6]={
0,6,1,5,MAX,MAX,
6,0,5,MAX,3,MAX,
1,5,0,5,6,4,
5,MAX,5,0,MAX,2,
MAX,3,6,MAX,0,6,
MAX,MAX,4,2,6,0
};
createGraph(G,"123456",(int*)arcs);
DFS(G,visited,0);
printf("\n");
prim(G,0);
return 0;
}
2.克鲁斯卡尔(Kruskal)算法
算法思想
基本上与贪心算法脱不开干系,寻找最小权值的各个边,依次加入最小生成树的集合里面,加入过程中判断是否形成环,成环则舍弃选取下一条代价最小的边,以此类推,直到最小生成树里面所以的顶点都存在。
基本步骤
1.维护一个边的数组并排序
2.判断图是否连通?
需要一个辅助数组,来记录当前索引的节点属于哪个连通分量(并查集)判断是否属于同一个连通分量
代码实现
#include<stdio.h>
#include<stdlib.h>
#define MAX 32767
typedef struct Graph
{
char* vexs;
int** arcs;//指向二维数组
int vexNum;
int arcNum;
}Graph;
typedef struct Edge
{
int start;
int end;
int weight;
}Edge;
Edge* initEdge(Graph* G)
{
int index=0;
Edge* edge=(Edge*)malloc(sizeof(Edge)*G->arcNum);
for(int i=0;i<G->vexNum;i++)
{
for(int j=0;j<G->vexNum;j++)
{
if(G->arcs[i][j]!=MAX)
{
edge[index].start=i;
edge[index].end=j;
edge[index].weight=G->arcs[i][j];
index++;
}
}
}
return edge;
}
void sortEdge(Edge* edge,Graph* G)
{
Edge temp;
for(int i=0;i<G->arcNum-1;i++)
{
for(int j=0;j<G->arcNum-i-1;j++)
{
if(edge[j].weight>edge[j+1].weight)
{
temp=edge[j];
edge[j]=edge[j+1];
edge[j+1]=temp;
}
}
}
}
void kruskal(Graph* G)
{
int * connected=(int*)malloc(sizeof(int)*G->vexNum);
for(int i=0;i<G->arcNum;i++)
{
connected[i]=i;
}
Edge* edge=initEdge(G);
sortEdge(edge,G);
for(int i=0;i<G->arcNum;i++)
{
int start=connected[edge[i].start];
int end=connected[edge[i].end];
if(start!=end)
{
printf("v%c-->v%c weight=%d\n",G->vexs[edge[i].start],G->vexs[edge[i].end],edge[i].weight);
for(int j=0;j<G->vexNum;j++)
{
if(connected[j]==end)
{
connected[j]=start;
}
}
}
}
}
Graph* initGraph(int vexNum)
{
Graph* G=(Graph*)malloc(sizeof(Graph));
G->vexs=(char*)malloc(sizeof(char)*vexNum);
G->arcs=(int**)malloc(sizeof(int*)*vexNum);
for(int i=0;i<vexNum;i++)
{
G->arcs[i]=(int*)malloc(sizeof(int)*vexNum);
}
G->vexNum=vexNum;
G->arcNum=0;
return G;
}//初始化图开辟空间
void createGraph(Graph* G,char* vexs,int *arcs)
{
for(int i=0;i<G->vexNum;i++)
{
G->vexs[i]=vexs[i];
for(int j=0;j<G->vexNum;j++)
{
G->arcs[i][j]=*(arcs+i * G->vexNum+j);//取到矩阵二维数组里面的数值
if(G->arcs[i][j]!=0&&G->arcs[i][j]!=MAX)
{
G->arcNum++;
}
}
}
G->arcNum/=2;//无向图一条边只算一次所以应该除2
}
void DFS(Graph* G,int*visited,int index)
{
printf("%c\t",G->vexs[index]);
visited[index]=1;
for(int i=0;i<G->vexNum;i++)
{
if(G->arcs[index][i]>0&&G->arcs[index][i]!=MAX&&!visited[i])
{
DFS(G,visited,i);
}
}
}
int main()
{
Graph* G=initGraph(6);
int* visited=(int*)malloc(sizeof(int)*G->vexNum);
for(int i=0;i<G->vexNum;i++)
{
visited[i]=0;
}
int arcs[6][6]={
0,6,1,5,MAX,MAX,
6,0,5,MAX,3,MAX,
1,5,0,5,6,4,
5,MAX,5,0,MAX,2,
MAX,3,6,MAX,0,6,
MAX,MAX,4,2,6,0
};
createGraph(G,"123456",(int*)arcs);
DFS(G,visited,0);
printf("\n");
kruskal(G);
return 0;
}
两种算法比较
prim 选择点 时间复杂度O(n*2)(n为顶点数) 适应稠密图
kruskal 选择边 时间复杂度 O(eloge)(e为边数) 适应稀疏图