prim算法:
就我自己的通俗理解,一个图有n个顶点,我们随便找一个点作为最小树的顶点,加入树中,我们要找下一个点加入树中,我们找的这个点要求,1.与现在的最小树有相连的边(就是),2.该边还是所有与现在树相连的边中最小的,3.该点没在最小树中。满足就加入到最小树中。直到所有点都加入到树中,最小树就生成了。我画个图可能更好理解。
这里
现在有这几个点,假设我先将a加入最小树
现在最小树只有一个点,我们要找下一个满足条件的点加入最小树中,直到所有点都加入到最小树中。
最小的是a,b就把b加入,现在最小树
然后在从还没有加入最小树的点集合里找,里已经在最小树集合最近的点,
所以现在最小树变成
这里有两条一样小且满足条件的边,我们就随便选择一条。
为什么不去比较ae,和cf这两条边呢,因为我们的边一头是已经在最小树的点,一边是还没加入最小树的点,而此时a,e,c,f都已经在最小树中了
此时最小树已经生成,当所有点都加入到树中。
上代码:
//tu是图的加权临界矩阵
public static void prim(int[][] tu){
int[] vertix = new int[tu.length];//用来记录该点是否已经加入树中
ArrayList<Integer> ints = new ArrayList<Integer>();//用来记录已经加入树中的点
vertix[0] = 1;//表示0这个点已经加入树中了
ints.add(0);//记录0这个点加入树中
//结束条件所有点都加入了树中就结束
while (ints.size() < tu.length) {
int minDistance = Integer.MAX_VALUE; //用于寻找最小权值,初始化为int最大值,相当于无穷大的意思
int minV = -1; //用于存放未被放入树中顶点中与已被遍历顶点有最小权值的顶点
int minI = -1; //用于存放已被放入树中的顶点与未被遍历顶点有最小权值的顶点 ;即tu[minI][minV]在剩余的权值中最小,就是我们要找的边
for(int i = 0;i < ints.size();i++) { //i 表示已被加入树中的顶点
int v1 = ints.get(i);
for(int j = 0;j < tu.length;j++) {
if(vertix[j] != 1) { //满足此条件的表示,顶点j未被加入树中
if(tu[v1][j] != -1 && tu[v1][j] < minDistance) {//tu[v1][j]值为-1表示v1和j是非相邻顶点
minDistance = tu[v1][j];
minV = j;
minI = v1;
}
}
}
}
vertix[minV] = 1;
ints.add(minV);
System.out.println("("+minI+"->"+minV+")"+"权重:"+tu[minI][minV]);
tu[minI][minV] = 0;
tu[minV][minI] = 0;
}
System.out.println();
for (int[] ints1 : tu) {
for (int i : ints1) {
System.out.print(i+",");
}
System.out.println();
}
}
kruskal算法:
简单描述,就是将所有边按权重从小到大排序,然后就是从最小的边开始选,是否选这条边加入的条件是看如果这条边加入后是否会形成环,如果会形成环就跳过这条边,如果不会就加入。如果有n个顶点,就直到找到n-1条边就结束。
第一次肯定加入bc,我们怎么来判断加入这条边是否会形成环呢?
判断是否成环:
我直接说如何实现:
假设有5个点,现在他们就是五个树,他们的根顶点就是自己。用一个数组来装他们各自根顶点,现在bc变成一个树,那么就把以c为根顶点的所有点的根顶点变成b的根顶点。
开始数组{0,1,2,3,4},现在{0,1,1,3,4}。我们怎么判断这个边加入后是否会成环,如果边是ad,a点的根顶点与d的根顶点相同就表示他们在一个树上,那么ad加入就会形成环,如果不相等就不会形成环,就可以加入。
1.
2.
3.
4.
本来该选4的,最小,但是如果加入就会形成环,所以就跳过这条边,选这下一条边。
此时最小树就生成了。5个点,4条边。
上代码:
class ENode {
int start; // 边的起点
int end; // 边的终点
int weight; // 边的权重
public ENode(int start, int end, int weight) {
this.start = start;
this.end = end;
this.weight = weight;
}
}
//将邻接矩阵转换成对应的边
class Graph {
private static final int INF = -1; // 表示没有边
int[] vertexs; // 顶点集合
int[][] matrix; // 邻接矩阵
// 得到当前有向图中的所有边信息
public List<ENode> getEdges() {
List<ENode> edges = new ArrayList<ENode>();
for (int i = 0; i < vertexs.length; i++) {
//生成的边都是ab这样的,就是始点比终点小,就是代号更小,我这里存的是0,1,2,3,4....
for (int j = i; j < vertexs.length; j++) {
if (matrix[i][j] != INF) {
ENode edge = new ENode(vertexs[i], vertexs[j], matrix[i][j]);
edges.add(edge);
}
}
}
return edges;
}
}
public static void qSort(List<ENode> edges, int low, int high) {
if (low < high) {
int i = low, j = high;
ENode edge = edges.get(low);
while (i < j) {
while (edge.weight <= edges.get(j).weight && i < j) {
j--;
}
edges.set(i, edges.get(j));
while (edge.weight >= edges.get(i).weight && i < j) {
i++;
}
edges.set(j, edges.get(i));
}
edges.set(i, edge);
qSort(edges, low, i - 1);
qSort(edges, i + 1, high);
}
}
public static void kruskal(Graph graph){
List<ENode> edges = graph.getEdges();
int edgeNum = edges.size();
// 2.对所有有向边进行排序
qSort(edges, 0, edgeNum - 1);
int[] vertexs = graph.vertexs;
int length = graph.vertexs.length;
for (int i = 0; i < length-1; i++) {
for (int i1 = 0; i1 < edges.size(); i1++) {
ENode eNode = edges.get(i1);
if (vertexs[eNode.start] != vertexs[eNode.end]) {
System.out.println(zm[eNode.start]+"->"+zm[eNode.end]+"权重:"+eNode.weight);
int start = vertexs[eNode.start];
int end = vertexs[eNode.end];
for (int i2 = 0; i2 < vertexs.length; i2++) {
if (vertexs[i2] == end) {
vertexs[i2] = start;
}
}
}
}
}
}
public static void test2(){
int[][] array1 = {{-1,3,5,4,-1,-1,-1,-1,-1,-1,-1,-1},
{3,-1,-1,-1,3,6,-1,-1,-1,-1,-1,-1},
{5,-1,-1,2,-1,-1,4,-1,-1,-1,-1,-1},
{4,-1,2,-1,1,-1,-1,5,-1,-1,-1,-1},
{-1,3,-1,1,-1,2,-1,-1,4,-1,-1,-1},
{-1,6,-1,-1,2,-1,-1,-1,-1,5,-1,-1},
{-1,-1,4,-1,-1,-1,-1,3,-1,-1,6,-1},
{-1,-1,-1,5,-1,-1,3,-1,6,-1,7,-1},
{-1,-1,-1,-1,4,-1,-1,6,-1,3,-1,5},
{-1,-1,-1,-1,-1,5,-1,-1,3,-1,-1,9},
{-1,-1,-1,-1,-1,-1,6,7,-1,-1,-1,8},
{-1,-1,-1,-1,-1,-1,-1,-1,5,9,8,-1}};
int[][] array={{-1,5,-1,6,-1},
{5,-1,1,3,-1},
{-1,1,-1,4,6},
{6,3,4,-1,2},
{-1,-1,6,2,-1}
};
int[] v = new int[array.length];
for (int i = 0; i < v.length; i++) {
v[i]=i;
}
Graph graph = new Graph();
graph.matrix= array;
graph.vertexs = v;
kruskal(graph);
}
prim算法与kruskal算法区别
对于最小生成树,有两种算法可以解决。一种是Prim算法,该算法的时间复杂度为O(n²),与图中边数无关,该算法适合于稠密图,而另外一种是Kruskal,该算法的时间主要取决于边数,它较适合于稀疏图。
Prim算法:设图G =(V,E),其生成树的顶点集合为U。 ①、把v0放入U。 ②、在所有u∈U,v∈V-U的边(u,v)∈E中找一条最小权值的边,加入生成树。 ③、把②找到的边的v加入U集合。如果U集合已有n个元素,则结束,否则继续执行②。
Kruskal算法与Prim算法的不同之处在于,Kruskal在找最小生成树结点之前,需要对所有权重边做从小到大排序。将排序好的权重边依次加入到最小生成树中,如果加入时产生回路就跳过这条边,加入下一条边。当所有结点都加入到最小生成树中之后,就找出了最小生成树。
1.Prim在稠密图中比Kruskal优,在稀疏图中比Kruskal劣。
2.与Dijkstra类似,Prim算法也可以用堆优化,优先队列代替堆,优化的Prim算法时间复杂度O(mlogn)模板,Prim_heap在任何时候都有令人满意的的时间复杂度,但是代价是空间消耗极大。(以及代码很复杂>_<)
但值得说一下的是,时间复杂度并不能反映出一个算法的实际优劣。
竞赛题一般给的都是稀疏图,选择Prim_heap即可;如果觉得代码量太大,想要在Prim与Kruskal算法中选一个,那就选择Kruskal算法。
借鉴:图论——最小生成树:Prim算法及优化、Kruskal算法,及时间复杂度比较 - 嘤嘤狂吠OVO - 博客园