之前我們介紹了求最短路徑算法,現(xiàn)在又講最小生成樹算法,這兩個算法有什么區(qū)別呢?
首先要明確,最短路徑和最小生成樹是兩個不同的概念。
最短路徑是對于一個圖的兩個結(jié)點而言的。在一個圖中,結(jié)點A通過某些結(jié)點和邊可以走到結(jié)點B,這些結(jié)點和邊組成的從A到B的路徑中,最短路徑就這些路徑中權(quán)值總和最小的那一條(或多條)。
最短路徑常用算法有:Floyd、Dijkstra、SPFA、A*等
最小生成樹是對于一個圖本身而言的。對于一個有n個結(jié)點的無向連通圖,必然可以去掉某些邊,使得最終剩下n-1條邊,與n個結(jié)點共同組成原圖的一個生成樹,而最小生成樹就是所有可能的生成樹中n-1條邊的權(quán)值總和最小的那一個(或多個)。
常用算法有:Kruskal、Prim
我們現(xiàn)在在1號位置,我們要走遍圖中的所有頂點,各條路上的數(shù)值即為權(quán)值,求這個過程中的最小權(quán)值是多少?
Input:
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
Output:
19
Kruskal
又稱 “加邊法”
適用于稀疏圖
時間復雜度:O(MlogN),但通常邊M的數(shù)目要比頂點N的數(shù)目多很多,所以最終時間復雜度為O(MlogM)
import java.util.Scanner;class edge {
int u, v, w;edge(
int u,
int v,
int w) {
this.u = u;
this.v = v;
this.w = w;}
}
public class Kruskal {static edge[] e =
new edge[
10];
static int n, m;
static int[] f =
new int[
7];
static int sum =
0;
static int count =
0;
static Scanner input =
new Scanner(System.in);
public static void main(String[] args) {n = input.nextInt();m = input.nextInt();
for (
int i =
1; i <= m; i++) {
int a = input.nextInt();
int b = input.nextInt();
int c = input.nextInt();e[i] =
new edge(a, b, c);}
/*** 按權(quán)值排序* */quicksort(e,
1, m);
for (
int i =
1; i <= n; i++) {f[i] = i;}kruskal();System.out.println(sum);}
private static void kruskal() {
/*** 從小到大枚舉每一條邊* */for (
int i =
1; i <= m; i++) {
/*** 檢查一條邊的兩個頂點是否已經(jīng)連通,即判斷是否在同一個集合中* */if (merge(e[i].u, e[i].v)) {count++;sum = sum + e[i].w;}
/*** 選到n-1邊之后,退出循環(huán)* */if (count == n -
1) {
break;}}}
public static int partition(edge[] a,
int p,
int q) {
int x = a[p].w;
int i = p;
for (
int j = p+
1; j <= q; j++) {
if (a[j].w <= x) {i +=
1;edge temp = a[i];a[i] = a[j];a[j] = temp;}}edge temp = a[p];a[p] = a[i];a[i] = temp;
return i;}
public static void quicksort(edge[] a,
int p,
int q) {
if (p < q) {
int r = partition(a ,p ,q);quicksort(a, p, r -
1);quicksort(a, r +
1, q);}}
private static int getf(
int v) {
if (f[v] == v) {
return v;}
else {
/*** 壓縮路徑,每次函數(shù)返回時,將該位置的編號轉(zhuǎn)成祖宗編號* */f[v] = getf(f[v]);
return f[v];}}
private static boolean merge(
int v,
int u) {
int t1 = getf(v);
int t2 = getf(u);
/*** 判斷祖先是否相同* */if (t1 != t2) {
/*** 靠左原則* */f[t2] = t1;
return true;}
return false;}
}
prim
又稱 “加點法”
適用于稠密圖
時間復雜度:O(N^2)
import java.util.Scanner;
public class prim {
static int[][] e =
new int[
7][
7];
static int[] book =
new int[
7];
static int[] dis =
new int[
7];
static int count =
0;
static int sum =
0;
static int n, m;
static int min, mark;
static Scanner input =
new Scanner(System.
in);
public static void main(String[] args) {n = input.nextInt();m = input.nextInt();
for (
int i =
1; i <= n; i++) {
for (
int j =
1; j <= n; j++) {
if (i == j) {e[i][j] =
0;}
else {e[i][j] =
99999999;}}}
for (
int i =
1; i <= m; i++) {
int a = input.nextInt();
int b = input.nextInt();
int c = input.nextInt();e[a][b] = c;e[b][a] = c;}
for (
int i =
1; i <= n; i++) {dis[i] = e[
1][i];}book[
1] =
1;prime();System.
out.println(sum);}
private static void prime() {count++;
while (count < n) {min =
99999999;
for (
int i =
1; i <= n; i++) {
if(book[i] ==
0 && dis[i] < min) {min = dis[i];mark = i;}}book[mark] =
1;count++;sum += dis[mark];
for (
int i =
1; i <= n; i++) {
if (book[i] ==
0 && dis[i] > e[mark][i]) {dis[i] = e[mark][i];}}}}
}
而如果借助“堆”,每次選邊的時間復雜度可以變?yōu)镺(logM)
方法:
數(shù)組dis用來記錄生成樹到各個頂點的距離。
數(shù)組h是一個最小堆,堆里面存儲的是頂點的編號。這里不是按照頂點編號的大小創(chuàng)建的,而是按照頂點在數(shù)組dis中對應(yīng)的值建立這個最小堆。
**數(shù)組po**s用來記錄每個頂點的最小堆的位置
下面的左圖代表,
1號結(jié)點到2號點的距離為1
1號結(jié)點到3號點的距離為2
1號結(jié)點到6號點的距離為max
1號結(jié)點到4號點的距離為max
1號結(jié)點到5號點的距離為max
下面的右圖代表
dis數(shù)組存放了左圖的信息
h數(shù)組存放堆
poa數(shù)組存放了頂點對應(yīng)在堆中的位置
import java.util.Scanner;
public class prim2 {static int[] book =
new int[
7];
static int[] dis =
new int[
7];
static int[] h =
new int[
7];
static int[] pos =
new int[
7];
static int[] u =
new int[
19];
static int[] v =
new int[
19];
static int[] w =
new int[
19];
static int[] first =
new int[
7];
static int[] next =
new int[
19];
static int count =
0, size;
static int sum =
0;
static int n, m;
static int min, mark, mark2;
static Scanner input =
new Scanner(System.in);
public static void main(String[] args) {n = input.nextInt();m = input.nextInt();
for (
int i =
1; i <= m; i++) {u[i] = input.nextInt();v[i] = input.nextInt();w[i] = input.nextInt();}
/*** 無向圖需要再將所有邊反向存儲一次* */for (
int i = m +
1; i <=
2 * m; i++) {u[i] = v[i - m];v[i] = u[i - m];w[i] = w[i - m];}
/*** 鄰接表* */for (
int i =
1; i <= n; i++) {first[i] = -
1;}
for (
int i =
1; i <=
2 * m; i++) {next[i] = first[u[i]];first[u[i]] = i;}prime();System.out.println(sum);}
private static void prime() {book[
1] =
1;count++;dis[
1] =
0;
/*** 初始化dis數(shù)組,存放1到其他各點的距離* */for (
int i =
2; i <= n; i++) {dis[i] =
99999999;}mark2 = first[
1];
while (mark2 != -
1) {dis[v[mark2]] = w[mark2];mark2 = next[mark2];}
/*** 初始化堆* */size = n;
for (
int i =
1; i <= size; i++) {h[i] = i;pos[i] = i;}
for (
int i = size /
2; i >=
1; i--) {siftdown(i);}
/*** 直接彈出堆頂元素* */pop();
while (count < n) {mark = pop();book[mark] =
1;count++;sum += dis[mark];
/*** 掃描mark的所有邊,以j為中間節(jié)點,進行松弛* */mark2 = first[mark];
while (mark2 != -
1) {
if (book[v[mark2]] ==
0 && dis[v[mark2]] > w[mark2]) {dis[v[mark2]] = w[mark2];
/*** 對該點在堆中進行向上調(diào)整,pos[v[k]]是訂點v[k]在堆中的位置* */siftup(pos[v[mark2]]);}mark2 = next[mark2];}}}
private static void siftup(
int i) {
int flag =
0;
/*** 堆頂* */if (i ==
1) {
return;}
while (i !=
1 && flag ==
0) {
/*** 當前節(jié)點是否小于父結(jié)點* */if (dis[h[i]] < dis[h[i/
2]]) {swap(i, i/
2);}
else {flag =
1;}
/*** 向上調(diào)整* */i = i/
2;}}
private static void swap(
int x,
int y) {
int temp = h[x];h[x] = h[y];h[y] = temp;temp = pos[h[x]];pos[h[x]] = pos[h[y]];pos[h[y]] = temp;}
private static void siftdown(
int i) {
int t, flag =
0;
while (i *
2 <= size && flag ==
0) {
if (dis[h[i]] > dis[h[i*
2]]) {t = i *
2;}
else {t = i;}
if (i *
2 +
1 <= size) {
if (dis[h[t]] > dis[h[i *
2 +
1]]) {t = i *
2 +
1;}}
if (t != i) {swap(t, i);i = t;}
else {flag =
1;}}}
private static int pop() {
/*** 記錄棧頂元素* */int t = h[
1];pos[t] =
0;
/*** 將堆底元素賦到堆頂* */h[
1] = h[size];pos[h[
1]] =
1;
/*** 棧元素數(shù)目減 1* */size--;siftdown(
1);
return t;}
}
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎
總結(jié)
以上是生活随笔為你收集整理的图论算法(四)--最小生成树的Kruskal [ 加边 ] 、Prim [ 加点 ] 的解法(JAVA)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。