班上有N名学生。其中有些人是朋友,有些则不是。他们的友谊具有传递性。如果已知A是B的朋友,B是C的朋友,那么可以认为A也是C的朋友。所谓的朋友圈,是指所有朋友的集合。给定一个N*N的矩阵M,表示班级中学生之间的朋友关系。如果M[i][j]=1,表示已知第i个和j个学生互为朋友关系,否则为不知道。请设计一个算法,输出所有学生中的已知的朋友圈总数。

解题思路:

朋友圈的主要特性是传递性(A→B 是朋友,B→C 是朋友,则 A→C 是朋友),因此可以用到“集合连通性”:

每个学生初始属于一个 “独立集合”(自己是自己的朋友圈);

若两名学生是朋友(M [i][j]=1),则将他们的集合 “合并”;

最终,每个连通的集合对应一个朋友圈,集合的总数量就是朋友圈总数。

例如:3 名学生中 0 和 1 是朋友、1 和 2 是朋友,合并后 3 人属于同一个集合,对应 1 个朋友圈;若 2 无朋友,则 2 属于独立集合,总朋友圈数为 2。

并查集是专门处理 “集合合并” 和 “元素归属查询” 的数据结构

1. 并查集结构体设计(存储集合状态)

2. 并查集的操作(实现集合管理)

(1)初始化:为每个学生创建独立集合(init 函数)

例如:n=3 时,parent = [0,1,2],count=3,对应 3 个独立朋友圈。

(2)查找根节点:确定学生的集合归属(find 函数,带路径压缩)

路径压缩:若集合是 “链状”(如 0→1→2→3,3 是根),首次查找 0 的根时,会将 0 直接挂到 3 下(parent[0]=3),后续查找 0 的根只需 1 步,避免重复遍历链状结构,将查找时间复杂度从 O (n) 降为 O (α(n))(α 是阿克曼函数反函数,接近常数)。

(3)合并集合:将朋友的集合合并(unite 函数,按秩合并)

按秩合并:避免合并后集合变成 “长链”(如每次都将高集合挂到矮集合下,会导致高度快速增长),确保集合高度始终较小,进一步优化查找效率。

(4)内存释放:避免内存泄漏(destroy 函数)

动态分配的内存需手动释放,避免程序运行时内存泄漏。

3.遍历关系矩阵,统计朋友圈总数(findCircleNum 函数)

该函数是 “并查集” 与 “问题场景” 的桥梁,通过遍历朋友关系矩阵,调用并查集操作完成朋友圈计数:

遍历优化:只处理上三角矩阵:

朋友关系是双向的(M [i][j]=1 等价于 M [j][i]=1),若遍历整个矩阵(i 从 0 到 n-1,j 从 0 到 n-1),会重复处理(i,j)和(j,i),导致无效的合并操作。只处理j=i+1的上三角,既覆盖所有朋友关系,又避免重复。

4.main 函数负责 “接收用户输入→调用核心逻辑→输出结果”,并支持多次测试(输入 N=0 退出):

(1)循环输入学生数量 N:若 N=0,退出程序;否则继续;

(2)动态创建关系矩阵:分配 N×N 的二维数组,存储用户输入的朋友关系;

(3)输入关系矩阵:用户按行输入 N 个数字(0 或 1),表示学生间的朋友关系;

(4)计算并输出结果:调用findCircleNum得到朋友圈总数,打印结果;

(5)释放矩阵内存:避免动态分配的二维数组导致内存泄漏。

5.以 “3 名学生,0 和 1 是朋友,1 和 2 是朋友” 为例,完整流程如下:

(1)用户输入 N=3,输入关系矩阵[[1,1,0],[1,1,1],[0,1,1]];

(2)findCircleNum初始化并查集:parent=[0,1,2],count=3;

(3)遍历矩阵上三角:

i=0,j=1:M [0][1]=1,合并 0 和 1 的集合→parent=[1,1,2],count=2;

i=0,j=2:M [0][2]=0,不操作;

i=1,j=2:M [1][2]=1,合并 1 和 2 的集合→parent=[1,2,2],count=1;

(4)返回count=1,输出 “朋友圈总数为: 1”;

(5)释放矩阵和并查集内存,等待下一次输入(或输入 N=0 退出)。

C++代码如下:

#include<iostream>
using namespace std;
//并查集结构体
struct UnionFind
{
    int *parent;  //存储每个元素的父节点
    int *rank;    //存储树的秩,用于优化合并
    int count;    //连通分量的数量
    int size;     //元素总数
};
//定义结构体指针类型
typedef struct UnionFind *Tree; 
//初始化函数-创建并初始化UnionFind结构
Tree init(int n);
//查找根节点,带路径压缩
int find(Tree T,int x);
//合并两个集合,按秩合并
void unite(Tree T,int x,int y);
//释放内存
void destroy(Tree T);
//计算朋友圈数量
int findCircleNum(int **M, int n);
int main()
{
    int i,j,N;
    while(1)
    {
	    cout<<"请输入学生数量N: ";
	    cin>>N;
	    if(N==0)
	    {
	    	cout<<"当学生数量N为0时,退出程序!"<<endl;
			break;
		}
	    //动态分配N*N的二维数组
	    int **M=new int*[N];
	    for(i=0;i<N;i++)
	    {
	        M[i]=new int[N];
	    }
	    //输入关系矩阵
	    cout<<"请输入"<<N<<"*"<<N<<"的关系矩阵(每行"<<N<< "个数字,用空格分隔):"<<endl;
	    for(i=0;i<N;i++)
	    {
	        for(j=0;j<N;j++)
	        {
	            cin>>M[i][j];
	        }
	    }
	    //计算并输出结果
	    cout<<"朋友圈总数为: "<<findCircleNum(M,N)<<endl;
	    //释放动态分配的内存
	    for(i=0;i<N;i++)
	    {
	        delete[] M[i];
	    }
	    delete[] M;
	}
    return 0;
}
//初始化函数-创建并初始化UnionFind结构
Tree init(int n)
{
	int i;
    Tree T=new UnionFind;  //分配内存并返回指针
    T->size=n;
    T->count=n;
    T->parent=new int[n];
    T->rank=new int[n];
    for(i=0;i<n;i++)
    {
        T->parent[i]=i;  //初始时每个元素的父节点是自己
        T->rank[i]=0;    //初始秩为0
    }
    return T;  //返回初始化好的指针
}
//查找根节点,带路径压缩
int find(Tree T,int x)
{
    if(T->parent[x]!=x)
    {
        T->parent[x]=find(T,T->parent[x]);  //递归压缩路径
    }
    return T->parent[x];
}
//合并两个集合,按秩合并
void unite(Tree T,int x,int y)
{
    int rootX=find(T, x);
    int rootY=find(T, y);
    if(rootX==rootY)
    {
		return;  //已在同一集合中
    }
    //按秩合并
    if(T->rank[rootX]<T->rank[rootY])
    {
        T->parent[rootX]=rootY;
    }
    else
    {
        T->parent[rootY]=rootX;
        if(T->rank[rootX]==T->rank[rootY])
        {
            T->rank[rootX]++;
        }
    }
    T->count--;  //合并后连通分量减少1
}
//释放内存
void destroy(Tree T)
{
    delete[] T->parent;
    delete[] T->rank;
    delete T;  //释放结构体本身
}
//计算朋友圈数量
int findCircleNum(int **M, int n)
{
    Tree T=init(n);  //初始化并查集  
    //遍历矩阵处理朋友关系
    for(int i=0;i<n;i++)
    {
        for(int j=i+1;j<n;j++)  //只处理上三角,避免重复
        {
            if(M[i][j]==1)
            {
                unite(T,i,j);
            }
        }
    }
    int result=T->count;
    destroy(T);  //释放内存
    return result;
}

Python代码如下:

class UnionFind:
    """并查集类,实现初始化、查找(带路径压缩)和合并(按秩合并)操作"""
    
    def __init__(self, n):
        """初始化并查集"""
        self.size = n  # 元素总数
        self.count = n  # 连通分量的数量
        self.parent = list(range(n))  # 存储每个元素的父节点
        self.rank = [0] * n  # 存储树的秩,用于优化合并
    
    def find(self, x):
        """查找根节点,带路径压缩"""
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])  # 递归压缩路径
        return self.parent[x]
    
    def unite(self, x, y):
        """合并两个集合,按秩合并"""
        root_x = self.find(x)
        root_y = self.find(y)
        
        if root_x == root_y:
            return  # 已在同一集合中,无需合并
        
        # 按秩合并
        if self.rank[root_x] < self.rank[root_y]:
            self.parent[root_x] = root_y
        else:
            self.parent[root_y] = root_x
            if self.rank[root_x] == self.rank[root_y]:
                self.rank[root_x] += 1
        
        self.count -= 1  # 合并后连通分量减少1


def find_circle_num(M, n):
    """计算朋友圈数量"""
    uf = UnionFind(n)  # 初始化并查集
    
    # 遍历矩阵处理朋友关系
    for i in range(n):
        for j in range(i + 1, n):  # 只处理上三角,避免重复
            if M[i][j] == 1:
                uf.unite(i, j)
    
    return uf.count


def main():
    while True:
        try:
            print("请输入学生数量N: ", end="")
            N = int(input())
            
            if N == 0:
                print("当学生数量N为0时,退出程序!")
                break
            
            # 输入关系矩阵
            print(f"请输入{N}*{N}的关系矩阵(每行{N}个数字,用空格分隔):")
            M = []
            for i in range(N):
                row = list(map(int, input().split()))
                # 验证输入的行是否有正确数量的元素
                if len(row) != N:
                    print(f"错误:每行必须输入{N}个数字!")
                    # 清除已输入的行,重新开始输入
                    M = []
                    i = -1  # 因为循环会i+1,所以设为-1让下一次循环从0开始
                    continue
                M.append(row)
            
            # 计算并输出结果
            print(f"朋友圈总数为: {find_circle_num(M, N)}")
            
        except ValueError:
            print("输入错误,请输入有效的整数!")
        except Exception as e:
            print(f"发生错误: {str(e)}")


if __name__ == "__main__":
    main()

Java代码如下:

import java.util.Scanner;

// 并查集类
class UnionFind {
    private int[] parent;  // 存储每个元素的父节点
    private int[] rank;    // 存储树的秩,用于优化合并
    private int count;     // 连通分量的数量
    private int size;      // 元素总数

    // 初始化并查集
    public UnionFind(int n) {
        size = n;
        count = n;
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;  // 初始时每个元素的父节点是自己
            rank[i] = 0;    // 初始秩为0
        }
    }

    // 查找根节点,带路径压缩
    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);  // 递归压缩路径
        }
        return parent[x];
    }

    // 合并两个集合,按秩合并
    public void unite(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        
        if (rootX == rootY) {
            return;  // 已在同一集合中
        }
        
        // 按秩合并
        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else {
            parent[rootY] = rootX;
            if (rank[rootX] == rank[rootY]) {
                rank[rootX]++;
            }
        }
        
        count--;  // 合并后连通分量减少1
    }

    // 获取连通分量数量
    public int getCount() {
        return count;
    }
}

public class FriendCircle {
    // 计算朋友圈数量
    public static int findCircleNum(int[][] M, int n) {
        UnionFind uf = new UnionFind(n);  // 初始化并查集
        
        // 遍历矩阵处理朋友关系
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {  // 只处理上三角,避免重复
                if (M[i][j] == 1) {
                    uf.unite(i, j);
                }
            }
        }
        
        return uf.getCount();
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        while (true) {
            System.out.print("请输入学生数量N: ");
            int N = scanner.nextInt();
            
            if (N == 0) {
                System.out.println("当学生数量N为0时,退出程序!");
                break;
            }
            
            // 输入关系矩阵
            System.out.println("请输入" + N + "*" + N + "的关系矩阵(每行" + N + "个数字,用空格分隔):");
            int[][] M = new int[N][N];
            
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    M[i][j] = scanner.nextInt();
                }
            }
            
            // 计算并输出结果
            System.out.println("朋友圈总数为: " + findCircleNum(M, N));
        }
        
        scanner.close();
    }
}
    

程序运行截图展示:

更多推荐