1. 程式人生 > >有向無環圖路==路徑規劃

有向無環圖路==路徑規劃

一道面試題 

我們有⼀一個有向⽆無環圖,權重在節點上。
需求:從⼀一個起點開始,找到⼀一條節點權重之和最⼤大的最優路路徑。
輸⼊入: 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);
    }
}