图是一种重要的数据结构,许多图的算法一开始都会先通过搜索来获取图的结构或者图中结点所保存的信息,图的搜索技巧就是图的相关算法的核心。
图的搜索算法有两种设计核心,分别是BFS(breadth first search)和DFS(depth first search),表示广度优先搜索和深度优先搜索。
1.BFS搜索算法
所谓广度优先算法,就是对图G的边以结点为单位进行系统性的探索来发现从源节点出发能到达的其他所有结点,广度优先算法的特点是搜索位置每到达一个点,就会挨个继续探索这个点的所有连接其他点的边(从上一个点到达这个点的边除外),整体上来看是从图的一个源点出发,在某一个方向上层层递进的搜索方式。BFS算法的设计核心是对于队列这一数据结构的利用,我们在队列中存储的是搜索顺序,队列头就是目前搜索位置所在的结点。
class Solution {
public void search(List<List<Integer>>map) {
/*
* 形参二维数组map表示图,数组的一维表示图的结点,二维表示结点的边所
* 连接的另一个结点。
* 队列BFS表示搜索顺序。
* searchPos用来存储每一个结点的状态(是否被搜索过),
* false表示未被搜索,true表示以被搜索。
*/
Queue<Integer>BFS=new LinkedList<>();
boolean[]searchPos=new boolean[map.size()];
for(int i=0;i<map.size();i++){
//往BFS队列中插入源结点
if(searchPos[i]==false) {
BFS.add(i);
searchPos[i]=true;
}
//若队列非空,则一直往下搜索
while(!BFS.isEmpty()){
int thisPos=BFS.poll();
//判断当前搜索位置的边连接的结点是否被搜索过,若没有,则送入队列尾
for(int k=0;k<map.get(thisPos).size();k++){
if(searchPos[map.get(thisPos).get(k)]==false){
BFS.add(map.get(thisPos).get(k));
searchPos[map.get(thisPos).get(k)]=true;
}
}
}
}
}
}
2.DFS搜索算法
深度优先算法,将图G的某一个点作为源点,只要可能,就在图中尽量深入下去,如果无法继续深入,就回到前一个结点找另一条路,若路全都选完了,则继续回到上一个结点,找另一条路,如此进行,知道图中的结点全被搜索过为止,整体上看就是——深入->回,找另一条路->深入->回,找另一条路->深入……DFS算法的设计核心就是对于函数递归的利用,利用递归函数来达到回溯的目的。
class Solution {
boolean[] searchPos;
public void search(List<List<Integer>>map) {
/*
* 形参二维数组map表示图,数组的一维表示图的结点,二维表示结点的边所
* 连接的另一个结点。
* searchPos用来存储每一个结点的状态(是否被搜索过),
* false表示未被搜索,true表示已被搜索。
*/
searchPos = new boolean[map.size()];
for(int i=0;i<map.size();i++){
//i表示源结点,DFS算法中,任何结点都可以作为源结点
DFS(i,map);
}
}
//利用递归进行深入搜索
private void DFS(int thisPos,List<List<Integer>>map){
for(int i=0;i<map.get(thisPos).size();i++){
if(searchPos[map.get(thisPos).get(i)]==false) {
DFS(map.get(thisPos).get(i), map);
searchPos[map.get(thisPos).get(i)]=true;
}
}
}
}
----------------------------------------分割线--------------------------------------------
其实对于DFS算法也可以不使用递归(我个人其实是不太喜欢递归的,递归算法总给人一种不安全的感觉),最近习得一种新的方式进行DFS算法设计——利用栈+哈希表,我原本一直以为利用栈只能实现二叉树的DFS遍历,但是利用栈实现图的DFS也是可以的,只不过需要哈希表进行辅助。
具体方法:栈进行当前搜索结点的保存,每搜索到一个结点,该结点入栈,但在从当前搜索结点前往下一个结点时,当前结点有两种处理方式:1.当前结点所连接的其他结点(可能有多个)没有搜索完,不弹出栈;2.当前结点所连接的其他结点已经搜索完,弹出栈。那么如何判断当前结点所连接的其他结点已经搜索完了呢,这就需要利用哈希表了,具体利用就是利用哈希表存储每一个结点已经搜索过的所连接的结点数,如果大于该结点所有所连接的结点数,则弹出栈,否则在该结点经行搜索时,不弹出栈。
具体代码以一道算法题进行展示:
题目:给你无向连通图中一个节点的引用,请你返回该图的深拷贝(克隆)。
解题思路:这道题需要我们完整的复制一个图结构(与原图结构的存储空间不同)。而复制这个图,其实就是搜索这个图的所有结点,在搜索的过程中进行复制就行了。
class Node {
public int val;
//neighbors用于存储该结点所通向的另一个结点
public List<Node> neighbors;
public Node() {
val = 0;
neighbors = new ArrayList<Node>();
}
public Node(int _val) {
val = _val;
neighbors = new ArrayList<Node>();
}
public Node(int _val, ArrayList<Node> _neighbors) {
val = _val;
neighbors = _neighbors;
}
}
//DFS非递归算法
class Solution {
public Node cloneGraph(Node node) {
/*
* DFS:DFS算法中所用到的栈
* Search:记录结点的状态(是否被搜索过)
* Val_Node:通过val映射新结点
* Node_Neighbor:通过新结点映射neighbors
*/
if(node==null)
return null;
Stack<Node>DFS=new Stack<>();
HashSet<Node>Search=new HashSet<>();
HashMap<Integer,Node>Val_Node=new HashMap<>();
HashMap<Node,Integer>Node_Neighbor=new HashMap<>();
DFS.push(node);
Search.add(node);
Val_Node.put(node.val,new Node(node.val));
Node_Neighbor.put(Val_Node.get(node.val),0);
while(!DFS.isEmpty()){
Node peek= DFS.peek();
Node newNode=Val_Node.get(peek.val);
int neighbor=Node_Neighbor.get(newNode);
Node_Neighbor.put(newNode,neighbor+1);
if(neighbor>= peek.neighbors.size()){
DFS.pop();
continue;
}
if(!Val_Node.containsKey(peek.neighbors.get(neighbor).val)){
int key=peek.neighbors.get(neighbor).val;
Val_Node.put(key,new Node(key));
Node_Neighbor.put(Val_Node.get(key),0);
}
if(!Search.contains(peek.neighbors.get(neighbor))){
Search.add(peek.neighbors.get(neighbor));
DFS.push(peek.neighbors.get(neighbor));
}
newNode.neighbors.add(Val_Node.get(peek.neighbors.get(neighbor).val));
}
return Val_Node.get(node.val);
}
}