有向图基础
术语定义:
- 一个顶点的出度为由该顶点指出的边的总数
- 一个顶点的入度为指向该顶点的边的总数
- 一条有向边的第一个顶点称为它的头,第二个顶点称为它的尾
数据结构:
使用邻接表来表示有向图,其中v->w表示为顶点v对应的邻接链表中包含一个w顶点。
算法实现:
public class Digraph {
private int V;//顶点数
private int E;//边数
private Bag[] adj;//邻接表
public Digraph(int V) {
this.V = V;
adj = (Bag[]) new Bag[V];
for(int i=0;i();
}
public int V() {return V;}
public int E() {return E;}
public void addEdge(int v,int w)
{ adj[v].add(w); E++;}
//顶点v所关联的所有顶点
public Iterable adj(int v){return adj[v];}
//有向图的反转
public Digraph reverse() {
Digraph R = new Digraph(V);
for(int v=0; v
深度优先遍历和广度优先遍历
有向图的深度优先遍历和广度优先遍历和无向图一模一样,可以通过之前的无向图遍历问题来学习。https://my.oschina.net/HuoQibin/blog/1592318
拓扑排序
拓扑排序:给定一幅有向图,将所有顶点排序,使得所有的有向边均从排在前面的元素指向排在后面的元素(或者说明无法做到这一点)。
优先级限制下不应该存在有向环,一个优先级限制的问题如果存在有向环,那么这个问题 肯定是无解的。所以先来解决有向环检测问题。
有向环检测:
采用深度优先遍历来解决这个问题:用一个栈表示“当前”正在遍历的有向路径上的顶点。一旦找到一条有向边v->w,并且w已经存在于栈中,那么就找到了一个环;如果没有找到这条边,那么就是无环图。
算法实现:
public class DirectedCycle {
private boolean[] marked;
private int [] edgeTo;
private Stack cycle;//有向环中所有顶点
private boolean[] onStack;//递归调用栈中所有顶点
public DirectedCycle(Digraph G) {
//略
}
//修改过的深度优先遍历
public void dfs(Digraph G,int v) {
onStack[v] = true;
marked[v] = true;
for(int w: G.adj(v)){
if(this.hasCycle()) return; //检测出有环,算法停止
else if(!marked[v]) {
edgeTo[w] = v; dfs(G,w);
}
else if(onStack[w]) { //如果为真,则说明形成了环,把环路经保存下来
cycle = new Stack();
for(int x = v;x!= w;x=edgeTo[x])
cycle.push(x);
cycle.push(w);
cycle.push(v);
}
}
}
public boolean hasCycle() { return cycle != null; }
public Iterable cycle(){ return cycle; }
}
拓扑排序:
其实,将有向无环图的深度优先遍历的路径记录下来就是一种拓扑排序。遍历的顺序取决于数据结构的性质以及是在递归调用之前还是之后保存。一般有三种排列排序:
- 前序:在递归调用之前将顶点加入队列
- 后序:在递归调用之后将顶点加入队列
- 逆后序:在递归调用之后将顶点压入栈
算法实现:
基于深度优先搜索的顶点排序:
public class DepthFirstOrder{
private boolean[] marked;
private Queue pre;//所有顶点的前序排列
private Queue post;//所有顶点的后序排列
private Stack reversePost;//所有顶点的逆后序排列
//构造器
public DepthFirstOrder(Digraph G){
pre = new Queue();
post = new Queue();
revercePost = new Stack();
marked = new boolean[G.V()];
for(int v = 0; v < G.V(); v++)
if(!marked[v]) dfs(G,v);
}
//深度优先遍历
private void dfs(Digraph G,int v) {
pre.enqueue(v);
marked[v] = true;
for(int w:G.adj(v))
if(!marked[w])
dfs(G,w);
post.enqueue(v);
reversePost.push(v);
}
//返回遍历结果
public Iterable pre(){ return pre; }
public Iterable post(){ return post; }
public Iterable reversePost(){ return reversePost; }
}
实现拓扑排序:
public class Tolpological {
private Iterable order;
public Tolpological(Digraph G) {
DirectedCycle cyclefinder = new DirectedCycle(G);
//如果无有向环,则调用深度优先遍历,最后输出逆后序排列
if(!cyclefinder.hasCycle()) {
DepthFirstOrder dfs = new DepthFirstOrder(G);
order = dfs.reversePost();
}
}
public Iterable order(){return order;}
}
- 一幅有向无环图的拓扑排序即为所有顶点的逆后序排序。
- 使用深度优先搜索对有向无环图进行拓扑排序需要的时间和V+E成正比。