lc_bfs_310_findMinHeightTrees

19 篇文章 0 订阅
10 篇文章 0 订阅

题目:最小高度树 middle
    对于一个具有树特征的无向图,我们可选择任何一个节点作为根。图因此可以成为树,
在所有可能的树中,具有最小高度的树被称为最小高度树。
给出这样的一个图,写出一个函数找到所有的最小高度树并返回他们的根节点。

格式:
    该图包含 n 个节点,标记为 0 到 n - 1。给定数字 n 和一个无向边 edges 列表(每一个边都是一对标签)。
你可以假设没有重复的边会出现在 edges 中。由于所有的边都是无向边, [0, 1]和 [1, 0] 是相同的,
因此不会同时出现在 edges 里。

package leetCode.BFS;

import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

public class lc_bfs_310_findMinHeightTrees {
/*
题目:最小高度树 middle
    对于一个具有树特征的无向图,我们可选择任何一个节点作为根。图因此可以成为树,
在所有可能的树中,具有最小高度的树被称为最小高度树。
给出这样的一个图,写出一个函数找到所有的最小高度树并返回他们的根节点。

格式:
    该图包含 n 个节点,标记为 0 到 n - 1。给定数字 n 和一个无向边 edges 列表(每一个边都是一对标签)。
你可以假设没有重复的边会出现在 edges 中。由于所有的边都是无向边, [0, 1]和 [1, 0] 是相同的,
因此不会同时出现在 edges 里。

示例 1:
输入: n = 4, edges = [[1, 0], [1, 2], [1, 3]]

        0
        |
        1
       / \
      2   3

输出: [1]

示例 2:
输入: n = 6, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]

     0  1  2
      \ | /
        3
        |
        4
        |
        5

输出: [3, 4]

说明:
    根据树的定义,树是一个无向图,其中任何两个顶点只通过一条路径连接。换句话说,
一个任何没有简单环路的连通图都是一棵树。树的高度是指根节点和叶子节点之间最长向下路径上边的数量。

*/

/*
思路:
首先,我们看了样例,发现这个树并不是二叉树,是多叉树。
然后,我们可能想到的解法是:
1.根据题目的意思,就挨个节点遍历bfs,统计下每个节点的高度,然后用map存储起来,
后面查询这个高度的集合里最小的就可以了。但是这样会超时的。即方法2
2.于是我们看图(题目介绍里面的图)分析一下,发现,越是靠里面的节点越有可能是最小高度树。
  这里是分析:
  BFS正确性简单证明:
      看到题解基本都用的BFS这个方法,因此该方法的正确性似乎并不一目了然,这里简单讲述一下。
  证明思路:
  对当前的图(初始的图或者删掉了前几层叶子节点的图),我们要找的满足题设的根节点所在位置只有两种可能,
  1)一种在当前图的叶子节点(即度为1的节点)
  2)一种为内部节点。
  a.若选择某叶子节点1为根节点,则该叶子节点1与任意其他叶子节点2的距离必定为: 叶子1-内部节点x-叶子2,
    深度为这三段边之和,必大于等于max(内部x-叶子1,内部x-叶子2),
  b.所以相比于叶子节点,解空间只能出现在内部节点,每层情况都是这样,
  c.所以我们要层层剥开叶子节点直到再无可分的内部节点为止。
3.我们从边缘开始,先找到所有出度为1的节点(即叶子结点),然后把所有出度为1的节点进队列,然后不断地bfs,
  每次遇到度为1的结点就入队,即叶子结点入队。最后找到的就是两边同时向中间靠近的节点,

*/


    /*
    剥洋葱法bfs
     */
    public List<Integer> findMinHeightTrees(int n, int[][] edges) {
        LinkedList<Integer> ans = new LinkedList<>();
        if (n == 1) {
            //如果只有一个节点,那么他就是最小高度树
            ans.add(0);
            return ans;
        }
//        建立各个节点的出度表
        int[] degree = new int[n];
//        建立图关系,在每个节点的list中存储相连节点
        List<List<Integer>> map = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            map.add(new LinkedList<>());
        }
        for (int i = 0; i < edges.length; i++) {
            degree[edges[i][0]]++;//出度++,即相邻结点数+1
            degree[edges[i][1]]++;
            map.get(edges[i][0]).add(edges[i][1]);//记录每个相邻节点
            map.get(edges[i][1]).add(edges[i][0]);
        }
        ArrayDeque<Integer> queue = new ArrayDeque<>();
//        把所有出度为1的节点,也就是叶子节点入队
        for (int i = 0; i < degree.length; i++)
            if (degree[i] == 1)
                queue.offer(i);
//          bfs开始,循环条件当然是经典的不空判断
        while (!queue.isEmpty()) {
//       这个地方注意,ans用于保存每层的叶子结点,表示需要剪去的叶子。每层循环都要new一个新的结果集合,
//       覆盖旧的叶子层结点集合,这样最后保存的就是最里层的叶子结点,也就是最终的最小高度树
            ans = new LinkedList<>();
            int count = queue.size();//这是每一层的节点的数量
            for (int i = 0; i < count; i++) {
                int cur = queue.poll();
                ans.add(cur);//把当前叶子节点加入结果集
//           当前节点的相邻接点都拿出来,把它们的出度都减1,因为cur节点已经不存在了,
//           cur的相邻节点们就有可能变成叶子节点
                List<Integer> neighbors = map.get(cur);
                for (int neigh : neighbors) {
                    degree[neigh]--;
//               如果是叶子节点就入队
                    if (degree[neigh] == 1) {
                        queue.offer(neigh);
                    }
                }
            }
        }
        return ans;//返回最后一次保存的叶子结点,也就是最里面的结点,也就是根节点
    }

    /**
     * 最正常的bfs想法,超时版本
     *
     * @return
     */
    public List<Integer> findMinHeightTrees2(int n, int[][] edges) {
        LinkedList<Integer> res = new LinkedList<>();
        if (edges == null || edges.length == 0) {
            res.add(0);
            return res;
        }
        int[][] matrix = new int[n][n];
        //转化为邻接矩阵关系
        for (int i = 0; i < edges.length; i++) {
            matrix[edges[i][0]][edges[i][1]] = 1;
            matrix[edges[i][1]][edges[i][0]] = 1;
        }
        int[] isVis = new int[n];
        int minHeight = Integer.MAX_VALUE;
        int[] heightOfSource = new int[n];
//        得到每个点为源点时的树的高度,存储在heightOfSource中,并记录最小的高度
        for (int i = 0; i < n; i++) {
            heightOfSource[i] = bfs(i, matrix, isVis);
            minHeight = Math.min(minHeight, heightOfSource[i]);
            isVis = new int[n];
        }
//        拿到最小的高度的源点
        for (int i = 0; i < heightOfSource.length; i++) {
            if (heightOfSource[i] == minHeight)
                res.add(i);
        }
        return res;
    }

    /*
    单个结点的bfs,n个数多时会超时。
    return 返回的是树的高度
     */
    public int bfs(int i, int[][] matrix, int[] isVis) {
        ArrayDeque<Integer> queue = new ArrayDeque<>();
        queue.offer(i);
        isVis[i] = 1;
        int point = -1;
        while (!queue.isEmpty()) {
            point = queue.poll();
            for (int k = 0; k < matrix[point].length; k++) {
                if (matrix[point][k] != 0 && isVis[k] == 0) {
                    queue.offer(k);
                    isVis[k] = isVis[point] + 1;
                }

            }
        }
        return isVis[point] - 1;
    }


}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值