图的深度优先和广度优先遍历及两点间最优路径实现

通用遍历

参考: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;
}
}

    原文作者:数据结构之图
    原文地址: https://blog.csdn.net/mike_caoyong/article/details/53573969
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞