广度优先算法

847访问所有节点的最短路径

存在一个由 n 个节点组成的无向连通图,图中的节点按从 0 到 n - 1 编号。
给你一个数组 graph 表示这个图。其中,graph[i] 是一个列表,由所有与节点 i 直接相连的节点组成。
返回能够访问所有节点的最短路径的长度。你可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边。
图片说明
图片说明

提示:

  • n == graph.length

  • 1 <= n <= 12

  • 0 <= graph[i].length < n

  • graph[i] 不包含 i

  • 如果 graph[a] 包含 b ,那么 graph[b] 也包含 a

  • 输入的图总是连通图

    思路:BFS + 状态压缩

  • BFS写法,大致的思路是,由于我们需要折返, 所以不能像标准bfs算法一样,根据一个节点或一条边是否被访问去修剪。

  • 问题就变成了如何去修剪,既不会不必要地重复访问,又不会把正解修剪掉。

  • 我们可以记录当我们位于一个节点时整个图被访问的状态,试想我们在当前节点知道目前整个图的状态state,我们要决定是否要访问一个邻节点v,只需要看一下元组(v, state)是否已经被访问过,如果是的话,证明这条路已经被遍历过了,不需要再访问。

  • 至于如何表示state,第一时间想到可以用一个HashSet,但是我们要快速检查一个状态是否被访问过,用HashSet效率太低。恰好题目最多只有12个节点,所以可以用位运算进行状态压缩。

class Solution {
    public int shortestPathLength(int[][] graph) {
        int n = graph.length;
        // Store tuple: (vertex, bit_mask)
        Queue<Pair<Integer, Integer>> q = new LinkedList<>();
        Set<Pair<Integer, Integer>> visited = new HashSet<>();
        for (int i = 0; i < n; ++i) {
            q.offer(new Pair<>(i, 1 << i));
            visited.add(new Pair<>(i, 1 << i));
        }

        // BFS
        int dis = 0;
        while (!q.isEmpty()) {
            int size = q.size();
            for (int i = 0; i < size; ++i) {
                Pair<Integer, Integer> p = q.poll();
                if (p.getValue() == (1 << n) - 1) {
                    return dis;
                } 
                for (int v: graph[p.getKey()]) {
                    Pair<Integer, Integer> pv = new Pair<>(v, p.getValue() | 1 << v);
                    if (!visited.contains(pv)) {
                        q.offer(pv);
                        visited.add(pv);
                    }
                }
            }
            ++dis;
        }
        return 0;
    }
}

解法二:

class Solution {
    public int shortestPathLength(int[][] graph) {
        int len=graph.length;
        if(graph==null || graph.length==0){
            return 0;
        }
        boolean[][] visited=new boolean[len][1<<len]; // 标记是否访问过,用于避免重复访问
        int finishState=(1<<len)-1;  // 用于检查是否访问完所有的节点,每个位代表一个节点的状态,形如1111
        Queue<int[]> queue=new LinkedList<>(); // 队列里的数组,第一个记录的是标号,第二个是状态
        for(int i=0; i<len; i++){
            queue.offer(new int[]{i,1<<i});
        }
        int step=0;
        while(!queue.isEmpty()){
            for(int i=queue.size(); i>0; i--){
                int[] node=queue.poll();
                if(finishState==node[1]){ // 如果标记的节点访问状态是结束,那么返回步长
                    return step;
                }
                for(int next:graph[node[0]]){
                    int nextState=node[1]|(1<<next); // 2个节点相或,标记着访问了这条边的2个点
                    if(visited[next][nextState]){
                        continue;
                    }
                    visited[next][nextState]=true;
                    queue.offer(new int[]{next,nextState}); // 将该节点和边的信息加入bfs对列
                }
            }
            step++;
        }
        return step;
    }
}
全部评论

相关推荐

中信银行 AI算法岗 29~32w
点赞 评论 收藏
转发
点赞 收藏 评论
分享
牛客网
牛客企业服务