1. 如何构造图
-
邻接矩阵(二维数组)
图的邻接矩阵存储方式是用两个数组来表示图,一个一维数组存储图中顶点信息,一个二维数组存储图中边或弧的信息。 -
邻接表
图中顶点信息用一个一维数组存储,还需存储指向第一个邻接点的指针,以便于查找该顶点的边信息。
其中每个顶点的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储。无向图成为顶点v的边表,有向图成为顶点v作为弧尾的出边表。 -
图示
-
等等其他表示方法
2. 拓扑排序概念
在图论中,如果一个有向图从任意顶点出发无法经过若干条边回到该点,则这个图是一个有向无环图(DAG图)。
由一个有向无环图的顶点组成的序列,当且仅当满足下列条件时,称为该图的一个拓扑排序(Topological sorting):
- 每个顶点出现且只出现一次;
- 若A在序列中排在B的前面,则在图中不存在从B到A的路径,即如果存在一条从顶点A到顶点B的路径(B依赖A),那么在排序结果中A在B的前面。
3. 拓扑排序代码实现
图的拓扑排序有两种算法:
/*
构造图
*/
class Graph {
// 图的表示:Map的key即所有顶点,value即与所关联的顶点
private Map<Vertex, List<Vertex>> mGraph = new HashMap<>();
// 添加一个节点
void addNode(Vertex vertex) {
if (mGraph.containsKey(vertex)) {
return;
}
mGraph.put(vertex, null);
}
//添加一个有向边, node --> edge
void addEdge(Vertex node, Vertex edge) {
if (!mGraph.containsKey(node)) {
//不包含该节点肯定不行
return;
}
if (!mGraph.containsKey(edge)) {
// 该图中都没有该节点,如何连边?先去添加node
return;
}
List<Vertex> edges = mGraph.get(node);
if (edges == null) {
edges = new ArrayList<>();
mGraph.put(node, edges);
}
edges.add(edge);
}
/**
* 顶点个数
*/
int nodesCount() {
return mGraph.size();
}
/**
* 获取节点的入度
*/
int getIncomingEdges(Vertex node) {
//获取所有其他节点指向它的个数
Collection<List<Vertex>> values = mGraph.values();
int incomingEdges = 0;
for (List<Vertex> value : values) {
if (value == null) continue;
if (value.contains(node)) {
++incomingEdges;
}
}
return incomingEdges;
}
List<Vertex> getIncomingNodes(Vertex node) {
Collection<List<Vertex>> values = mGraph.values();
List<Vertex> list = new ArrayList<>();
for (List<Vertex> value : values) {
if (value == null) continue;
if (value.contains(node)) {
list.add(node);
}
}
return list;
}
/**
* 获取节点的出度
*/
int getOutgoingEdges(Vertex node) {
if (!mGraph.containsKey(node)) {
return 0;
}
return mGraph.get(node).size();
}
List<Vertex> getOutgoingNodes(Vertex node) {
if (!mGraph.containsKey(node)) {
return null;
}
return mGraph.get(node);
}
Set<Vertex> getNodes() {
return mGraph.keySet();
}
}
- Kahn算法
class KahnTopologicalSort {
/*
L ← Empty list that will contain the sorted elements
S ← Set of all nodes with no incoming edges
while S is non-empty do
remove a node n from S
add n to tail of L
for each node m with an edge e from n to m do
remove edge e from the graph
if m has no other incoming edges then
insert m into S
if graph has edges then
return error (graph has at least one cycle)
else
return L (a topologically sorted order)
*/
private Graph mGraph;
private List<Vertex> mSortResult;// 拓扑排序后结果,即 L
private Stack<Vertex> mSetOfZeroIndegree; // 入度为0的顶点队列,即 S
private Map<Vertex, Integer> mIndegrees; //记录每个顶点当前的入度
KahnTopologicalSort(Graph graph) {
this.mGraph = graph;
mSortResult = new ArrayList<>();
mSetOfZeroIndegree = new Stack<>();
mIndegrees = new HashMap<>();
//初始化节点入读
for (Vertex vertex : graph.getNodes()) {
int incomingEdges = graph.getIncomingEdges(vertex);
mIndegrees.put(vertex, incomingEdges);
if (incomingEdges == 0) {
mSetOfZeroIndegree.push(vertex);
}
}
}
List<Vertex> kahnSort() {
while (!mSetOfZeroIndegree.isEmpty()) {
Vertex poll = mSetOfZeroIndegree.pop();
mSortResult.add(poll);
List<Vertex> outgoingNodes = mGraph.getOutgoingNodes(poll);
if (outgoingNodes == null) {
continue;
}
for (Vertex vertex : outgoingNodes) {
int degree = mIndegrees.get(vertex);
--degree;
mIndegrees.put(vertex, degree);
if (0 == degree) {
mSetOfZeroIndegree.push(vertex);
}
}
}
if(mSortResult.size() != mGraph.nodesCount()){
throw new RuntimeException("This graph contains cyclic dependencies");
}
return mSortResult;
}
}
- 基于DFS的算法
class DFSTopologicalSort {
/*
L ← Empty list that will contain the sorted nodes
while there are unmarked nodes do
select an unmarked node n
visit(n)
function visit(node n)
if n has a temporary mark then stop (not a DAG)
if n is not marked (i.e. has not been visited yet) then
mark n temporarily
for each node m with an edge from n to m do
visit(m)
mark n permanently
unmark n temporarily
add n to head of L
*/
private Graph mGraph;
private List<Vertex> mSortResult;
private Set<Vertex> mSortTmpMarked;
DFSTopologicalSort(Graph graph) {
this.mGraph = graph;
mSortResult = new ArrayList<>();
mSortTmpMarked = new HashSet<>();
}
List<Vertex> DFSSort() {
mSortResult.clear();
mSortTmpMarked.clear();
for (Vertex vertex : mGraph.getNodes()) {
dfs(vertex, mSortResult, mSortTmpMarked);
}
Collections.reverse(mSortResult);
return mSortResult;
}
private void dfs(Vertex node, List<Vertex> result, Set<Vertex> tmpMarked) {
if (result.contains(node)) {
return;
}
if (tmpMarked.contains(node)) {
throw new RuntimeException("This graph contains cyclic dependencies");
}
tmpMarked.add(node);
List<Vertex> outgoingNodes = mGraph.getOutgoingNodes(node);
if (outgoingNodes != null) {
for (Vertex outgoingNode : outgoingNodes) {
dfs(outgoingNode, result, tmpMarked);
}
}
tmpMarked.remove(node);
result.add(node);
}
}
4. 算法复杂度
O(E + V),结点个数和边个数。