在無向加權圖中,n個頂點的最小生成樹有n-1條邊,這些邊使得n個頂點之間可達,且總的代價最小。
prim算法是一種貪心算法,將全部的頂點劃分爲2個集合,每次總在2個集合之間中找最小的一條邊,局部最優最終達到全局最優,這正是貪心的思想。
具體的描述參見相關書籍:
描述
從單一頂點開始,普里姆算法按照以下步驟逐步擴大樹中所含頂點的數目,直到遍及連通圖的所有頂點。
1. 輸入:一個加權連通圖,其中頂點集合爲V,邊集合爲E;
2. 初始化:Vnew ={x},其中x爲集合V中的任一節點(起始點),Enew ={};
3. 重複下列操作,直到Vnew =V:
1. 在集合E中選取權值最小的邊(u, v),其中u爲集合Vnew中的元素,而v則不是(如果存在有多條滿足前述條件即具有相同權值的邊,則可任意選取其中之一);
2. 將v加入集合Vnew中,將(u, v)加入集合Enew中;
4. 輸出:使用集合Vnew和Enew來描述所得到的最小生成樹。
prim的實現:
1 //prim最小生成樹
2
3 public Edge[] getEdges(int position){ //返回從頂點position開始的最小生成樹的邊數組
4 Edge[] edges = new Edge[size()-1]; //最小生成樹裏邊數爲n-1,!!!!size()
5 VNodes[position].setVisited(true); //將原來的遍歷中用的標誌在這分離集合
6 for(int i = 0;i <edges.length;i++) //找n-1條邊出來
7 {
8 Edge edge =getMinEdge(VNodes); //在當前分離的2個集合之間找到最小邊
9 edges[i] = edge;
10 VNodes[edge.getEnd()].setVisited(true);//將新添加的邊的另一端的頂點分離出來
11 }
12 return edges;
13 }
14
15 private Edge getMinEdge(VNode[] VNodes){ //從分離的2個集合之間求出最小的邊
16 return min;
17 }
18
19 private boolean hasEdge(VNode node){ //判斷某個標記true的頂點跟另一個集合之間是否有邊
20 return false;
21 }
22
23
24 private Edge getMinEdgeFrom(VNode node){ //如果有邊(前提),求出這個頂點到對方集合的最小邊
25 return min;
26 }
27
28
因爲我存的是頂點,所以還要找邊,比較麻煩一點,如果在圖中記錄了一個邊得數組,就可以直接在邊得數組裏面去找最小邊。
解決問題的思路是先很容易就可以寫出上述最小生成樹的邏輯實現,然後去一一實現支持它的方法。
三個支持方法的實現:
private Edge getMinEdge(VNode[] VNodes){ //從分離的2個集合之間求出最小的邊
Edge min = null;
//for(int i = 0;i < VNodes.length;i++)
for(int i = 0;i <size();i++)
{
if(VNodes[i].getVisited() && hasEdge(VNodes[i]))//從true集合向false集合找
{
Edge temp =getMinEdgeFrom(VNodes[i]);
if(min == null)//第一次的初始化min
min = temp;
else if(temp.getLen()< min.getLen())
min = temp;
}
}
return min;
}
private boolean hasEdge(VNode node){ //判斷某個標記true的頂點跟另一個集合之間是否有邊
if(node.getFirst() == null)
return false;
else
{
Edge temp = node.getFirst();
while(temp != null)
{
int index = temp.getEnd();
if(VNodes[index].getVisited() == false)
return true;
else temp = temp.getNext();
}
}
return false;
}
private Edge getMinEdgeFrom(VNode node){ //如果有邊(前提),求出這個頂點到對方集合的最小邊
Edge min = null;
Edge temp = node.getFirst();
while(temp != null)
{
int index = temp.getEnd();
if(VNodes[index].getVisited() == false)//說明此時的temp是到對方集合的一條邊
{
if(min == null)
min = temp;
else if(temp.getLen()< min.getLen())
min = temp;
}
temp = temp.getNext();
}
return min;
}
然後添加一個圖來測試一下:
public static void main(String[] args) {
// TODO Auto-generated method stub
UnDirectedGraph g = new UnDirectedGraph();
for(int i = 1;i <7;i++)
g.addVNode("V" +i);
g.addEdge(0, 1, 6);
g.addEdge(0, 2, 1);
g.addEdge(0, 3, 5);
g.addEdge(1, 2, 5);
g.addEdge(1, 4, 5);
g.addEdge(2, 3, 5);
g.addEdge(2, 4, 6);
g.addEdge(2, 5, 4);
g.addEdge(3, 5, 2);
g.addEdge(4, 5, 6);//嚴蔚敏數據結構中的那個圖
Edge[] edges = g.getEdges(0);
System.out.println("輸出最小生成樹的邊");
for(int i = 0;i <edges.length;i++)
{
int start = edges[i].getStart();
int end = edges[i].getEnd();
System.out.println("邊: " + g.VNodes[start].getVNode() +"---" +g.VNodes[end].getVNode()
+ " 長度:" + edges[i].getLen());
}
}
結果:
輸出最小生成樹的邊:
邊: V1---V3 長度:1
邊: V3---V6 長度:4
邊: V6---V4 長度:2
邊: V3---V2 長度:5
邊: V2---V5 長度:5
可以發現這5條邊是跟圖中生成過程的順序一樣,依次找到放入數組的。
更多相關資料:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/30/2615542.html
例題 hdu1233還是暢通工程:
模板題目
package 最小生成樹;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Scanner;
public class hdu還是暢通工程 {
public static void main(String[] args) {
Scanner sc = new Scanner(new InputStreamReader(System.in));
boolean []v;
int [][]map;
int a,b,INF=100000000;
while(true){
int N = sc.nextInt();
if(N==0)break;
int M = N*(N-1)/2;
map = new int[N][N];
for(int i=0;i<N;i++)map[i][i] = INF;
v = new boolean[N];
for(int i=0;i<M;i++){
a = sc.nextInt();
b = sc.nextInt();
map[a-1][b-1] = map[b-1][a-1] = sc.nextInt();
}
int falg = 0;
int MIN ;
int SUM = 0;
v[0] = true;
for(int i=1;i<N;i++){//循環N-1次代表構造好了最小生成樹
MIN = 1000000;
for(int j=0; j<N; j++)
if(!v[j]&& MIN > map[0][j]){
MIN = map[0][j];
falg = j;
}
v[falg] = true;
SUM += MIN;
for(int j=0; j<N; j++){
if(!v[j] && map[0][j]>map[falg][j]){//參見數據結構
map[0][j] = map[falg][j];
}
}
}
System.out.println(SUM);
}
}}
}
其實該題用kruskal算法最簡便。
出於練習的心態於是乎,先用prime做了一下,開始時並查集+prime。最後上網找找該題的prime解法,發現直接用prime算出最小路徑---順便判斷圖的連通性更簡便
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.Scanner;
public class hdu1863暢通工程_最小生成樹{
static int map[][],fa[],n,m,a,b,c,INF=100000000;
static boolean v[],flag;
public static void main(String[] args) {
Scanner sc = new Scanner(new InputStreamReader(System.in));
while(true){
n = sc.nextInt();//道路條數
m = sc.nextInt();//村莊數
if(n==0)break;
map = new int[m+1][m+1];
v = new boolean[m+1];
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++)
map[j][i]=map[i][j]=INF;
for(int i=1;i<=n;i++){
a = sc.nextInt();
b = sc.nextInt();
c = sc.nextInt();
if(map[a][b]>c)
map[a][b]=map[b][a]=c;//更新路長
}int sum;
if((sum=prime())==-1)
System.out.println("?");
else
System.out.println(sum);
}
}
private static int prime() {
v[1] = true;
int MIN,k,sum=0;
for(int i=1;i<m;i++){
MIN = 10000000;
k = -1;
for(int j=1;j<=m;j++)
if(!v[j]&& MIN>map[1][j]){
MIN = map[1][j];
k = j;
}
if(k==-1)//判斷是否有最小生成樹
return -1;
v[k] = true;
sum += MIN;
for(int j=1;j<=m;j++){
if(!v[j]&& map[1][j] > map[k][j])
map[1][j] = map[k][j];
}
}
return sum;
}
}
並查集 + prime
import java.io.InputStreamReader;
import java.util.Scanner;
public class hdu1863暢通工程_最小生成樹 {
static int[]fa;
public static void main(String[] args) {
// TODO Auto-generated method stub
int map[][];
int INF = 1000000000;
boolean[]v;
Scanner sc = new Scanner(new InputStreamReader(System.in));
while(true){
int n = sc.nextInt();//路的條數
int m = sc.nextInt();//村莊數
if(n==0)break;
map = new int[m][m];
v = new boolean[m];
fa = new int[m+1];
for(int i=1;i<=m;i++)fa[i] = i;
for(int i=0;i<m;i++)
for(int j=i;j<m;j++)
map[i][j]=map[j][i]=INF;
for(int i=0;i<n;i++){
int a = sc.nextInt();
int b = sc.nextInt();
Union(a,b);
map[a-1][b-1]=map[b-1][a-1]=sc.nextInt();
}
int Count=0;
for(int i=1;i<=m;i++)
if(i==fa[i])
Count++;
if(Count!=1){
System.out.println("?");
continue;
}
int MIN,sum=0,falg = 0;
v[0] = true;
for(int i=1;i<m;i++){
MIN = 100000000;
for(int j=0;j<m;j++)
if(!v[j] && MIN>map[0][j]){
MIN = map[0][j];
falg = j;
}
v[falg] = true;
sum += MIN;
for(int j=0;j<m;j++){
if(!v[j]&& map[0][j]>map[falg][j])
map[0][j]=map[falg][j];
}
}
System.out.println(sum);
}
}
private static void Union(int a, int b) {
int x = find(a);
int y = find(b);
if(x!=y)
fa[y] = x;
}
private static int find(int b) {
while(b!=fa[b])
b = fa[b];
return b;
}
}