单源最短路径问题——分支限界法(Java)
1、 前置芝士
1.1 分支限界法求解目標
分支限界法與回溯法的不同求解目標:
- 回溯法的求解目標:找出解空間樹中滿足約束條件的所有解;
- 分支限界法的求解目標:找出滿足約束條件的一個解,或是在滿足約束條件的解中找出使用某一目標函數值達到極大或極小的解,即在某種意義下的最優解。
1.2 分支限界法引言
分支限界法與回溯法的不同搜索方式:
- 回溯法以深度優先的方式搜索解空間樹,而分支限界法則以廣度優先或以最小耗費優先的方式搜索解空間樹。
- 分支限界法的搜索策略:在擴展結點處,先生成其所有的兒子結點(分支),然后再從當前的活結點表中選擇下一個擴展對點。為了有效地選擇下一擴展結點,以加速搜索的進程,在每一活結點處,計算一個函數值(限界),并根據這些已計算出的函數值,從當前活結點表中選擇一個最有利的結點作為擴展結點,使搜索朝著解空間樹上有最優解的分支推進,以便盡快地找出一個最優解。
1.3 分支限界法基本思想
- 分支限界法通常以廣度優先或以最小耗費(最大效益)優先的方式搜索問題的解空間樹。
- 問題的解空間樹是表示問題解空間的一棵有序樹,常見的有子集樹和排列樹。
1.4 兩種典型的解空間樹
子集樹(Subset Trees):
當所給問題是從n個元素的集合中找出滿足某種性質的子集時,相應的解空間樹稱為子集樹。在子集樹中,|S0|=|S1|=…=|Sn-1|=c,即每個結點有相同數目的子樹,通常情況下c=2,所以,子集樹中共有2n個葉子結點,因此,遍歷子集樹需要O(2n)時間。
排列樹(Permutation Trees):
當所給問題是確定n個元素滿足某種性質的排列時,相應的解空間樹稱為排列樹。在排列樹中,通常情況下,|S0|=n,|S1|=n-1,…,|Sn-1|=1,所以,排列樹中共有n!個葉子結點,因此,遍歷排列樹需要O(n!)時間。
2、分支限界法解題過程
2.1 算法要點
- 在分支限界法中,每一個活結點只有一次機會成為擴展結點。
- 活結點一旦成為擴展結點,就一次性產生其所有兒子結點。在這些兒子結點中,導致不可行解或導致非最優解的兒子結點被舍棄,其余兒子結點被加入活結點表中。
- 從活結點表中取下一結點成為當前擴展結點,并重復上述結點擴展過程
- 這個過程一直持續到找到所求的解或活結點表為空時為止。
活結點表:具有先進先出的性質,是隊列
2.2 兩個重要機制
- 產生分支(解空間樹)
- 產生一個界限,能夠終止許多分支(剪枝)
2.3 適用范圍
- 分支限界法類似于回溯法,有一些問題其實無論用回溯法還是分支限界法都可以得到很好的解決,但是另外一些則不然。
- 下表列出了回溯法和分支限界法的一些區別:
2.4 兩種方式
從活結點表中選擇下一擴展結點的不同方式導致不同的分支限界法。
最常見的有以下兩種方式:
- 隊列式(FIFO)分支限界法:隊列式分支限界法將活結點表組織成一個隊列,并按隊列的先進先出原則選取下一個結點為當前擴展結點。
- 優先隊列式分支限界法:優先隊列式分支限界法將活結點表組織成一個優先隊列,按優先隊列中規定的結點優先級選取優先級最高的下一個結點成為當前擴展結點。
常用堆來實現優先隊列
3、單源最短路徑問題
3.1 問題描述
給定帶權有向圖G =(V,E),其中每條邊的權是非負實數.另外,還給定V中的一個頂點,稱為源。現在要計算從源到所有其它各頂點的最短路長度。這里路的長度是指路上各邊權之和。這個問題通常稱為單源最短路徑問題。
用優先隊列式分支限界法解有向圖G的單源最短路徑問題產生的解空間樹。其中,每一個結點內數字表示該結點所對應的當前路長
3.2 圖解題目
4、程序代碼
import java.util.ArrayList; import java.util.List; import java.util.PriorityQueue; import java.util.Scanner;/*** TODO* 11 19* SA 2 SB 3 SC 4 AF 7 AB 3 AE 2 BE 9 BD 2 CD 2 FG 9 FH 1 EH 3 ED 1 DI 1 DH 5 GT 7 HT 8 IH 2 IT 2*/ public class t1 {static int N; // 節點個數static int EDGES; // 邊的數量static float[][] adj; // 鄰接矩陣public static void main(String[] args) {Scanner scanner = new Scanner(System.in);System.out.print("input the number of vertix and edge:");N = scanner.nextInt();EDGES = scanner.nextInt();adj = new float[N + 1][N + 1];for (int i = 0; i < adj.length; i++) {for (int j = 0; j < adj.length; j++) {adj[i][j] = Float.MAX_VALUE;}}System.out.println("please input vertix and weight:");for (int i = 0; i < EDGES; i++) {String vertix = scanner.next();int startPos = vertix.charAt(0) - 'A' + 1, targetPos = vertix.charAt(1) - 'A' + 1;if (vertix.charAt(0) == 'S') {startPos = 0;}if (vertix.charAt(1) == 'T') {targetPos = N - 1;}float weight = scanner.nextFloat();adj[startPos][targetPos] = weight;}for (int i = 0; i < N; i++) {for (int j = 0; j < N; j++) {if (adj[i][j] != Float.MAX_VALUE && adj[i][j] != 0 && i != j) {char start = (char) ('A' + i - 1), end = (char) ('A' + j - 1);if (i == 0) {start = 'S';}if (j == N - 1) {end = 'T';}System.out.println("(" + start + ", " + end + ") = " + adj[i][j]); // System.out.println("(" + i + ", " + j + ") = " + adj[i][j]);}}}int[] path = new int[N + 1]; // path[i]:記錄最佳路徑中,i的上一個頂點是path[i]float[] dist = new float[N + 1]; // dist[i]:從源點到當前頂點i的距離int vertix = 0;for (int j = 1; j <= N; j++) {dist[j] = Float.MAX_VALUE;}shortest(vertix, adj, dist, path);System.out.print("最小堆求解的路徑為:"); // for (int i = 1; i < path.length; i++) { // System.out.println(path[i]); // } // System.out.println("------------------------");// TODO 10 9 4 2 0List<Integer> list = new ArrayList<>();list.add(N - 1);list.add(path[N - 1]);while (true) {if (path[list.get(list.size() - 1)] == 0) {list.add(0);break;}list.add(path[list.get(list.size() - 1)]);}// for (int i = 0; i < list.size(); i++) { // System.out.println(list.get(i)); // } // System.out.println("-------------------------");for (int j = list.size() - 1; j >= 0; j--) {if (list.get(j) == 0) {System.out.print("S-->");} else {char c = (char) (list.get(j) + 'A' - 1);if (j != 0) {System.out.print(c + "-->");} else {System.out.println('T');}}}System.out.println("從頂點S到各頂點最短距離:");for (int i = 1; i < dist.length - 1; i++) {char end = (char) ('A' + i - 1);System.out.print("dist[" + end + "] = " + dist[i] + " ");}System.out.println("dist[" + 'T' + "] = " +dist[10]);}public static void shortest(int v, float[][] adj, float[] dist, int[] path) {int n = path.length - 1;HeapNode enode = new HeapNode(v, 0);PriorityQueue<HeapNode> pq = new PriorityQueue<>();pq.offer(enode);while (!pq.isEmpty()) {HeapNode pollNode = pq.poll();int start = pollNode.vIdx;float currStep = pollNode.step;// 搜索問題的解空間for (int i = 1; i <= n; i++) {if (adj[start][i] <= Float.MAX_VALUE && pollNode.step + adj[start][i] < dist[i]) {dist[i] = currStep + adj[start][i];path[i] = start;HeapNode node = new HeapNode(i, dist[i]);pq.offer(node); // System.out.println(start + "-->" + i);}}}}static class HeapNode implements Comparable {int vIdx; // 頂點編號float step; // 當前路長public HeapNode(int vIdx, float step) {this.vIdx = vIdx;this.step = step;}@Overridepublic int compareTo(Object o) {float oLen = ((HeapNode) o).step;if (step < oLen) {return -1;}if (step == oLen) {return 0;}return 1;}} } 復制代碼其中,shorest方法的while()代碼塊可以用以下代碼替換
while (true) {// 搜索問題的解空間for (int i = 1; i <= n; i++) {if (adj[enode.vIdx][i] < Float.MAX_VALUE && enode.step + adj[enode.vIdx][i] < dist[i]) {dist[i] = enode.step + adj[enode.vIdx][i];path[i] = enode.vIdx;HeapNode node = new HeapNode(i, dist[i]);pq.offer(node);}}if (pq.isEmpty()) {break;} else {// 移除enode = (HeapNode) pq.poll();}} 復制代碼運行結果
5、參考資料
- 算法設計與分析(第四版)
結束!
作者:7_
原文鏈接:https://juejin.cn/post/7176061385477423162
?
總結
以上是生活随笔為你收集整理的单源最短路径问题——分支限界法(Java)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 陕西省宝鸡市高考成绩查询2021年,20
- 下一篇: 【数据分析实战】基于python对Air