求最小生成树MST:Prim算法(普里姆算法)
最小生成树简称为MST,给定一个带权的无向连通图,如何选取一棵生成树,使树上所有边上权的总和为最小,这叫最小生成树。
上图中红框标记的子图就是我们要的最小生成树
引入MST概念后,我们以经典的修路问题来引出prim算法
【例子】如下图,有一个7村庄(A~G),需要修路将7个村庄连通,且不同路的路径不同(权值不同),要求既要连通7村,也要路径最短
算法分析:
就是一个求MST的题,接下来我们用prim算法求MST
prim算法核心就是:指定一个起点顶点,标记该顶点为已访问,将该顶点可能直连的顶点(未访问的)找到。然后根据连接顶点路径长度,找到最短路径即可。在重复顶点个数的次数后,停止循环。就能得到MST树。
prim算法的操作有点类似贪心算法。局部最优得到全局最优。
代码实现:(代码中包含图结构的知识,不清楚的朋友请猛击此处(待完善!!!)查看我前面的文章)
package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.prim;
class Graph{
int vertexes;//顶点数
char[] verData;//顶点数据
int[][] edges;//邻接矩阵
public Graph(int vertexes) {
this.vertexes = vertexes;
this.verData=new char[vertexes];
this.edges=new int[vertexes][vertexes];
}
}
class MinTree{
public void createGraph(Graph graph,char[] verData,int[][] edges){
int i,j;
for (i=0;i<graph.edges.length;i++){
graph.verData[i]=verData[i];
for (j=0;j<graph.edges[0].length;j++){
graph.edges[i][j]=edges[i][j];
}
}
}
public void showGraph(Graph graph){
for (int[] temp:graph.edges) {
for (int t:temp) {
System.out.print(t+" ");
}
System.out.println();
}
}
public void prim(Graph graph,int n){
//创建一个记录顶点是否被访问的数组(未访问为0,访问为1)
int visited[]=new int[graph.verData.length];
//将起始顶点设为已访问
visited[n]=1;
int index1=-1;//保存起点顶点索引
int index2=-1;//保存终点顶点索引
int minWeight=10000;//保存循环当前的最小权值(默认为10000)
//按照顶点数遍历邻接矩阵找到每一个顶点所连接顶点的最小权值路径
for (int k=1;k<visited.length;k++){
for (int i=0;i<graph.edges.length;i++){
for (int j=0;j<graph.edges[0].length;j++){
//要满足i指向已访问的起点顶点,j指向未访问的顶点,两点间权值小于minWeight
if (visited[i]==1 && visited[j]==0 && graph.edges[i][j]<minWeight){
//按照邻接矩阵的规律可知:i将保存已被访问的起点顶点索引,j将保存未被访问且与起点权值最小的终点顶点
minWeight=graph.edges[i][j];
index1=i;
index2=j;
}
}
}
System.out.println("<顶点"+graph.verData[index1]+"--"+"顶点"+graph.verData[index2]+">: "+graph.edges[index1][index2]);
minWeight=10000;//重置
visited[index2]=1;//终点顶点已被访问
}
}
}
public class 普里姆算法_prim_修路问题 {
public static void main(String[] args) {
char[] verData={'A','B','C','D','E','F','G'};
int vertexes=verData.length;
int[][] edges=new int[][]{
{10000,5,7,10000,10000,10000,2},
{5,10000,10000,9,10000,10000,3},
{7,10000,10000,10000,8,10000,10000},
{10000,9,10000,10000,10000,4,10000},
{10000,10000,8,10000,10000,5,4},
{10000,10000,10000,4,5,10000,6},
{2,3,10000,10000,4,6,10000}
};
Graph graph=new Graph(vertexes);
MinTree minTree=new MinTree();
minTree.createGraph(graph,verData,edges);
minTree.prim(graph,0);
}
}
<顶点A--顶点G>: 2
<顶点G--顶点B>: 3
<顶点G--顶点E>: 4
<顶点E--顶点F>: 5
<顶点F--顶点D>: 4
<顶点A--顶点C>: 7
求最小生成树MST:Kruskal算法(克鲁斯卡尔算法)
和prim算法对应的就是K算法,他们都是求带权最小生成树的算法。
【例子】七个公交站点(A~G),需要将七个站点连通,每个路的长度(权)不同,如何修路使站点连通,且长度最短
算法分析:
就是一个求MST的题,接下来我们用K算法求MST
K算法的核心是:将各个边按权值大小排序,将排序的边按顺序选取,同时要满足所选取的边不能与之前选取的边形成回路。若不满足,就跳过该边(如下图第4步)。
所以,K算法的核心问题集中在两点上:
1)将边按权值排序
2)将边添加到最小生成树时,如何判断是否形成回路
对于1)而言,可以从八大排序算法中,任选一种来作排序,也可以利用Java特性,继承Comparable接口,通过实现compareTo方法来实现排序
对于2)而言,采用终点相同判定来实现。即每个边的顶点都对应一个终点(与之连通的最大顶点),当终点相同时说明产生回路。如下图,C终点为F,F终点为F,故权值为6的边,不能连入,否则将形成回路
代码实现:(其中的getEnd方法使用了并查集原理)
package cn.dataStructureAndAlgorithm.demo.tenAlgorithm.kruskal;
import java.util.Arrays;
class Graph{
int vertexes;//顶点数
char[] verData;//顶点数据
int[][] edges;//邻接矩阵
//Edata类是对边的具体描述类,该类继承了Comparable接口
static class EData implements Comparable<EData>{
char start;//记录边的一个顶点
char end;//记录边的另一个顶点
int weight;//边的权值
public EData(char start, char end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
@Override
public int compareTo(EData o) {
return this.weight-o.weight;
}
@Override
public String toString() {
return "EData{<" + start +","+ end +","+ weight +">}";
}
}
public Graph(int vertexes) {
this.vertexes = vertexes;
this.verData=new char[vertexes];
this.edges=new int[vertexes][vertexes];
}
}
class MinTree{
private int valueEdges=0;
private Graph graph;
private int INF;
public MinTree(Graph graph,char[] verData,int[][] edges,int INF) {
//制图
for (int i=0;i<graph.edges.length;i++){
graph.verData[i]=verData[i];
for (int j=0;j<graph.edges[0].length;j++){
graph.edges[i][j]=edges[i][j];
}
}
this.graph=graph;
//求有效边数
for (int i=0;i<graph.vertexes;i++){
for (int j=i+1;j<graph.vertexes;j++){
if (graph.edges[i][j]!=INF){
this.valueEdges++;
}
}
}
this.INF=INF;
}
/**
* 用于获取顶点值对应的顶点下标
* @param ch 顶点值
* @return 下标或-1
*/
public int getPosition(char ch){
for (int i=0;i<graph.vertexes;i++){
if (graph.verData[i]==ch){
return i;
}
}
return -1;
}
/**
* 用于获取所有有效边,通过邻接矩阵来获取
* @return 有效边集合
*/
public Graph.EData[] getEdges(){
Graph.EData[] edges = new Graph.EData[valueEdges];
int index=0;
for (int i=0;i<graph.vertexes;i++){
for (int j=i+1; j<graph.vertexes;j++){
if (graph.edges[i][j]!=INF){
edges[index++]=new Graph.EData(graph.verData[i],graph.verData[j],graph.edges[i][j]);
}
}
}
return edges;
}
/**
* 用于获得边的终点(并查集原理)
* @param ends 每个边对应的终点集合
* @param p 边的索引
* @return 该边的终点下标
*/
public int getEnd(int[] ends,int p){
while (ends[p]!=0){//不断向上查找到边的最终顶点
p=ends[p];
}
return p;
}
/**
* 克鲁斯卡尔算法主体,获取到排序后的有效边,依次判断各边添加后是否形成回路,没有即可添加
*/
public void kruskal(){
Graph.EData[] edges = getEdges();//存储所有有效边
Arrays.sort(edges);//对有效边进行排序
Graph.EData[] result = new Graph.EData[graph.vertexes-1];//存储最小生成树的各边,边数=顶点数-1
int[] ends=new int[valueEdges];//存储各边对应的终点
int p1,p2,n,m,index=0;//p1,p2分别为边的一个顶点与另一个顶点下标,n,m为顶点对应的终点下标
for (int i=0;i<valueEdges;i++){//依次判断添加各边后,子图是否连通
p1=getPosition(edges[i].start);
p2=getPosition(edges[i].end);
n=getEnd(ends,p1);
m=getEnd(ends,p2);
//若两顶点终点不同,声明不成回路,可以采用该边
if (n!=m){
ends[n]=m;//将start对应的终点设作end的顶点
result[index++] = edges[i];//添加正确解
}
}
System.out.println(Arrays.toString(result));
}
}
public class 克鲁斯卡尔算法_kruskal_公交问题 {
static final int INF=Integer.MAX_VALUE;
public static void main(String[] args) {
char[] verData={'A','B','C','D','E','F','G'};
int vertexes=verData.length;
int[][] edges=new int[][]{
{0,12,INF,INF,INF,16,14},
{12,0,10,INF,INF,7,INF},
{INF,10,0,3,5,6,INF},
{INF,INF,3,0,4,INF,INF},
{ INF, INF,5,4,0,2,8},
{16,7,6, INF,2,0,9},
{14,INF,INF,INF,8,9,0}
};
Graph graph=new Graph(vertexes);
MinTree minTree=new MinTree(graph,verData,edges,INF);
minTree.kruskal();
}
}
普里姆与克鲁斯卡尔算法:
> 普利姆算法时间复杂度为O(n2),该算法适用于求边稠密网的最小支撑树
> 克鲁斯卡尔算法的时间复杂度为O(eloge),适用于求稀疏网的最小支撑树
> 克鲁斯卡尔算法在执行过程中,以边为核心,而普利姆算法则是以顶点为核心,在算法执行过程中,则是把顶点集合分为已经访问的顶点集合和未访问的顶点集合,通过不断的寻找两个集合间的最短边实现的。
其他常用算法,见下各链接
【常用十大算法_迪杰斯特拉(Dijkstra)算法,弗洛伊德(Floyd)算法】
【数据结构与算法整理总结目录 :>】<-- 宝藏在此(doge)