深广度优先搜索系列题解
大体认知
在算法第四版中无向图部分对深度优先与广度优先进行了比较细致的讲解,详情可以参见我在github
上完善的[算法第四版无向图](https://github.com/maycope/Algorithms4-Emphasis/blob/master/Chapter04-Grapths/4.1 无向图.md)的基础介绍。其实在了解完(在校都应该学习过系列的数据结构)深度与广度优先算法之后,我们可以将完整的过程表示为一下的两张图片:
深度优先
以一个点出发不断深入其中
对于深度优先算法来说其模板代码如下:
public class DepthFirstSearch {
private boolean[] marked; // 定义一个boolean 类型的数组,具体的数组的长度,可以在具体的使用的过程中根据具体的长度进行初始化。
public DepthFirstSearch(Graph G, int s) {
marked = new boolean[G.V()];// 这里的G.V() 表示此图的顶点个数
validateVertex(s);
dfs(G, s);
}
private void dfs(Graph G, int v) {
marked[v] = true;// 表示没有被访问过。
for (int w : G.adj(v)) {
// 以当前节点进行不断深度,在访问到暂时还没有被标记的节点的时候进入我的的递归深度优先函数中。
if (!marked[w]) {
dfs(G, w);
}
}
}
}
广度优先
以一个点出发以发散的形式进行节点的遍历
广度优先算法的模板:
public class BreadThFirstSearch{
Queue<Integer> queue = new LinkedList<>();
Map<...,...> map =new HashMap<>();// 根据具体的情况而定,因为对于系列的广度搜索的变形大部分都是依赖于节点之间的关联关系
for (...) {
queue.add(...)// 表示对当前的这个queue进行初始化入值
}
while (!queue.isEmpty()) {// 当当前的 queue 暂时还不为空的时候
Integer top = queue.poll();
// 以当前从queue中拿出来的值进行遍历查询 就是遍历其周围的值 实现广度的搜索。
for (int successor : adj[top]) {
... // 进行具体的逻辑代码的实现功能
if (...) {// 进行逻辑的判断
queue.add(successor);// 将
}
}
}
return ... ;// 适当时候进行返回。
}
}
课程表问题(经典)
对于这个课程表问题也是经典的使用到我们深度和广度的例子。对于系统学习过数据结构
的同学,都应该知道邻接表
的概念,其实这个课程表中的所谓的先后的课程学习就是邻接表的具体体现,利用类似的邻接表的先与后的绑定的关系来模拟出我们深度与广度中的图论:将课程幻化为节点,将课程之间的依赖关系幻化为边
所以就转换成为了,给定一个节点(是没有任何入度的,就是说学习这门课程不需要其他的课程作为前驱),然后来对模拟的图
进行深度或者广度的遍历,最后来看看是否能对整个图都完整遍历完全。
广度优先
首先铭记在心里(写在前面的)模板。
- 首先我们要有一个
queue
队列,来模拟出现一个节点时候,我们能够找寻到其所有相关联的节点。个人觉得这里有些类似于对于树的不同的遍历方式
也是利用类似的思想。 - 因为给定我们的是数组,而不是直接了当的一个图,所以要处理如何找到对应的邻接关系,还需要我们定义一个二维的链表,来模拟我们的邻接表。
剩下的就是根据具体的情况添加一(亿)点点小细节。具体的讲解可以参看源码:
public boolean canFinish(int count, int[][] num) {
List<List<Integer>> lists = new ArrayList<>();
// 表示入度信息 因为我们课程之间都是有依赖关系,我们需要找寻对于每个课程来说都会有几个前驱的课程在学习完成之后才能够学习当前的课程。
int [] temp = new int [count];
Queue<Integer> queue = new LinkedList<>();
for(int i=0;i<count;i++){
lists.add(new ArrayList<>());
}
for(int [] p: num){
temp[p[0]]++;// 进行入度节点的读入
lists.get(p[1]).add(p[0]); // 进行依赖关系的读入
}
for(int i= 0;i<count;i++){
if(temp[i]==0 ) queue.add(i);
// 表示对于那些没有入读节点的课程来说,读入到queue中,表示此课程可直接进行学习,不需要前驱的课程。
}
while(!queue.isEmpty()){
int ress = queue.poll();
for(int q: lists.get(ress))
{
temp[q]--;// 表示读取到相关的课程之后,该课程的入读可以减一,若是为0 时候 表示该课程已经也可以作为入度来获取到其他的课程信息了。
if(temp[q]== 0)
queue.add(q);
}
count --;
}
return count == 0;
}
深度优先
- 利用之前的模板进行先必要遍历的初始化操作。
- 进行for-each 循环 进行不断地深入处理。
public boolean canFinish(int count, int[][] num) {
int []flag = new int[count];
List<List<Integer>> lists = new ArrayList<>();
for(int i= 0;i<count;i++)
lists.add(new ArrayList<>());
for(int i=0;i<num.length;i++){
lists.get(num[i][1]).add(num[i][0]);
}
for(int i =0;i<count;i++)
if(!getresult(lists,flag,i))
return false;
return true;
}
private static boolean getresult (List<List<Integer>> lists ,int [] flag ,int i){
// 表示当前节点已经访问过。
if(flag[i] == -1)
return true;
// 表示就是说如果访问过了 就不能够重复的进行访问
if(flag[i] == 1 )
{
// 表示从正在访问到正在访问 遇到了环。
return false;
}
// 将当前节点这是为正在访问。
flag[i] = 1;
for(int temp : lists.get(i))
{
if(!getresult(lists,flag,temp)) return false;
}
// 设置为访问完成。
flag[i] = -1;
return true;
}
对图的克隆
深度优先
对上面的模板进行理解即可。
private static Node cloneGraph(Node node) {
if(node == null)
return node;
Map<Integer, Node> map = new HashMap<>();
return getDepth(node,map);
}
private static Node getDepth(Node node,Map<Integer,Node> map ){
if(map.containsKey(node.val))
{
// 表示在map中已经存在过 进行返回即可。
return map.get(node.val);
}
Node n = new Node();
n.val = node.val;
n.neighbors = new ArrayList<>();
map.put(n.val,n);
// 以上都是进行新节点的建立 并赋初值。
for(Node p : node.neighbors){
// 对原来的节点的 邻居进行遍历,找到一个邻居 然后以该邻居为中心进行发展。
n.neighbors.add(getDepth(p,map
));
}
return n;
}
广度优先
对于广度优先来说,在遍历当前节点的时候,邻居节点没有生成,那么我们可以一边遍历一边生成邻居节点,就可以同时更新 neighbors
了。
public Node cloneGraph(Node node){
if(node == null)
{
return null;
}
Queue <Node> queue = new LinkedList<>();
Map<Integer,Node> map = new HashMap<>();
queue.offer(node);
Node n= new Node();
n.val = node.val;
n.neighbors = new ArrayList<>();
map.put(n.val,n);
while(!queue.isEmpty()){
Node q = queue.poll();
for(Node p : q.neighbors){
if(!map.containsKey(p.val)){
n = new Node();
n.val = p.val;
n.neighbors = new ArrayList<>();
map.put(n.val,n);
queue.offer(p);
}
map.get(q.val).neighbors.add(map.get(p.val));
}
}
return map.get(node.val);
}
}
子树中标签相同的节点数
深度优先
int ans[];
public int [] countSubTrees(int n, int[][] edges, String labels) {
ans = new int [n];
boolean [] visited = new boolean[n];
List<List<Integer>> lists = new ArrayList<>();
for(int i= 0;i<n;i++){
lists.add(new ArrayList<>());
}
for(int i = 0 ;i<edges.length;i++){
lists.get(edges[i][0]).add(edges[i][1]);
lists.get(edges[i][1]).add(edges[i][0]);
}
// 这是深度优先搜索
dfs(0,lists,visited,labels);
return ans;
}
public int [] dfs(int n,List<List<Integer>> lists, boolean [] visited,String labels){
visited[n]= true;
int [] c = new int [26];
for(int p: lists.get(n)){
if(visited[p]== false){
int [] q= dfs(p,lists,visited,labels);
for(int i= 0;i<26;i++){
c[i]+= q[i];
}
}
}
c[labels.charAt(n)-'a']++;
ans[n]= c[labels.charAt(n)-'a'];
return c;
}