经典编程题:服务器广播

题目描述:

服务器连接方式包括直接相连,间接连接。A 和 B 直接连接,B 和 C 直接连接,则 A 和 C 间接连接。直接连接和间接连接都可以发送广播。

给出一个 N*N 数组,代表 N 个服务器,matrix[i][j]==1,则代表 i 和 j 直接连接;

不等于 1 时,代表 i 和 j 不直接连接。

matrix[i][i]==1,即自己和自己直接连接。matrix[i][j]==matrix[j][i]。

计算初始需要给几台服务器广播,才可以使每个服务器都收到广播。

输入描述

输入为N行,每行有N个数字,为0或1,由空格分隔,构成N*N的数组,N的范围为 1<=N<=50

输出描述

输出一个数字,为需要广播的服务器数量

示例 1:

输入

1 0 0

0 1 0

0 0 1

输出

3

说明

3台服务器互不连接,所以需要分别广播这3台服务器

示例 2:

输入

1 1

1 1

输出

1

说明

2台服务器相互连接,所以只需要广播其中一台服务器。

要解决这个问题,我们需要确定网络中有多少个互不连接的子网络(即连通分量)。每个连通分量中的服务器可以通过直接或间接连接互相通信,因此对于每个连通分量,只需从其中一台服务器广播即可覆盖整个子网络。

解题步骤:

  1. 输入解析:

    • 首先读取所有的输入行,并将其转换为一个二维列表(矩阵)。
    • 确定 N 的值,即服务器的数量,等于输入的行数。
  2. 图的表示:

    • 将服务器之间的连接关系表示为一个无向图,使用邻接矩阵的形式。
  3. 连通分量的计算:

    • 使用深度优先搜索(DFS)或广度优先搜索(BFS)来遍历图,计算有多少个连通分量。
    • 每找到一个新的连通分量,广播次数加一。
  4. 输出结果:

    • 最终输出连通分量的数量,即所需的最少广播次数。

下面是使用 Python 实现的代码:

python 复制代码
def count_broadcasts(matrix):
    N = len(matrix)
    visited = [False] * N
    count = 0

    def dfs(node):
        for neighbor in range(N):
            if matrix[node][neighbor] == 1 and not visited[neighbor]:
                visited[neighbor] = True
                dfs(neighbor)

    for i in range(N):
        if not visited[i]:
            visited[i] = True
            dfs(i)
            count += 1

    return count

def main():
    import sys
    input_lines = sys.stdin.read().strip().split('\n')
    matrix = []
    for line in input_lines:
        if line.strip() == '':
            continue
        row = list(map(int, line.strip().split()))
        matrix.append(row)
    # Validate that it's an N x N matrix
    N = len(matrix)
    for row in matrix:
        if len(row) != N:
            print("输入的矩阵不是 N*N 的格式。")
            return
    result = count_broadcasts(matrix)
    print(result)

if __name__ == "__main__":
    main()

代码说明:

  1. count_broadcasts 函数:

    • 接受一个二维列表 matrix 作为输入,表示服务器之间的连接关系。
    • 使用 DFS 来遍历每个连通分量,count 用于记录连通分量的数量。
  2. main 函数:

    • 从标准输入读取所有输入行,并将其转换为整数矩阵。
    • 检查输入是否为一个有效的 N×N 矩阵。
    • 调用 count_broadcasts 函数并输出结果。

示例运行:

示例 1:

输入:
1 0 0
0 1 0
0 0 1

输出:
3

示例 2:

输入:
1 1
1 1

输出:
1

使用方法:

将上述代码保存为一个 Python 脚本,例如 broadcast.py,然后在命令行中运行:

bash 复制代码
python broadcast.py

然后输入矩阵数据,按 Ctrl+D(或在 Windows 上按 Ctrl+Z 然后回车)结束输入,程序将输出所需的广播次数。

在给定的解决方案中,我们使用深度优先搜索(DFS)来计算图中的连通分量数量,从而确定需要广播的服务器数量。下面详细分析该算法的时间复杂度和空间复杂度。

时间复杂度(Time Complexity)

  1. 输入解析:

    • 读取并解析输入的 N×N 矩阵需要遍历所有的元素,因此时间复杂度为 O(N²)
  2. DFS 遍历:

    • 在最坏情况下,图是一个完全连通的图(每个服务器都直接连接到其他所有服务器),DFS 需要遍历所有的边和节点。
    • 对于一个 N 个节点的无向图,边的数量最多为 N(N-1)/2 ,因此 DFS 的时间复杂度为 O(N²)
  3. 整体时间复杂度:

    • 输入解析和 DFS 遍历都是 O(N²) ,因此整体时间复杂度为 O(N²)

空间复杂度(Space Complexity)

  1. 存储矩阵:

    • 输入的 N×N 矩阵需要 O(N²) 的空间来存储。
  2. 辅助数据结构:

    • visited 数组 :用于记录每个服务器是否已被访问,空间复杂度为 O(N)
    • 递归调用栈 (在最坏情况下,递归深度为 N):空间复杂度为 O(N)
  3. 整体空间复杂度:

    • 主导因素是存储矩阵的 O(N²) ,因此整体空间复杂度为 O(N²)

总结

  • 时间复杂度: O(N²)
  • 空间复杂度: O(N²)

这种复杂度在 N 的范围内(1 ≤ N ≤ 50)是可以接受的,因为 N 的上限相对较小,不会导致性能问题。

进一步优化

虽然当前的时间和空间复杂度已经适用于题目给定的约束,但如果 N 的范围更大,可以考虑以下优化:

  1. 使用邻接表表示图:

    • 对于稀疏图,邻接表可以减少空间复杂度到 O(N + E),其中 E 是边的数量。
    • DFS 的时间复杂度也可以降低到 O(N + E)
  2. 使用并查集(Union-Find):

    • 并查集可以高效地合并连通分量,并且几乎具有常数时间的查询和合并操作。
    • 时间复杂度为 O(N² α(N)),其中 α(N) 是阿克曼函数的反函数,几乎可以看作是常数。

不过,对于当前题目中的 N 范围,这些优化并不是必要的。

使用**并查集(Union-Find)**来解决这个问题是一种高效的方法,尤其适用于处理连通性问题。并查集能够快速合并和查找集合,帮助我们确定网络中有多少个独立的连通分量,从而计算需要广播的服务器数量。

并查集简介

并查集是一种数据结构,用于跟踪元素分组情况,支持以下两种操作:

  1. 查找(Find): 确定某个元素属于哪个集合。
  2. 合并(Union): 将两个集合合并成一个。

为了优化并查集的性能,通常会使用**路径压缩(Path Compression)按秩合并(Union by Rank)**技术。

使用并查集解决问题的步骤

  1. 初始化并查集:

    • 每个服务器初始时都属于自己的集合。
  2. 遍历矩阵并执行合并操作:

    • 对于矩阵中的每一对服务器 (i, j),如果 matrix[i][j] == 1,则将它们合并到同一个集合中。
    • 由于矩阵是对称的,我们只需遍历上三角或下三角部分以避免重复处理。
  3. 统计独立的集合数量:

    • 最终并查集中不同的根节点数量即为需要广播的服务器数量。

Python 实现

以下是使用并查集解决该问题的 Python 代码:

python 复制代码
class UnionFind:
    def __init__(self, size):
        # 初始化父节点数组,每个节点的父节点是自己
        self.parent = [i for i in range(size)]
        # 初始化秩数组,用于按秩合并
        self.rank = [1] * size

    def find(self, x):
        # 路径压缩:递归查找根节点,并将路径上的所有节点直接连接到根节点
        if self.parent[x] != x:
            self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def union(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
        elif self.rank[root_x] > self.rank[root_y]:
            self.parent[root_y] = root_x
        else:
            # 秩相同,任意合并,并增加新根节点的秩
            self.parent[root_y] = root_x
            self.rank[root_x] += 1

def count_broadcasts_union_find(matrix):
    if not matrix:
        return 0

    N = len(matrix)
    uf = UnionFind(N)

    # 遍历上三角矩阵,避免重复处理
    for i in range(N):
        for j in range(i + 1, N):
            if matrix[i][j] == 1:
                uf.union(i, j)

    # 使用集合来统计不同的根节点
    unique_roots = set()
    for i in range(N):
        root = uf.find(i)
        unique_roots.add(root)

    return len(unique_roots)

def main():
    import sys
    input_lines = sys.stdin.read().strip().split('\n')
    matrix = []
    for line in input_lines:
        if line.strip() == '':
            continue
        row = list(map(int, line.strip().split()))
        matrix.append(row)
    # 验证输入是否为 N x N 矩阵
    N = len(matrix)
    for row in matrix:
        if len(row) != N:
            print("输入的矩阵不是 N*N 的格式。")
            return
    result = count_broadcasts_union_find(matrix)
    print(result)

if __name__ == "__main__":
    main()

代码说明

  1. UnionFind 类:

    • __init__ 方法: 初始化父节点和秩数组。
    • find 方法: 实现路径压缩,确保树的高度尽可能低。
    • union 方法: 根据秩合并两个集合,确保较小的树连接到较大的树下。
  2. count_broadcasts_union_find 函数:

    • 初始化并查集。
    • 遍历上三角矩阵,如果 matrix[i][j] == 1,则合并服务器 ij
    • 最后,通过查找每个服务器的根节点,并使用集合统计独立的根节点数量。
  3. main 函数:

    • 从标准输入读取矩阵数据。
    • 验证输入矩阵是否为 N×N 格式。
    • 调用 count_broadcasts_union_find 函数并输出结果。

示例运行

示例 1:

输入:
1 0 0
0 1 0
0 0 1

输出:
3

示例 2:

输入:
1 1
1 1

输出:
1

使用方法

将上述代码保存为一个 Python 脚本,例如 broadcast_union_find.py,然后在命令行中运行:

bash 复制代码
python broadcast_union_find.py

然后输入矩阵数据,按 Ctrl+D(或在 Windows 上按 Ctrl+Z 然后回车)结束输入,程序将输出所需的广播次数。

时间复杂度和空间复杂度分析

时间复杂度

  1. 初始化并查集: O(N)
  2. 遍历矩阵并执行合并操作:
    • 对于一个 N×N 的矩阵,需要检查 N(N-1)/2 个元素(上三角部分)。
    • 每次 findunion 操作的时间复杂度接近于 O(1),由于路径压缩和按秩合并的优化,整体近似为 O(N²)
  3. 统计独立的根节点: O(N),需要对每个节点执行 find 操作。

总体时间复杂度: O(N²)

空间复杂度

  1. 存储并查集的父节点和秩数组: O(N)
  2. 存储输入矩阵: O(N²)

总体空间复杂度: O(N²)

比较并查集与 DFS 的方法

  • 时间复杂度: 两者均为 O(N²),因为都需要遍历整个矩阵。
  • 空间复杂度: 并查集额外使用 O(N) 的空间,而 DFS 使用 O(N) 的辅助空间(访问数组和递归栈)。由于矩阵本身需要 O(N²) 的空间存储,因此总体空间复杂度相同。

总结: 使用并查集和使用 DFS 在时间和空间复杂度上相似,选择哪种方法主要取决于个人偏好和具体应用场景。在某些情况下,并查集可能更易于实现和理解,尤其是在需要频繁合并和查询集合的情况下。

相关推荐
人生无根蒂,飘如陌上尘1 小时前
网站自动签到
python·签到
深图智能1 小时前
OpenCV的双边滤波函数
python·opencv·计算机视觉
eybk1 小时前
采用pycorrector纠错word文件段落,并保存为word文件标红显示出来
python·word
背锅浩1 小时前
python批量删除redis key
redis·python·bootstrap
范纹杉想快点毕业1 小时前
XML通过HTTP POST 请求发送到指定的 API 地址,进行数据回传
xml·c语言·开发语言·数据结构·c++·python·c#
黑客老陈1 小时前
BaseCTF scxml 详解
开发语言·网络·python·sql·安全·web安全
Ma_si2 小时前
python 如何调整word 文档页眉页脚
python·c#·word
bachelores2 小时前
数据结构-排序
数据结构·算法·排序算法
Gui林2 小时前
【ROS2】☆ launch之Python
python·机器人·开源
飞鸢逐浪2 小时前
3D形状匹配 - SpiderMatch
算法