算法十——深度优先搜索和广度优先搜索

文章出处:极客时间《数据结构和算法之美》-作者:王争。该系列文章是本人的学习笔记。

搜索算法

算法是作用于数据结构之上的。深度优先搜索、广度优先搜索是作用于图这种数据结构之上的。图上的搜索算法可以理解为从一个顶点到另外一个顶点。

常用的搜索算法有:暴力的深度优先搜索、广度优先搜索,还有A*、IDA*等启发式搜索。

实际应用举例

在社交网络中有六度分隔理论,就是一个人最多通过六个人可以认识另外一个人。一个用户的一度好友就是他的好友,二度好友是他好友的好友,以此类推。给你一个关系图,你可以找到一个用户的三度好友吗?

广度优先搜索(BFS)

BFS:先找离起始顶点最近的点,然后是次近,依次向外搜索。

下面的代码是无向图的BFS代码。

/**
 * 无向图
 */
public class UnDirectedGraph {

    private int v;//顶点个数
    private LinkedList<Integer> adj[];//邻接表
    public UnDirectedGraph(int v){
        this.v = v;
        this.adj = new LinkedList[v];
        for(int i=0;i<v;i++){
            this.adj[i] = new LinkedList<>();
        }
    }

    public void addEdge(int s,int t){
        this.adj[s].add(t);
        this.adj[t].add(s);
    }

    /**
     * 广度优先搜索从s节点到t节点:打印从s到t的节点路径
     * @param s
     * @param t
     */
    public void bfs(int s, int t) {
        if(s==t){
            System.out.println(s);
            return;
        }
        Queue<Integer> queue = new LinkedList<>();
        queue.offer(s);

        boolean[] visited = new boolean[this.v];
        visited[s] = true;

        int[] pre = new int[v];
        Arrays.fill(pre,-1);

        while(!queue.isEmpty()){
            int size = queue.size();
            for(int i =0;i<size;i++){//每一层
                int w = queue.poll();
                for(int j =0;j<this.adj[w].size();j++){
                    int q = this.adj[w].get(j);
                    if(!visited[q]){
                        pre[q] = w;
                        if(q==t){
                            printPath(pre,s,t);
                            return;
                        }
                        visited[q]=true;
                        queue.offer(q);
                    }
                }
            }
        }
    }

    private void printPath(int[] pre,int s,int t) {
        if(s!=t && pre[t]!=-1){
            printPath(pre,s,pre[t]);
        }
        System.out.print(t+"\t");
    }
}

这里三个重要的临时变量queue、visited、pre。
queue:是一个队列,存储已经被访问,但是邻接点还没有被访问的节点。
visited:记录已经访问过的节点,防止重复访问。
pre:记录从哪个节点可以达到下标所表示的节点。pre[w]记录从哪个节点达到w节点 。

时间复杂度分析:BFS中每个节点都会被访问一次,入队一次,每条边都会被访问一次,时间复杂度O(V+E)。V表示顶点个数,E表示边的个数。

空间复杂度分析:临时变量queue、visited、pre的个数都不会超过顶点个数。所以上O(V)。

上面的代码可以抽象出BFS代码的框架。

深度优先搜索(DFS)

DFS:DFS是从起始顶点开始,按照一条路径一直走到终点t或者不能再走下去。然后返回到上一个可选择的状态,选择另外一条路径,继续走。DFS是一种非常有名的算法思想:回溯。

下图中实现代表搜索路径,虚线代表回退。

	private boolean found = false;
    public void dfs(int s, int t) {
        boolean[] visited = new boolean[this.v];
        int[] pre = new int[v];
        Arrays.fill(pre,-1);
        dfs(s,t,visited,pre);
        printPath(pre,s,t);

    }

    private void dfs(int w, int t, boolean[] visited, int[] pre) {
        if(w==t){
            found = true;
            return;
        }
        visited[w] = true;
        if(!found){
            for(int j =0;j<this.adj[w].size() && !found;j++){
                int q = this.adj[w].get(j);
                if(!visited[q]) {
                    pre[q] = w;
                    visited[q]=true;
                    dfs(q,t,visited,pre);
                }
            }

        }

    }

时间复杂度分析:从图中看每条边最多被访问2次,一次搜索,一次回退。时间复杂度O(E)。

空间复杂度分析:消耗内存主要是 visited、prev 数组和递归调用栈。visited、prev 数组和顶点个数相同。递归调用不会超过顶点的个数。所以空间复杂度O(V)。

适用范围

DFS和BFS搜索的空间复杂度都是O(V),当顶点个数很大的时候,就不适合这两种算法。

三度好友

上面提到的查找一个人的三度好友适合用BFS。一层一层向外搜索。找到第三层。

发布了148 篇原创文章 · 获赞 35 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/flying_all/article/details/100030068