利用DFS,BFS来遍历搜索图,实现以下功能
可达性分析
给定有向图,判断某一个顶点是否可以到达另一个顶点,或者从一个顶点集合出发是否可以到达另一个顶点。单点可达性以及多点可达性问题,其中多点可达性可以用来解决java GC中存活对象的判断。
利用marked来标记dfs过程中访问的顶点坐标,则dfs完成时,marked数组中为true的顶点表示可达。
//有向图 dfs 可达性分析
public class DirectedDFS {
private boolean[] marked;
//单点可达性
//从起点s出发到某个顶点是否可达
//dfs之后 marked[true]顶点表示可达
public DirectedDFS(Digraph g, int s){
marked = new boolean[g.V()];
dfs(g, s);
}
//多点可达性
//从集合sources出发到某个顶点是否可达
//GC 判断对象是否存活
public DirectedDFS(Digraph g, Iterable<Integer> sources){
marked = new boolean[g.V()];
for(Integer s : sources){
if(!marked[s]){
dfs(g, s);
}
}
}
private void dfs(Digraph g, int v) {
marked[v] = true;
for(int w : g.adj(v)){
if(!marked[w])
dfs(g, w);
}
}
public static void main(String[] args) {
Digraph g = GraphUtils.getDiGraphPointG1().getG();
System.out.println(g.toString());
DirectedDFS dfs = new DirectedDFS(g, 9);
System.out.println(Arrays.toString(dfs.marked));
Bag<Integer> b = new Bag<>();
b.add(0);
b.add(9);
DirectedDFS ddfs = new DirectedDFS(g, b);
System.out.println(Arrays.toString(ddfs.marked));
}
}
单点有向路径
给定一个有向图,求出从某一个顶点到其他顶点的单点有向路径
和无向图中路径一样,利用dfs来搜索图,利用marked数组来保存访问顶点,利用edgeTo数组来表示有向路径,edgeTo[v]=w表示由w可以到达v。
//有向图 深度优先搜索 单点有向路径
public class DepthFirstDirectedPaths {
private boolean[] marked;
//edgeTo[w]=v的含义是由v可以直接到达w
private int[] edgeTo;
private final int s;
public DepthFirstDirectedPaths(Digraph g, int s){
this.marked = new boolean[g.V()];
this.edgeTo = new int[g.V()];
this.s = s;
dfs(g, s);
}
private void dfs(Digraph g, int v) {
marked[v] = true;
for(int w : g.adj(v)){
if(!marked[w]){
//向v下一个指向w
//edgeTo[x]得到谁指向x
edgeTo[w] = v;
dfs(g, w);
}
}
}
//起点s到节点v是否存在单点路径
public boolean hasPathTo(int v){
return marked[v];
}
//返回s-->v路径
public Iterable<Integer> pathTo(int v){
if(!hasPathTo(v)) return null;
//利用栈倒序存储
Stack<Integer> s = new Stack<>();
//由最终节点向起始节点寻找 edgeTo[x]表示谁指向x
for(int x=v; x!=this.s; x = edgeTo[x]){
s.push(x);
}
s.push(this.s);
return s;
}
public List<Integer> pathTo2(int v){
if(!hasPathTo(v)) return null;
//利用栈倒序存储
Stack<Integer> s = new Stack<>();
//由最终节点向起始节点寻找 edgeTo[x]表示谁指向x
for(int x=v; x!=this.s; x = edgeTo[x]){
s.push(x);
}
s.push(this.s);
List<Integer> l = new ArrayList<>();
while(!s.isEmpty()){
l.add(s.pop());
}
return l;
}
public String pathTo3(int v){
if(!hasPathTo(v)) return null;
List<Integer> l = pathTo2(v);
StringBuilder sb = new StringBuilder();
sb.append(s + ":" + v + " ");
for(int i=0; i<l.size(); i++){
sb.append(l.get(i));
if(i!=l.size()-1){
sb.append("-->");
}
}
return sb.toString();
}
public static void main(String[] args) {
Digraph g = GraphUtils.getDiGraphPointG1().getG();
System.out.println(g.toString());
DepthFirstDirectedPaths dfp = new DepthFirstDirectedPaths(g, 0);
System.out.println("x" + " " + "edgeTo[x]");
for(int i=0; i<dfp.edgeTo.length; i++){
System.out.println(i + " " + dfp.edgeTo[i]);
}
System.out.println(Arrays.toString(dfp.marked));
System.out.println(dfp.pathTo3(4));
}
}
单点最短有向路径
dfs可以解决单点有向路径,单最终得到的路径不一定是最短的,可以利用bfs来得到单点最短有向路径。
利用队列来保存顶点,起始顶点入队,访问该顶点的每一个未被访问过的相邻顶点,访问完成,标记该顶点,并且将该顶点入队。
//广度优先搜索 有向图 单点最短有向路径
public class BreadthFirstDirectedPaths {
private boolean[] marked;
private int[] edgeTo;
private final int s;
public BreadthFirstDirectedPaths(Digraph g, int s){
marked = new boolean[g.V()];
edgeTo = new int[g.V()];
this.s = s;
bfs(g, s);
}
//bfs按照距离顶点s距离为0,1,...依次来遍历图
//距离顶点s越近的顶点v越早进入队列,并且标记v已访问,记录s--v路径edgeTo[v]=s;
//并且之后由于标记过,不会再次访问,edgeTo不会更新,即bfs之后edgeTo中记录最短路径
private void bfs(Digraph g, int s) {
Queue<Integer> q = new LinkedList<>();
marked[s] = true;
q.add(s);
while(!q.isEmpty()){
int v = q.poll();
for(int w : g.adj(v)){
if(!marked[w]){
edgeTo[w] = v;
marked[w] = true;
q.add(w);
}
}
}
}
//起点s到节点v是否存在单点路径
public boolean hasPathTo(int v){
return marked[v];
}
//返回s-->v路径
public Iterable<Integer> pathTo(int v){
if(!hasPathTo(v)) return null;
//利用栈倒序存储
Stack<Integer> s = new Stack<>();
//由最终节点向起始节点寻找 edgeTo[x]表示谁指向x
for(int x=v; x!=this.s; x = edgeTo[x]){
s.push(x);
}
s.push(this.s);
return s;
}
public List<Integer> pathTo2(int v){
if(!hasPathTo(v)) return null;
//利用栈倒序存储
Stack<Integer> s = new Stack<>();
//由最终节点向起始节点寻找 edgeTo[x]表示谁指向x
for(int x=v; x!=this.s; x = edgeTo[x]){
s.push(x);
}
s.push(this.s);
List<Integer> l = new ArrayList<>();
while(!s.isEmpty()){
l.add(s.pop());
}
return l;
}
public String pathTo3(int v){
if(!hasPathTo(v)) return null;
List<Integer> l = pathTo2(v);
StringBuilder sb = new StringBuilder();
sb.append(s + ":" + v + " ");
for(int i=0; i<l.size(); i++){
sb.append(l.get(i));
if(i!=l.size()-1){
sb.append("-->");
}
}
return sb.toString();
}
public static void main(String[] args) {
Digraph g = GraphUtils.getDiGraphPointG1().getG();
System.out.println(g.toString());
BreadthFirstDirectedPaths bfp = new BreadthFirstDirectedPaths(g, 0);
System.out.println("x" + " " + "edgeTo[x]");
for(int i=0; i<bfp.edgeTo.length; i++){
System.out.println(i + " " + bfp.edgeTo[i]);
}
System.out.println(Arrays.toString(bfp.marked));
System.out.println(bfp.pathTo3(4));
}
/*
* 图搜索算法思路
* 将起点加入数据结构中,重复一下步骤直到数据结构为空
* 取出数据结构中的数据,标记数据
* 将与该数据所有相邻未标记节点加入到数据结构中
* 深度优先数据结构使用栈 每次取出最近的节点
* 广度优先使用队列,每次取出最远的节点
* */
}
有向环检测
给定一个有向图,判断有向图中,是否含有有向环。
利用dfs来从每一个未被访问的顶点作为起始点来进行递归,利用堆栈数组来保存从当前起始点开始,依次访问的顶点顺序,即每递归调用dfs则说明搜索到一个新的节点,判断该节点是否被访问过,如果没有被访问则标记该节点,并且记录在堆栈数组中;如果某个顶点被访问过则判断是否在堆栈数组中,如果存在则说明存在有向环。
//有向环检测 dfs
public class DirectedCycle {
//记录是否访问过
private boolean[] marked;
//有向路径保存
private int[] edgeTo;
//记录是否在当前栈中
private boolean[] onStack;
//记录最终的环节点
private Stack<Integer> cycle;
public DirectedCycle(Digraph g){
marked = new boolean[g.V()];
onStack = new boolean[g.V()];
edgeTo = new int[g.V()];
for(int s=0; s<g.V(); s++){
if(!marked[s])
dfs(g, s);
}
}
private void dfs(Digraph g, int v) {
//标记当前节点入栈
onStack[v] = true;
//标记已经访问
marked[v] = true;
for(int w : g.adj(v)){
if(hasCycle()) return;
//如果w没有被访问过 记录v-->w路径 对于w继续进行dfs w入栈
if(!marked[w]){
edgeTo[w] = v;
dfs(g, w);
}
//如果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);
}
}
//w出栈
onStack[v] = false;
}
public boolean hasCycle(){
return cycle != null;
}
public Iterable<Integer> cycle(){
return this.cycle;
}
public static void main(String[] args) {
Digraph g = GraphUtils.getDiGraphPointG1().getG();
System.out.println(g.toString());
DirectedCycle dc = new DirectedCycle(g);
System.out.println(dc.cycle());
}
}