深度优先搜索DFS即Depth First Search。其过程简要来说是对每一个可能的分支路径深入到不能再深入为止,而且每个节点只能访问一次。广度优先搜索BFS是Breadth First Search。所有因为展开节点而得到的子节点都会被加进一个先进先出的队列中。
DFS/BFS搜索算法分析定理一:深度优先搜索标记与起点连通的所有顶点所需的时间和顶点的度数之和成正比。
假设我们有两个点v和w,在一张图中,当v先访问到w时,v-w这条边从未检查的状态变为已检查,此时这条边访问了一次。当w访问v时,w-v这条边又被检查一次,但是发现已经被检查,此时这条边被访问了两次。除此之外,不可能再有其他情况导致v-w(w-v)这条边被访问。所以可得,图中每条边会被访问两次,边×2 = 顶点 Σ 度数 (各顶点度数之和)。所以成正比。定理二:一般的,使用深度优先搜索能解决的问题都可转化为广度优先搜索解决。
深度优先搜索的优点在于:递归易于理解、简单。但是深度优先搜索并没有明确的目的性,而广度优先搜索按照由近及远的顺序搜索,在很多情况下能找出最优解,而且循环的效率高于递归,并没有栈溢出的风险。在稀疏图中广度优先搜索的效率更是快过深度优先搜索很多,稠密图相差无几。在不一定需要广度优先搜索的情况下,我们可以尽量使用深度优先搜索。定理三:在使用邻接表作为图记录方法时,深度优先搜索与广度优先搜索时间复杂度均为O(V+E)。
访问元素所需的时间主要取决于图数据的记录方法,无论是深度优先搜索或是广度优先搜索,都需要检查整张图后才能计算完毕,耗时的主要部分取决于记录方式,在使用邻接矩阵作为记录数据的方法时,复杂度为O(n2),而邻接表中只有(顶点+边数×2)的数据,我们只需要执行其中一半的边数,另一半可由检查免除运算。所以,在使用邻接表作为图记录方法时,DFS与BFS时间复杂度均为O(V+E)。
基本数据结构——图类
深度优先算法和广度优先算法是基于图论的算法。在实现应用之前,先实现基本的无向图类数据结构。
Graph类用V定义定点,E定义边,LinkedList[ ]定义邻接表。package Graph;
import java.util.LinkedList;
public class Graph {
private final int V;
private int E;
private LinkedList[] adj;
public Graph(int V) {
this.V = V;
this.E = 0;
adj = (LinkedList[]) new LinkedList[V];
for (int v = 0; v < V; v++)
adj[v] = new LinkedList<>();
}
public int V() {
return V;
}
public int E() {
return E;
}
public void addEdge(int v, int w) {
adj[v].add(w);
adj[w].add(v);
E++;
}
public LinkedList adj(int v) {
return adj[v];
}
public int degree(int v,Graph g){
int count = 0;
for(int s : adj(v))
count++;
return count;
}
}
Graph类中的泛型数组
需要说明的是:这里虽然仅仅声明了泛型数组、用普通数组类型转化来实现,但也存在安全隐患。
类似下面的程序,编译通过但是内容出错,因为泛型在运行期被擦除,Object数组类间进行赋值不报错。public static void main(String[] args) {
LinkedList[] adj;
adj = (LinkedList[]) new LinkedList[5];
Object o = adj;
Object[] oa = (Object[]) o;
List li = new LinkedList<>();
li.add("s");
oa[0] = li;
System.out.println(adj[0]);
}
这种情况需要了解,但是这篇文章主要介绍算法,这部分不过多讨论。谨在此列出出错的可能性。
连通问题package Graph;
import java.util.ArrayDeque;
import java.util.Queue;
public class Connected {
private Graph g;
private boolean[] marked;
private int count;
public Connected(Graph g) {
this.g = g;
marked = new boolean[g.V()];
}
/**
* DFS算法计算连通结点
*
* @param s
* 起点
*/
public void DFS(int s) {
marked[s] = true;
count++;
for (int w : g.adj(s))
if (!marked[w])
DFS(w);
}
/**
* BFS算法计算连通结点
*
* @param s
* 起点
*/
public void BFS(int s) {
Queue q = new ArrayDeque<>();
q.add(s);
marked[s] = true;
count++;
while (!q.isEmpty()) {
for (int w : g.adj(q.poll()))
if (!marked[w]) {
marked[w] = true;
count++;
q.add(w);
}
}
}
/**
* 初始化marked标记数组状态
*/
public void cleanMarked() {
for (boolean b : marked)
b = false;
}
/**
* 返回该起点总连通结点数
*
* @return 连通结点数
*/
public int count() {
return count;
}
/**
* 判断一个结点是否被连通
*
* @param v
* 判断结点
* @return 连通状态
*/
public boolean isMarked(int v) {
return marked[v];
}
}
单点路径存在问题package Graph;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Stack;
public class Paths {
private Graph g;
private boolean[] marked;
private int[] edgeTo;
public Paths(Graph g) {
this.g = g;
marked = new boolean[g.V()];
edgeTo = new int[g.V()];
}
/**
* DFS算法计算单点路径问题
*
* @param s
* 起点
*/
public void DFS(int s) {
marked[s] = true;
for (int w : g.adj(s))
if (!marked[w]) {
edgeTo[w] = s;
DFS(w);
}
}
/**
* 初始化marked标记数组状态
*/
public void cleanMarked() {
for (boolean b : marked)
b = false;
}
/**
* 判断一个结点是否被连通
*
* @param v
* 判断结点
* @return 连通状态
*/
public boolean isMarked(int v) {
return marked[v];
}
/**
* 是否存在从s到v的路径,默认调用深度优先,可以选择广度优先
*
* @param s
* 起点
* @param v
* 终点
* @return 存在状态
*/
public boolean hasPathTo(int s, int v) {
DFS(s);
if (isMarked(v))
return true;
return false;
}
}
单点最短路径package Graph;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.Stack;
public class Paths {
private Graph g;
private boolean[] marked;
private int[] edgeTo;
public Paths(Graph g) {
this.g = g;
marked = new boolean[g.V()];
edgeTo = new int[g.V()];
}
/**
* DFS算法计算单点路径问题
*
* @param s
* 起点
*/
public void DFS(int s) {
marked[s] = true;
for (int w : g.adj(s))
if (!marked[w]) {
edgeTo[w] = s;
DFS(w);
}
}
/**
* BFS算法计算单点最短路径问题
*
* @param s
* 起点
*/
public void BFS(int s) {
Queue q = new ArrayDeque<>();
q.add(s);
marked[s] = true;
while (!q.isEmpty()) {
for (int w : g.adj(q.poll()))
if (!marked[w]) {
marked[w] = true;
edgeTo[w] = s;
q.add(w);
}
}
}
/**
* 初始化marked标记数组状态
*/
public void cleanMarked() {
for (boolean b : marked)
b = false;
}
/**
* 判断一个结点是否被连通
*
* @param v
* 判断结点
* @return 连通状态
*/
public boolean isMarked(int v) {
return marked[v];
}
/**
* 是否存在从s到v的路径,默认调用深度优先,可以选择广度优先
*
* @param s
* 起点
* @param v
* 终点
* @return 存在状态
*/
public boolean hasPathTo(int s, int v) {
DFS(s);
// BFS(v);
if (isMarked(v))
return true;
return false;
}
/**
* 输出最短路径
*
* @param s
* 起点
* @param v
* 终点
*/
public void pathTo(int s, int v) {
if (!hasPathTo(s, v))
return;
BFS(s);
// DFS(s); 但深度优先可能不是最短路径
Stack sta = new Stack<>();
sta.push(v);
for (int i = v; i != s; i = edgeTo[i])
sta.push(edgeTo[i]);
while (!sta.isEmpty())
System.out.println(sta.pop() + " ");
}
}
连通分量计算package Graph;
public class ConnectedComp {
private Graph g;
private boolean[] marked;
private int count;
private int[] id;
public ConnectedComp(Graph g) {
this.g = g;
id = new int[g.V()];
marked = new boolean[g.V()];
}
/**
* 调用方法,便利全部结点判断分量数
*/
public void DFS() {
for (int s = 0; s < g.V(); s++) {
if (!marked[s]) {
DFS(s);
count++;
}
}
}
/**
* DFS算法计算连通结点
*
* @param s
* 起点
*/
private void DFS(int s) {
marked[s] = true;
id[s] = count;
for (int w : g.adj(s))
if (!marked[w])
DFS(w);
}
/**
* 初始化marked标记数组状态
*/
public void cleanMarked() {
for (boolean b : marked)
b = false;
}
/**
* 返回该图总分量数目
*
* @return 分量数
*/
public int count() {
return count;
}
/**
* 返回该节点属于第几个分量
*
* @param s
* 判断结点
* @return 分量组数
*/
public int id(int s) {
return id[s];
}
}
无环图问题package Graph;
public class Cycle {
private Graph g;
private boolean[] marked;
private boolean hasCycle;
public Cycle(Graph g) {
this.g = g;
marked = new boolean[g.V()];
for(int s=0;s
if(!marked[s])
DFS(s,s);
}
/**
* DFS算法计算无环图问题
*
* @param s
* 起点
*/
public void DFS(int s, int v) {
marked[s] = true;
for (int w : g.adj(s))
if (!marked[w])
DFS(w, s);
else if (w != v)
hasCycle = true;
}
/**
* 初始化marked标记数组状态
*/
public void cleanMarked() {
for (boolean b : marked)
b = false;
}
/**
* 判断是否有环
*
* @return 判断结果
*/
public boolean hasCycle() {
return hasCycle;
}
}
二分图双色问题package Graph;
public class TwoColor {
private Graph g;
private boolean[] color;
private boolean[] marked;
private boolean isTwoColor;
public TwoColor(Graph g) {
this.g = g;
marked = new boolean[g.V()];
color = new boolean[g.V()];
isTwoColor = true;
for(int s=0;s
if(!marked[s])
DFS(s);
}
/**
* DFS算法计算二分图问题
*
* @param s
* 起点
*/
public void DFS(int s) {
marked[s] = true;
for (int w : g.adj(s))
if (!marked[w]) {
color[w] = !color[s];
DFS(w);
} else if (color[w] == color[s])
isTwoColor = false;
}
/**
* 初始化marked标记数组状态
*/
public void cleanMarked() {
for (boolean b : marked)
b = false;
}
/**
* 判断是否为二分图
*
* @return 判断结果
*/
public boolean isTwoColor() {
return isTwoColor;
}
}