1. 程式人生 > >圖的深度優先和廣度優先遍歷及兩點間最優路徑實現

圖的深度優先和廣度優先遍歷及兩點間最優路徑實現

通用遍歷

參考:https://segmentfault.com/a/1190000002685939

遍歷
圖的遍歷,所謂遍歷,即是對結點的訪問。一個圖有那麼多個結點,如何遍歷這些結點,需要特定策略,一般有兩種訪問策略:

深度優先遍歷
廣度優先遍歷
深度優先
深度優先遍歷,從初始訪問結點出發,我們知道初始訪問結點可能有多個鄰接結點,深度優先遍歷的策略就是首先訪問第一個鄰接結點,然後再以這個被訪問的鄰接結點作為初始結點,訪問它的第一個鄰接結點。總結起來可以這樣說:每次都在訪問完當前結點後首先訪問當前結點的第一個鄰接結點。


我們從這裡可以看到,這樣的訪問策略是優先往縱向挖掘深入,而不是對一個結點的所有鄰接結點進行橫向訪問。


具體演算法表述如下:


訪問初始結點v,並標記結點v為已訪問。
查詢結點v的第一個鄰接結點w。
若w存在,則繼續執行4,否則演算法結束。
若w未被訪問,對w進行深度優先遍歷遞迴(即把w當做另一個v,然後進行步驟123)。
查詢結點v的w鄰接結點的下一個鄰接結點,轉到步驟3。
例如下圖,其深度優先遍歷順序為 1->2->4->8->5->3->6->7


廣度優先
類似於一個分層搜尋的過程,廣度優先遍歷需要使用一個佇列以保持訪問過的結點的順序,以便按這個順序來訪問這些結點的鄰接結點。


具體演算法表述如下:


訪問初始結點v並標記結點v為已訪問。
結點v入佇列
當佇列非空時,繼續執行,否則演算法結束。
出佇列,取得隊頭結點u。
查詢結點u的第一個鄰接結點w。
若結點u的鄰接結點w不存在,則轉到步驟3;否則迴圈執行以下三個步驟:
1). 若結點w尚未被訪問,則訪問結點w並標記為已訪問。
2). 結點w入佇列
3). 查詢結點u的繼w鄰接結點後的下一個鄰接結點w,轉到步驟6。
如上圖,其廣度優先演算法的遍歷順序為:1->2->3->4->5->6->7->8

Java實現 GraphSearchImpl.java類


import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;


public class GraphSearchImpl
{
private List<Integer> nodeList;// 儲存點的連結串列
private boolean[] visited; // 是否遍歷標識
private int[][] edges;// 鄰接矩陣,用來儲存邊
private int edgeNum;// 邊的數目
private int nodeNum;


public GraphSearchImpl(int n)
{
// 初始化矩陣,一維陣列,和邊的數目
edges = new int[n][n];
nodeList = new ArrayList<Integer>(n);
visited = new boolean[n];
edgeNum = 0;
nodeNum = n;
initVisited();
}

// 返回v1,v2的權值
public int getWeight(int v1, int v2)
{
return edges[v1][v2];
}

// 插入結點
public void insertNodeData(Integer nodeId)
{
nodeList.add(nodeList.size(), nodeId);
}


// 插入結點
public void insertEdge(int v1, int v2, int weight)
{
edges[v1][v2] = weight;
edgeNum++;
}

// 刪除結點
public void deleteEdge(int v1, int v2)
{
edges[v1][v2] = 0;
edgeNum--;
}


// 對外公開函式,深度優先遍歷,與其同名私有函式屬於方法過載
public void depthFirstSearch()
{
for (int i = 0; i < nodeNum; i++)
{
// 因為對於非連通圖來說,並不是通過一個結點就一定可以遍歷所有結點的。
if (!visited[i])
{
depthFirstSearch(visited, i);
}
}
}

// 對外公開函式,廣度優先遍歷
public void broadFirstSearch()
{
for (int i = 0; i < nodeNum; i++)
{
if (!visited[i])
{
broadFirstSearch(visited, i);
}
}
}


public void initVisited()
{
int len = visited.length;
for (int i = 0; i < len; i++)
{
visited[i] = false;
}
}

// 私有函式,深度優先遍歷
private void depthFirstSearch(boolean[] isVisited, int i)
{
// 首先訪問該結點,在控制檯打印出來
System.out.print(getNodeId(i) + "  ");
// 置該結點為已訪問
isVisited[i] = true;


int w = getFirstNeighbor(i);
while (w != -1)
{
if (!isVisited[w])
{
depthFirstSearch(isVisited, w);
}
w = getNextNeighbor(i, w);
}
}


// 私有函式,廣度優先遍歷
private void broadFirstSearch(boolean[] isVisited, int i)
{
int u, w;
LinkedList<Integer> queue = new LinkedList<Integer>();


// 訪問結點i
System.out.print(getNodeId(i) + "  ");
isVisited[i] = true;
// 結點入佇列
queue.addLast(i);
while (!queue.isEmpty())
{
u = ((Integer) queue.removeFirst()).intValue();
w = getFirstNeighbor(u);
while (w != -1)
{
if (!isVisited[w])
{
// 訪問該結點
System.out.print(getNodeId(w) + "  ");
// 標記已被訪問
isVisited[w] = true;
// 入佇列
queue.addLast(w);
}
// 尋找下一個鄰接結點
w = getNextNeighbor(u, w);
}
}
}


// 得到第一個鄰接結點的下標
private int getFirstNeighbor(int index)
{
for (int j = 0; j < nodeList.size(); j++)
{
if (edges[index][j] > 0)
{
return j;
}
}
return -1;
}


// 根據前一個鄰接結點的下標來取得下一個鄰接結點
private int getNextNeighbor(int v1, int v2)
{
for (int j = v2 + 1; j < nodeList.size(); j++)
{
if (edges[v1][j] > 0)
{
return j;
}
}
return -1;
}


private Integer getNodeId(int index)
{
return nodeList.get(index);
}


}

Java實現 GraphSearchTest.java類

public class GraphSearchTest
{
public static void main(String[] args)
{
int nodeNumber = 8; // 代表節點個數
        int nodeIds[] ={1,2,3,4,5,6,7,8};//節點的標識
        GraphSearchImpl graph =new GraphSearchImpl(nodeNumber);
        for(Integer nodeId :nodeIds) {
            graph.insertNodeData(nodeId);//插入節點
        }
        //插入九條邊
        graph.insertEdge(0, 1, 1);
        graph.insertEdge(0, 2, 1);
        graph.insertEdge(1, 3, 1);
        graph.insertEdge(1, 4, 1);
        graph.insertEdge(3, 7, 1);
        graph.insertEdge(4, 7, 1);
        graph.insertEdge(2, 5, 1);
        graph.insertEdge(2, 6, 1);
        graph.insertEdge(5, 6, 1);
        graph.insertEdge(1, 0, 1);
        graph.insertEdge(2, 0, 1);
        graph.insertEdge(3, 1, 1);
        graph.insertEdge(4, 1, 1);
        graph.insertEdge(7, 3, 1);
        graph.insertEdge(7, 4, 1);
        graph.insertEdge(6, 2, 1);
        graph.insertEdge(5, 2, 1);
        graph.insertEdge(6, 5, 1);
        
        System.out.println("深度優先搜尋序列為:");
        graph.depthFirstSearch();
        System.out.println();
        System.out.println("廣度優先搜尋序列為:");
        graph.initVisited();
        graph.broadFirstSearch();
}
}

自已實現的深度優先和廣度優先查詢已知兩點間的最優距離以及兩點間的多條路徑

測試類:
package com.caoyong.path;


import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;


public class PathDemoTest
{
public static void main(String[] args)
{
int beginNodeId = 1;
int endNodeId = 7;
List<LinkInfo> linkList = initData();


PathArithmetic pathArithmetic = new PathArithmetic(linkList);
ReturnStruct<LinkInfo> result = pathArithmetic.depthFirstSearch(beginNodeId, endNodeId);
System.out.println("depthFirstSearch result: " + result.isSuccess());
for (Entry<String, List<LinkInfo>> entry : result.getPathMap().entrySet())
{
System.out.println("****path:" + entry.getKey());
}


result = pathArithmetic.mulDepthFirstSearch(beginNodeId, endNodeId);
System.out.println("mulDepthFirstSearch result: " + result.isSuccess());
for (Entry<String, List<LinkInfo>> entry : result.getPathMap().entrySet())
{
System.out.println("****path:" + entry.getKey());
}


result = pathArithmetic.breadthFirstSearch(beginNodeId, endNodeId);
System.out.println("breadthFirstSearch result: " + result.isSuccess());
for (Entry<String, List<LinkInfo>> entry : result.getPathMap().entrySet())
{
System.out.println("****path:" + entry.getKey());
}


}


/**
* 1 --> 7 有三條路 
     * path:1[1,2]-->3[2,4]-->5[4,6]-->9[6,7]
* path:2[1,3]-->4[3,5]-->6[5,6]-->9[6,7]
* path:7[1,8]-->8[8,6]-->9[6,7]
*/
private static List<LinkInfo> initData()
{
List<LinkInfo> linkList = new ArrayList<LinkInfo>();
// 1-5有兩條路: 1-2-4-6-5,1-3-5
LinkInfo link = new LinkInfo(1, 1, 2);
linkList.add(link);


link = new LinkInfo(2, 1, 3);
linkList.add(link);


link = new LinkInfo(3, 2, 4);
linkList.add(link);


link = new LinkInfo(4, 3, 5);
// link.setWeight(10);
linkList.add(link);


link = new LinkInfo(5, 4, 6);
linkList.add(link);


link = new LinkInfo(6, 5, 6);
linkList.add(link);


link = new LinkInfo(7, 1, 8);
// link.setWeight(10);
linkList.add(link);


link = new LinkInfo(8, 8, 6);
linkList.add(link);


link = new LinkInfo(9, 6, 7);
linkList.add(link);


return linkList;
}


}




邊物件LinkInfo類:
package com.caoyong.path;


public class LinkInfo
{
private int linkId;


private int sourceNodeId;


private int destNodeId;


// 權重
private int weight = 1;

// 是否訪問過
private boolean visit;


public LinkInfo(int linkId, int sourceNodeId, int destNodeId)
{
this.linkId = linkId;
this.sourceNodeId = sourceNodeId;
this.destNodeId = destNodeId;
}


public int getLinkId()
{
return linkId;
}


public void setLinkId(int linkId)
{
this.linkId = linkId;
}


public int getSourceNodeId()
{
return sourceNodeId;
}


public void setSourceNodeId(int sourceNodeId)
{
this.sourceNodeId = sourceNodeId;
}


public int getDestNodeId()
{
return destNodeId;
}


public void setDestNodeId(int destNodeId)
{
this.destNodeId = destNodeId;
}


public int getWeight()
{
return weight;
}


public void setWeight(int weight)
{
this.weight = weight;
}


public boolean isVisit()
{
return visit;
}


public void setVisit(boolean visit)
{
this.visit = visit;
}


@Override
public boolean equals(Object obj)
{
if (obj instanceof LinkInfo)
{
LinkInfo o = (LinkInfo) obj;
return o.getLinkId() == linkId;
}


return false;
}


@Override
public int hashCode()
{
return linkId;
}


@Override
public String toString()
{
return "linkId=" + linkId + " sId=" + sourceNodeId + " dId=" + destNodeId;
}


}


資料返回類ReturnStruct:
package com.caoyong.path;


import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * 返回結構體
 * 
 * @author cy
 *
 * @param <T>
 */
public class ReturnStruct<T>
{
private boolean success;


private String errorInfo;


// 多條路徑MAP集合(key:路徑key,value:對應路徑元素集合)
private Map<String, List<T>> pathMap = new HashMap<String, List<T>>();


public boolean isSuccess()
{
return success;
}


public void setSuccess(boolean success)
{
this.success = success;
}


public String getErrorInfo()
{
return errorInfo;
}


public void setErrorInfo(String errorInfo)
{
this.errorInfo = errorInfo;
}


public Map<String, List<T>> getPathMap()
{
return pathMap;
}


public void setPathMap(Map<String, List<T>> pathMap)
{
this.pathMap = pathMap;
}


public void addPath(String pathKey, List<T> list)
{
pathMap.put(pathKey, list);
}


}


具體實現PathArithmetic類:
package com.caoyong.path;


import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;


/**
 * 路徑演算法實現類
 * 
 * @author cy
 *
 */
public class PathArithmetic
{
// 最大節點ID
private int maxNodeId;


// 連纖列表
private List<LinkInfo> linkList;


public PathArithmetic(List<LinkInfo> linkList)
{
this.linkList = linkList;
for (LinkInfo link : linkList)
{
if (link.getSourceNodeId() > maxNodeId)
{
maxNodeId = link.getSourceNodeId();
}


if (link.getDestNodeId() > maxNodeId)
{
maxNodeId = link.getDestNodeId();
}
}
}


/**
* 深度優化(depth-first search) --返回多條路徑(pathMap)

* @param beginId
*            開始節點ID
* @param endId
*            結束節點ID
* @return ReturnStruct<LinkInfo>
*/
public ReturnStruct<LinkInfo> mulDepthFirstSearch(int beginId, int endId)
{
// 兩點間的多條路徑遍歷
Map<String, List<LinkInfo>> pathMap = new HashMap<String, List<LinkInfo>>();
depthFirstSearch(beginId, endId, null, pathMap);
ReturnStruct<LinkInfo> result = new ReturnStruct<LinkInfo>();
result.setSuccess(true);
if (beginId != endId && pathMap.size() == 0)
{
result.setSuccess(false);
}

for (Entry<String, List<LinkInfo>> entry : pathMap.entrySet())
{
result.addPath(entry.getKey(), entry.getValue());
}

return result;
}


/**
* 深度優化(depth-first search)

* @param beginId
*            開始節點ID
* @param endId
*            結束節點ID
* @return ReturnStruct<LinkInfo>
*/
public ReturnStruct<LinkInfo> depthFirstSearch(int beginId, int endId)
{
// 單條最短路徑
int[] nodeWeights = getNodeArray(); // 權重陣列
int[] nodeLinks = getNodeArray();// 節點鏈路陣列序號為節點ID,對應值為鏈路ID,這個很關鍵當前節點只對應權重小的鏈路
LinkedList<Integer> tempNodes = new LinkedList<Integer>();
tempNodes.add(beginId);
depthFirstSearch(nodeWeights, beginId, endId, nodeLinks);


return getReturnStruct(beginId, endId, nodeLinks);
}


/**
* 深度遍歷--返回多條路徑(pathMap)

* @param beginId
*            開始節點ID
* @param endId
*            線束節點ID
* @param parentPaths
*            父路徑集合
* @param pathMap
*            路徑MAP
*/
private void depthFirstSearch(int beginId, int endId, List<LinkInfo> parentPaths,
Map<String, List<LinkInfo>> pathMap)
{
List<LinkInfo> currentList = getLinkInfoList(beginId);
if (currentList.size() == 0)
{
return;
}


List<LinkInfo> tmpRetList;
for (LinkInfo link : currentList)
{
tmpRetList = new ArrayList<LinkInfo>();
if (null != parentPaths)
{
tmpRetList.addAll(parentPaths);
}
tmpRetList.add(link);
if (link.getDestNodeId() == endId)
{
pathMap.put(getLinkListKey(tmpRetList), tmpRetList);
return;
}
else
{
depthFirstSearch(link.getDestNodeId(), endId, tmpRetList, pathMap);
}
}
}


/**
* 深度遍歷--返回單條最優路徑(pathMap)

* @param beginId
*            開始節點ID
* @param endId
*            線束節點ID
* @param nodeWeights
*            節點權重陣列
* @param nodeLinks
*            節點序號對應的連纖陣列
*/
private void depthFirstSearch(int[] nodeWeights, int beginId, int endId, int[] nodeLinks)
{
LinkInfo link = getNextLinkInfo(beginId);
while (null != link && !link.isVisit())
{
link.setVisit(true);
int destNodeId = link.getDestNodeId();
int tmpWeight = nodeWeights[beginId] + link.getWeight();
if (tmpWeight < nodeWeights[destNodeId] || nodeWeights[destNodeId] == 0)
{
if (tmpWeight > nodeWeights[endId] && nodeWeights[endId] != 0)
{
// 表示當前節點比結束點的權重還高,故不處理,進行下次處理
continue;
}
else
{
if (destNodeId == endId)
{
nodeWeights[destNodeId] = tmpWeight;
nodeLinks[destNodeId] = link.getLinkId();
return;
}
else
{
nodeWeights[destNodeId] = tmpWeight;
nodeLinks[destNodeId] = link.getLinkId();
depthFirstSearch(nodeWeights, destNodeId, endId, nodeLinks);


}
}
}


link = getNextLinkInfo(beginId);
}
}


/**
* 廣度優先遍歷 (breadth-first search)

* @param beginId
*            開始節點ID
* @param endId
*            結束節點ID
* @return ReturnStruct<LinkInfo>
*/
public ReturnStruct<LinkInfo> breadthFirstSearch(int beginId, int endId)
{
int tmpWeight = 0;
int[] nodeWeights = getNodeArray(); // 權重陣列
int[] nodeLinks = getNodeArray();// 節點鏈路陣列 序號為節點ID
// 對應值為鏈路ID,這個很關鍵當前節點只對應權重小的鏈路
LinkedList<Integer> tempNodes = new LinkedList<Integer>();
tempNodes.add(beginId);
while (!tempNodes.isEmpty())
{
int tmpNodeId = tempNodes.pop();
List<LinkInfo> links = getLinkInfoList(tmpNodeId);
for (LinkInfo link : links)
{
int destNodeId = link.getDestNodeId();
tmpWeight = nodeWeights[tmpNodeId] + link.getWeight();
if (nodeWeights[tmpNodeId] < nodeWeights[destNodeId] || nodeWeights[destNodeId] == 0)
{
if (nodeWeights[endId] == 0 || tmpWeight <= nodeWeights[endId])
{
nodeWeights[destNodeId] = nodeWeights[tmpNodeId] + link.getWeight();
tempNodes.add(destNodeId);
nodeLinks[destNodeId] = link.getLinkId();
}
}
}
}


return getReturnStruct(beginId, endId, nodeLinks);
}


private ReturnStruct<LinkInfo> getReturnStruct(int beginId, int endId, int[] nodeLinks)
{
ReturnStruct<LinkInfo> result = new ReturnStruct<LinkInfo>();
List<LinkInfo> retList = new ArrayList<LinkInfo>();
result.setSuccess(true);
if (beginId != endId && nodeLinks[endId] == 0)
{
result.setSuccess(false);
}
else
{
int tempNodeId = endId;
while (beginId != tempNodeId)
{
int linkId = nodeLinks[tempNodeId];
LinkInfo link = getLinkInfoById(linkId);
if (null == link)
{
break;
}


retList.add(link);
tempNodeId = link.getSourceNodeId();
}
}


Collections.reverse(retList);
String pathKey = getLinkListKey(retList);
result.addPath(pathKey, retList);


return result;
}


private String getLinkListKey(List<LinkInfo> linkList)
{
StringBuffer keys = new StringBuffer();
for (LinkInfo link : linkList)
{
if (keys.length() > 0)
{
keys.append("-->");
}
keys.append(link.getLinkId() + "[" + link.getSourceNodeId() + "," + link.getDestNodeId() + "]");
}


return keys.toString();
}


private List<LinkInfo> getLinkInfoList(int sourceNodeId)
{
List<LinkInfo> tmpList = new ArrayList<LinkInfo>();
for (LinkInfo link : linkList)
{
if (link.getSourceNodeId() == sourceNodeId)
{
tmpList.add(link);
}
}


return tmpList;
}


private LinkInfo getNextLinkInfo(int srcNodeId)
{
for (LinkInfo link : linkList)
{
if (!link.isVisit() && link.getSourceNodeId() == srcNodeId)
{
return link;
}
}


return null;
}


private LinkInfo getLinkInfoById(int linkId)
{
for (LinkInfo link : linkList)
{
if (link.getLinkId() == linkId)
{
return link;
}
}


return null;
}


private int[] getNodeArray()
{
int[] nodes = new int[maxNodeId + 1];
for (int i : nodes)
{
nodes[i] = 0;
}


return nodes;
}
}