package leetcodejava.array;

import org.junit.Assert;
import org.junit.Test;

import java.util.LinkedList;
import java.util.Queue;

/**
 * This is the solution of No.2 problem in the LeetCode,
 * the website of the problem is as follow:
 * https://leetcode-cn.com/problems/add-two-numbers
 * <p>
 * The description of problem is as follow:
 * ==========================================================================================================
 * 有 n 个城市，其中一些彼此相连，另一些没有相连。如果城市 a 与城市 b 直接相连，且城市 b 与城市 c 直接相连，那么城市 a 与城市 c 间接相连。
 * <p>
 * 省份 是一组直接或间接相连的城市，组内不含其他没有相连的城市。
 * <p>
 * 给你一个 n x n 的矩阵 isConnected ，其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连，而 isConnected[i][j] = 0 表示二者不直接相连。
 * <p>
 * 返回矩阵中 省份 的数量。
 * <p>
 * 输入：isConnected = [[1,1,0],[1,1,0],[0,0,1]]
 * 输出：2
 * 来源：力扣（LeetCode）
 * 链接：https://leetcode-cn.com/problems/number-of-provinces
 * 著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。
 * ==========================================================================================================
 *
 * @author zhangyu (zhangyuyu417@gmail.com)
 */
public class NumberofProvinces547 {

    @Test
    public void numberOfProvincesTest() {
        int relations[][] = {{1, 1, 0}, {1, 1, 0}, {0, 0, 1}};
        int result = findCircleNum(relations);
        System.out.println(result);
        Assert.assertEquals(result, 2);
    }

    /**
     * 深度优先遍历（DFS)
     *
     * @param isConnected 是否连接
     * @return 省的个数
     */
    public int findCircleNum2(int[][] isConnected) {
        // int[][] isConnected 是无向图的邻接矩阵，n 为无向图的顶点数量
        int n = isConnected.length;
        // 定义 boolean 数组标识顶点是否被访问
        boolean[] visited = new boolean[n];
        // 定义 cnt 来累计遍历过的连通域的数量
        int cnt = 0;
        for (int i = 0; i < n; i++) {
            // 若当前顶点 i 未被访问，说明又是一个新的连通域，则遍历新的连通域且cnt+=1.
            if (!visited[i]) {
                cnt++;
                dfs(i, isConnected, visited);
            }
        }
        return cnt;
    }

    /**
     * 深度优先帮助类
     *
     * @param i           当前标记
     * @param isConnected 是否连接
     * @param visited     访问
     */
    private void dfs(int i, int[][] isConnected, boolean[] visited) {
        // 对当前顶点 i 进行访问标记
        visited[i] = true;
        // 继续遍历与顶点 i 相邻的顶点（使用 visited 数组防止重复访问）
        for (int j = 0; j < isConnected.length; j++) {
            if (isConnected[i][j] == 1 && !visited[j]) {
                dfs(j, isConnected, visited);
            }
        }
    }

    /**
     * 并查集（UnionFind）
     *
     * @param isConnected 是否连接
     * @return 省的个数
     */
    public int findCircleNum(int[][] isConnected) {
        if (isConnected == null || isConnected.length == 0 || isConnected[0].length == 0) {
            return 0;
        }
        int n = isConnected.length;
        UnionFind2 uf = new UnionFind2(n);
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                if (isConnected[i][j] == 1) {
                    uf.union(i, j);
                }
            }
        }
        return uf.getCount();
    }

    /**
     * 广度优先遍历（BFS)
     *
     * @param isConnected 是否连接
     * @return 省的个数
     */
    public int findCircleNum3(int[][] isConnected) {
        // int[][] isConnected 是无向图的邻接矩阵，n 为无向图的顶点数量
        int n = isConnected.length;
        // 定义 boolean 数组标识顶点是否被访问
        boolean[] visited = new boolean[n];
        // 定义 count 来累计遍历过的连通域的数量
        int count = 0;
        Queue<Integer> queue = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            // 若当前顶点 i 未被访问，说明又是一个新的连通域，则bfs新的连通域且cnt+=1.
            if (!visited[i]) {
                count++;
                queue.offer(i);
                visited[i] = true;
                while (!queue.isEmpty()) {
                    int v = queue.poll();
                    for (int w = 0; w < n; w++) {
                        if (isConnected[v][w] == 1 && !visited[w]) {
                            visited[w] = true;
                            queue.offer(w);
                        }
                    }
                }
            }
        }
        return count;
    }
}

class UnionFind2 {
    private int[] root = null;
    private int count = 0;

    /**
     * 初始化
     *
     * @param num 个数
     */
    public UnionFind2(int num) {
        count = num;
        root = new int[num];
        for (int i = 0; i < num; i++) {
            root[i] = i;
        }
    }

    /**
     * 查看两个节点是否有关系
     *
     * @param x 节点x
     * @param y 节点y
     */
    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX != rootY) {
            count--;
            root[rootX] = rootY;
        }
    }

    /**
     * 查看某个点的根节点
     *
     * @param x 数字x
     * @return 根节点
     */
    public int find(int x) {
        if (root[x] == x) {
            return x;
        }
        return root[x] = find(root[x]);
    }

    /**
     * 获取省份总数量
     *
     * @return 身份个数
     */
    public int getCount() {
        return count;
    }
}
