统计朋友圈数目

Posted by 谢玄xx on May 6, 2022

写在前面

  • 第一次遇到这个问题,对于未能现场bug-free通过耿耿于怀,遂将此题记录下来,尝试使用多种方法解决该问题。

题目描述

班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有传递性:如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。之后返回全部学生朋友圈的数目,类型为整型。

  • 示例1:

输入:
[[1,1,0], [1,1,0], [0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈;第2个学生自己在一个朋友圈。所以返回2。

  • 示例2:

输入: [[1,1,0], [1,1,1], [0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。

问题分析

本题可用多种方法解决——dfs, bfs, 图,并查集。扎实的基础才是解决问题的核心所在哇。

解决思路

广度优先搜索(bfs)

  • 广度优先搜索(BFS,Breadth First Search),其过程是对每一层节点依次访问,访问完一层进入下一层,而且每个节点只能访问一次。
  • 广度优先遍历树,需要用到队列(Queue)来存储节点对象,队列的特点就是先进先出。

代码如下:

int findCircleNum(vector<vector<int>> &nums) {
    //定义visited数组,用来存储bfs顶部元素
    vector<int> visited(nums.size()) = {0};
    int count = 0;//计数值,用于返回该结果
    queue<int> queue;//bfs快乐队列
    for (int i = 0; i < nums.size(); i++) {
        if (visited[i] == 0) {
            queue.push_back(i);
            while (!queue.empty()) {
                int s = queue.pop_back();
                visited[s] = 1;
                for (int j = 0; j < nums.length; j++) {
                    if (nums[s][j] == 1 && visited[j] == 0)
                        queue.push_back(j);
                }
            }
            count++;
        }
    }
    return count;
}

深度优先搜索(dfs)

遍历二维数组的所有元素,对于每个元素,如果该元素尚未被访问过,则从该元素开始深度优先搜索,通过矩阵isConnected得到该元素组成朋友的元素有哪些,这些元素和该元素属于同一个朋友圈,然后对这些元素继续dfs,直到同一个连通分量的所有元素都被访问到,即可得到一个计数值。遍历完全部元素以后,即可得到连通分量的总数,即朋友圈的总数。

代码1:

    //查找和第i个人为同一朋友圈的人 
    void dfs(vector<vector<int>>& nums,int i, vector<bool>& visited)
    {
        visited[i] = true;
        for(int j = 0; j < nums.size(); j++)
        {
            if(nums[i][j] == 0 || visited[j] == true) continue; //这句话是关键
            dfs(nums, j, visited);     //向前递归查找           
        }
    }
    int findCircleNum (vector<vector<int>>& nums) {
        int n = nums.size();
        if(n == 0) return 0;
        int res = 0;
        vector<bool> visited(n, false); //这个人是否被归入某一朋友圈
        for(int i = 0; i < n; i++)
        {
            if(visited[i] == true) continue;
            dfs(nums, i, visited);
            res++;
        }
        return res;
    }

代码2

//先定义dfs函数,变量为原二维数组,遍历所需一维数组,朋友圈中的元素总数和行数i
void dfs(vector<vector<int>>& isConnected, vector<int>& visited, int elements, int i) {
    visited[i] = true;  //标记遍历过的数组下标为true
    //纵向遍历
    for (int j = 0; j < elements; j++) {
        //如果当前位置为1,而它的对称列并未被遍历,则将那一列置1
        if (isConnected[i][j] == 1 && !visited[j]) {
            visited[j] = 1;
            dfs(isConnected, visited, elements, j);
        }
    }
}

int findCircleNum(vector<vector<int>>& isConnected) {
    int elements = isConnected.size();//定义行数
    vector<int> visited(elements);//遍历判断所需一维数组
    int cnt = 0;
    //横向遍历。如果该行未遍历过,就开始dfs
    for (int i = 0; i < elements; i++) {
        if (!visited[i]) {
            dfs(isConnected, visited, elements, i);
            cnt++;
        }
    }
    return cnt;
}

并查集思想

并查集是判断元素是否在统一集合中的数据结构。 最开始,每个元素都在一个单独的集合里,集合的顶点就是自己;当有相同集合的元素合并之后,他们就在同一集合里,且元素值相等。

  • 并查集的缺点:如果发生不恰当的合并动作,可能造成查询时间过长的结果(因为需要遍历集合)。

代码如下:

int arr[10000];
int cnt = 0;
void init() {
    for(int i = 0; i < 10000; i++) {
        arr[i] = i;     //先把集合元素初始化为它的下标,表明它在单独的集合里
    }
    //定义一个查询函数,返回它所在集合的顶点元素
    int get(int x) {
        if(x == arr[x]) return x;   //如果下标x的值就是当前下标,表明它已经是顶点元素了,返回即可
        arr[x] = get(arr[x]);       //一个简单的递归,往上寻找顶点元素
        return arr[x];
    }
    //定义一个合并函数,
    void merge(int x, int y) {
        arr[get(x)] = get(y);
    }
    
    int findFriendCircle(vector<vector<int>> &nums) {
        init();
        for(int i = 0; i < nums.size(); i++) {
            for(int j = 0; j < nums[0].size(); j++) {
                //如果下标为i,j的元素值为1,那么i和j就属于同一朋友圈
                if(nums[i][j] == 1) {
                    merge(i, j);    //把i和j合并起来
                }
            }
        }
        //然后遍历这个并查集的集合
        for(int i = 0; i < nums.size(); i++) {
            //如果这个元素等于它自己,那他自己就是单独的一个朋友圈,cnt++就可
            if(arr[i] == i) {
                cnt++;
            }
        }
        return cnt;
        
    }
}