有向無環圖路==路徑規劃
阿新 • • 發佈:2018-12-11
一道面試題
我們有⼀一個有向⽆無環圖,權重在節點上。
需求:從⼀一個起點開始,找到⼀一條節點權重之和最⼤大的最優路路徑。
輸⼊入: n個節點,m個路路徑,起點
輸出: 最優路路徑的權重值之和
舉例例:
3個節點與權重: A=1, B=2, C=2
3條路路徑: A->B, B->C, A->C
起點: A
輸出: 5 (最優路路徑是 A->B->C , 權重之和是 1+2+2=5)
請考慮演算法效率優化,考慮異常情況(⽐比如輸⼊入的圖有環路路)要避免死迴圈或者崩潰。
實現如下:
揹包實現
import java.util.Iterator; // 定義一個揹包集合,支援泛型,支援迭代 @SuppressWarnings("all") public class Bag<Item> implements Iterable<Item> { private class BagNode<Item> { Item item; BagNode next; } BagNode head; int size; @Override public Iterator<Item> iterator() { return new Iterator<Item>() { BagNode node = head; @Override public boolean hasNext() { return node.next != null; } @Override public Item next() { Item item = (Item) node.item; node = node.next; return item; } }; } public Bag() { head = new BagNode(); size = 0; } // 往前插入 public void add(Item item) { BagNode temp = new BagNode(); // 以下兩行程式碼一定要宣告,不可直接使用temp = head,那樣temp賦值的是head的引用,對head的所有修改會直接同步到temp,temp就不具備快取的功能,引發bug。。 temp.next = head.next; temp.item = head.item; head.item = item; head.next = temp; size++; } public boolean isEmpty() { return size == 0; } public int size() { return this.size; } }
節點資料封裝類
public class BagData { private Integer index; private String nodeName; private Integer nodeValue; public BagData(){ } public BagData(Integer index,String nodeName, Integer nodeValue) { super(); this.index = index; this.nodeName = nodeName; this.nodeValue = nodeValue; } public String getNodeName() { return nodeName; } public void setNodeName(String nodeName) { this.nodeName = nodeName; } public Integer getNodeValue() { return nodeValue; } public void setNodeValue(Integer nodeValue) { this.nodeValue = nodeValue; } public Integer getIndex() { return index; } public void setIndex(Integer index) { this.index = index; } @Override public String toString() { return "BagData [index=" + index + ", nodeName=" + nodeName + ", nodeValue=" + nodeValue + "]"; } }
圖的實現
import java.util.HashMap; import java.util.Map; import java.util.Stack; public class DigraphV2 { private final int V;// 頂點總數,定義final,第一次初始化以後不可更改。 private int E;// 邊總數 private Bag<BagData>[] adj;// {鄰接表}頂點為陣列下標,值為當前下標為頂點值所連通的頂點個數。 private Map<Integer,BagData> points;// = new HashMap<>(); public DigraphV2(int v) { points = new HashMap<>(); this.V = v; this.E = 0; adj = new Bag[V]; for (int i = 0; i < V; i++) { adj[i] = new Bag<BagData>(); } } public int V() { return this.V; } public int E() { return this.E; } public Map<Integer, BagData> getPoints() { return points; } /** * v和w是兩個頂點,中間加一條邊,增加稠密度。 * * @param v 大V是頂點總數,v是頂點值,所以並v不存在大小限制 * @param w 同上。 */ public void addEdge(BagData v, BagData w) { if(!points.containsKey(v.getIndex())) points.put(v.getIndex(), v); if(!points.containsKey(w.getIndex())) points.put(w.getIndex(), w); adj[v.getIndex()].add(w); E++; } /** * 返回一個頂點的連通頂點集合的迭代器 * * @param v * @return Bag本身就是迭代器,所以返回該頂點的連通頂點集合Bag即可。 */ public Iterable<BagData> adj(int v) { return adj[v]; } /** * 將圖中所有方向反轉 * * @return 返回一個圖將所有方向反轉後的副本 */ public DigraphV2 reverse() { DigraphV2 R = new DigraphV2(V); for (int v = 0; v < V; v++) { for (BagData w : adj[v]) {// 遍歷原圖中跟v頂點連通的頂點w。 R.addEdge(w, points.get(v)); } } return R; } /** * 按照鄰接表陣列結構輸出有向圖內容 * * @return */ public String toString() { String s = points.size() + " vertices, " + E + " edges\n"; for (int v = 0; v < points.size(); v++) { s += points.get(v).toString() + ": "; for (BagData w : this.adj(v)) { s += w.toString(); } s += "\n"; } return s; } public String getWeightPath() { Map<Integer,String> weightPaths = new HashMap<>(); for (int v = 0; v < points.size(); v++) { int weights = 0; String path = ""; weights += points.get(v).getNodeValue(); path += points.get(v).getNodeName(); path += "->"; Stack<BagData> temp = new Stack<>(); for (BagData w : this.adj(v)) { temp.push(w); } for(;;){ if(temp.isEmpty())break; BagData w = temp.pop(); weights += w.getNodeValue(); path += w.getNodeName(); path += "->"; } path = path.substring(0, path.length()-2); weightPaths.put(weights, path); } Map.Entry<Integer, String> result = weightPaths.entrySet().stream().max((e1,e2)->e1.getKey().compareTo(e2.getKey())).get(); return "path = "+result.getValue()+" weight = "+result.getKey(); } }
有環無向圖的實現 以及路徑權重計算
import java.util.Stack;
public class DirectedCycleV2 {
private boolean[] marked;// 以頂點為索引,值代表了該頂點是否標記過(是否可達)
private Stack<Integer> cycle; // 用來儲存有向環頂點。
// *****重點理解這裡start****
private int[] edgeTo;// edgeTo[0]=1代表頂點1->0, to 0的頂點為1。
// *****重點理解這裡end****
private boolean[] onStack;// 頂點為索引,值為該頂點是否參與dfs遞迴,參與為true
public DirectedCycleV2(DigraphV2 digraph) {
// 初始化成員變數
marked = new boolean[digraph.V()];
onStack = new boolean[digraph.V()];
edgeTo = new int[digraph.V()];
cycle = null;
// 檢查是否有環
for (int v = 0; v < digraph.V(); v++) {
dfs(digraph, v);
}
}
public String getWeightPath(DigraphV2 d){
String result = "";
if (this.hasCycle()) {
System.out.println("輸入錯誤,圖是有環路的!");
} else {
result = d.getWeightPath();
}
return result;
}
private void dfs(DigraphV2 digraph, int v) {
onStack[v] = true;// 遞迴開始,頂點上棧
marked[v] = true;
for (BagData w : digraph.adj(v)) {// 遍歷一條邊,v-> w
// 終止條件:找到有向環
if (hasCycle()) return;
// 使用onStack標誌位來記錄有效路徑上的點,如果w在棧上,說明w在前面當了出發點,
if (!marked[w.getIndex()]) {
edgeTo[w.getIndex()] = v;// to w的頂點為v
dfs(digraph, w.getIndex());
} else if (onStack[w.getIndex()]) {// 如果指到了已標記的頂點,且該頂點遞迴棧上。(棧上都是出發點,而找到了已標記的頂點是終點,說明出發點和終點相同了。)
cycle = new Stack<Integer>();
for (int x = v; x != w.getIndex(); x = edgeTo[x]) {//起點在第一次迴圈中已經push了,不要重複
cycle.push(x);// 將由v出發,w結束的環上中間的結點遍歷push到cycle中。
}
cycle.push(w.getIndex());// push終點
}
}
onStack[v] = false;// 當遞迴開始結算退出時,頂點下棧。
}
public boolean hasCycle() {
return cycle != null;
}
public Iterable<Integer> cycle() {
return cycle;
}
public static void main(String[] args) {
DigraphV2 d = new DigraphV2(3);
BagData A = new BagData(0 , "A" , 1);
BagData B = new BagData(1 , "B" , 2);
BagData C = new BagData(2 , "C" , 2);
d.addEdge(A, B);
d.addEdge(B, C);
d.addEdge(A, C);
d.addEdge(C, A);
DirectedCycleV2 directedCycle = new DirectedCycleV2(d);
String result = directedCycle.getWeightPath(d);
System.out.println(result);
}
}