2 solutions

  • 0
    @ 2024-10-21 15:06:36
    from collections import deque, defaultdict    #这个输出模式出度都是1,所以不需要再建图了
    
    # 读取输入并初始化数据
    n = int(input())
    edges = list(map(int, input().split()))
    
    # 初始化每个节点的入度数组和子节点数量数组
    in_degree = [0] * n
    nums = [0] * n
    
    # 更新每个节点的入度
    for i in range(n):
        in_degree[edges[i]] += 1
    
    # 队列用于存储所有入度为0的节点
    q = deque()
    
    # 将所有入度为0的节点加入队列
    for i in range(n):
        if in_degree[i] == 0:
            q.append(i)
    
    # BFS遍历
    while q:
        cur_idx = q.popleft()
        cur_next_idx = edges[cur_idx]
        in_degree[cur_next_idx] -= 1
        # 更新每个节点的子节点数目
        nums[cur_next_idx] += nums[cur_idx] + 1
        if in_degree[cur_next_idx] == 0:
            q.append(cur_next_idx)
    
    # 存储所有环的列表
    circles = []
    # 存储每个环的内聚值 H = L(环内节点数量) - V(指向环的节点数量)
    h_val = []
    # 存储每个环中的最大节点索引
    max_idx_in_circles = []
    
    # 找到环
    for i in range(n):
        if in_degree[i] == 0:
            continue
        cur_idx = i
        v = 0
        max_idx = i
        cur_circle = []
        # 遍历找到环
        while in_degree[cur_idx] > 0:
            v += nums[cur_idx]
            cur_circle.append(cur_idx)
            in_degree[cur_idx] = 0
            cur_idx = edges[cur_idx]
            max_idx = max(max_idx, cur_idx)
        
        circles.append(cur_circle)
        max_idx_in_circles.append(max_idx)
        # 计算内聚值,并加入列表
        h_val.append(len(cur_circle) - v)
    
    # 对环进行排序
    idx = list(range(len(h_val)))
    
    # 对索引数组进行排序,优先根据内聚值排序,其次看最大节点索引
    idx.sort(key=lambda a: (-h_val[a], -max_idx_in_circles[a]))
    
    # 找到最好的环
    res_circle = circles[idx[0]]
    
    # 按照顺序输出从最小节点开始的环
    start_idx = min(res_circle)
    print(start_idx)
    for i in range(len(res_circle)):
        print(start_idx, end=" " if i != len(res_circle) - 1 else "\n")
        start_idx = edges[start_idx]
    
    • 0
      @ 2024-8-21 4:32:21

      题面描述:

      在这道题中,我们需要处理一个包含 n 个微服务的调用关系数组 edges,其中每个微服务通过数组值指向另一个微服务。我们的目标是识别所有的微服务环群组,计算每个环的内聚值 H = L - V(L 为环中微服务的数量,V 为可以访问该环的其他微服务数量),并根据 H 值进行排序(当 H 值相同时,按环中最大微服务编号排序)。最后,输出 H 值最大的环,确保输出顺序是从环中编号最小的微服务开始。输入包含一个整数 n 和一个长度为 n 的数组 edges,输出为满足条件的微服务编号,以空格分隔。

      思路:BFS遍历

      1,6,3组成了微服务群组(环) a1,L1值为3,编号为4、9的2个微服务可以访问到a1,因此√1值为2,H1为L1V1 =1;

      0,2,10,5组成了微服务群组 (环) a2,L2值为4,编号为7、8、11的3个微服务可以访问到2,因此v2值为3,H2为L2-V2=1;

      先对比H值,H1=H2,H值相等;

      再对比环中序号最大值,a1中最大数为6。a2中最大数为10,a2排前面,因此输出答案为:0 2 10 5

      题解

      本题要求识别微服务调用中的环(强连通分量),并计算它们的内聚值。具体步骤如下:

      1. 输入数据结构:首先,我们需要一个整数 n 表示微服务数量,以及一个数组 edges,其中 edges[i] 表示微服务 i 调用的目标微服务。

      2. 图的表示:将微服务调用关系表示为有向图。每个微服务通过调用关系指向其他微服务。

      3. 强连通分量:利用 Tarjan 算法找出所有的强连通分量(SCC),每个强连通分量内的节点可以互相到达。

      4. 计算内聚值

        • 对于每个强连通分量,计算环内微服务数量 L 和可访问该环的其他微服务数量 V,从而得到内聚值 H = L - V。
        • H 值越大,表示环的内聚性越强。
      5. 排序和输出:根据 H 值从大到小排序,若 H 值相等,则选择环中编号最大的微服务进行比较。最终输出 H 值最大的环,且按照环中最小编号的微服务开始输出。

      代码

      C++

      #include <bits/stdc++.h>
      using namespace std;
      
      const int maxn = 2e5 + 10; // 最大微服务数量
      int n, m; // n: 微服务数量, m: 边的数量(暂未使用)
      int a[maxn]; // 存储微服务调用关系
      int ver[maxn << 1], Next[maxn << 1], head[maxn], tot = 1; // 图的边信息
      int low[maxn], dfn[maxn], num; // Tarjan算法中的访问标记和低点
      int Stack[maxn], top, ins[maxn], c[maxn]; // Tarjan算法的栈和标记
      int cnt, id, ans = INT_MIN; // cnt: 强连通分量数量, id: 当前最大H值的强连通分量编号
      vector<int> scc[maxn]; // 存储所有强连通分量
      
      // 添加边到邻接表
      inline void add(int x, int y) {
          ver[++tot] = y; 
          Next[tot] = head[x]; 
          head[x] = tot;
      }
      
      // Tarjan算法实现
      void tarjan(int x) {
          dfn[x] = low[x] = ++num; // 设置访问时间和最低点
          Stack[++top] = x; // 将当前节点入栈
          ins[x] = 1; // 标记当前节点在栈中
          for (int i = head[x]; i; i = Next[i]) {
              int y = ver[i]; // 访问邻接点
              if (!dfn[y]) { // 如果y未被访问
                  tarjan(y); // 递归访问
                  low[x] = min(low[x], low[y]); // 更新最低点
              } else if (ins[y]) {
                  low[x] = min(low[x], dfn[y]); // 更新最低点
              }
          }
          // 强连通分量的根节点
          if (low[x] == dfn[x]) {
              ++cnt; // 新的强连通分量
              int y;
              do {
                  y = Stack[top--]; // 出栈
                  ins[y] = 0; // 标记出栈
                  c[y] = cnt; // 记录所属强连通分量
                  scc[cnt].push_back(y); // 存储强连通分量
              } while (x != y); // 直到回到根节点
          }
      }
      
      // 获取强连通分量中的最大编号
      int getMax(int id) {
          int res = 0;
          for (int x : scc[id]) {
              res = max(res, x); // 比较取最大
          }
          return res;
      }
      
      // 计算访问到达某个强连通分量的数量
      int dfs(int x, int fa) {
          int tmp = 0;
          for (int i = head[x], y; i; i = Next[i]) {
              y = ver[i];
              if (y == fa) continue; // 不回到父节点
              tmp += dfs(y, x); // 递归访问
          }
          return tmp + 1; // 返回访问到的节点数
      }
      
      int main() {
          std::ios::sync_with_stdio(false); // 加速输入输出
          cin >> n; // 输入微服务数量
          // 构建图
          for (int i = 0; i < n; ++i) {
              cin >> a[i]; // 输入每个微服务的调用关系
              add(i, a[i]); // 添加边
          }
          // 使用Tarjan算法求解强连通分量
          for (int i = 1; i <= n; ++i) {
              if (!dfn[i]) {
                  tarjan(i);
              }
          }
          // 新建反图的构建
          for (int i = 0; i < n; ++i) {
              if (scc[c[i]].size() == 1) { // 该强连通分量只有一个点
                  add(c[a[i]] + n, c[i] + n); // 只有不在环中的点才需要建立边
              }
          }
          // 遍历所有强连通分量
          for (int i = 1; i <= cnt; ++i) {
              if (scc[i].size() != 1) { // 说明为环
                  int V = dfs(i + n, -1) - 1; // 计算可访问的节点数量
                  int H = scc[i].size() - V; // 计算内聚值
                  if (H > ans) { // 更新最大内聚值
                      id = i;
                      ans = H;
                  } else if (H == ans && getMax(id) < getMax(i)) { // H相等时,比较最大编号
                      id = i;
                  }
              }
          }
          // 找到该强连通分量中编号最小的点的位置
          int pos = 0;
          for (int i = 0; i < scc[id].size(); ++i) {
              if (scc[id][i] < scc[id][pos]) {
                  pos = i; // 更新最小编号的位置
              }
          }
          // 求scc时,编号按遍历顺序倒着加入进scc中,所以倒序输出
          for (int i = pos; i >= 0; --i) {
              cout << scc[id][i] << " "; // 输出环中节点
          }
          for (int i = scc[id].size() - 1; i > pos; --i) {
              cout << scc[id][i] << " "; // 输出环中节点
          }
      
          return 0;
      }
      
      

      python

      # 输入微服务数量
      n = int(input())
      # 输入调用关系
      to = list(map(int, input().split()))
      
      # 初始化并查集(Union-Find)的父节点数组
      f = list(range(n))
      # 初始化每个微服务的入度计数
      d = [0] * n
      
      # 查找函数,路径压缩
      def find(x):
          if x != f[x]:  # 如果 x 不是根节点
              f[x] = find(f[x])  # 递归查找并进行路径压缩
          return f[x]  # 返回根节点
      
      # 遍历每个微服务的调用关系
      for i, v in enumerate(to):
          f[find(i)] = find(v)  # 合并并查集
          d[v] += 1  # 增加目标微服务的入度
      
      from collections import deque
      
      # 初始化队列,存储入度为0的微服务
      q = deque()
      for i in range(n):
          if d[i] == 0:  # 找到没有入度的微服务
              q.append(i)  # 将其加入队列
      
      # 处理队列中的微服务
      while q:
          u = q.popleft()  # 从队列中取出微服务
          d[to[u]] -= 1  # 减少目标微服务的入度
          if d[to[u]] == 0:  # 如果入度为0,则加入队列
              q.append(to[u])
      
      # 使用集合存储所有的根节点(强连通分量)
      s = set()
      for i in range(n):
          s.add(find(i))  # 通过查找获取根节点并添加到集合
      
      # 映射根节点到索引
      mp = {}
      for i, v in enumerate(s):
          mp[v] = i  # 为每个根节点分配一个唯一索引
      
      # 初始化每个强连通分量的信息:数量L, 可访问数量V, 最大编号, 最小编号
      a = [[0] * 4 + [float("inf")] for _ in range(len(s))]
      for i in range(n):
          index = mp[find(i)]  # 找到当前微服务所在的强连通分量
          if d[i]:  # 如果当前微服务有入度
              a[index][0] += 1  # L值增加
              a[index][2] = max(a[index][2], i)  # 更新最大编号
              a[index][3] = min(a[index][3], i)  # 更新最小编号
          else:
              a[index][1] += 1  # V值增加
      
      # 根据内聚值 H = L - V 和最大编号排序
      a.sort(key=lambda x: (-(x[0] - x[1]), -x[2]))
      
      # 选择内聚值最高的强连通分量,输出起始编号
      start = a[0][3]  # 取最小编号作为起始
      res = [start]  # 结果列表,初始包含起始编号
      s = {start}  # 集合用于记录已访问的微服务
      
      # 遍历当前环,直到回到已访问的微服务
      while True:
          start = to[start]  # 移动到下一个微服务
          if start in s:  # 如果已经访问过,则终止
              break
          res.append(start)  # 添加当前微服务
          s.add(start)  # 记录当前微服务为已访问
      
      # 输出结果,按照要求的格式打印
      print(' '.join(str(x) for x in res))
      
      

      Java

      import java.util.*;
      
      public class Main {
      
          static int n;
          static int[] edges;
          static int[] nums;  // 每个节点的子节点数目
          static int[] inDegree;
      
          public static void main(String[] args) {
              // Input
              Scanner sc = new Scanner(System.in);
              n = sc.nextInt();
              edges = new int[n]; // 存储每个节点指向的下一个节点的数组
              inDegree = new int[n]; // 存储每个节点的入度数组
              nums = new int[n]; // 每个节点的子节点数目
      
              // 读取每个节点的下一个节点,并更新入度数组
              for (int i = 0; i < n; ++i) {
                  edges[i] = sc.nextInt(); // 读取每个节点的下一个节点
                  inDegree[edges[i]]++;
              }
      
              Queue<Integer> q = new LinkedList<>(); // 创建队列以存储所有入度为0的节点
      
              // 将所有入度为0的节点加入队列
              for (int i = 0; i < n; ++i) {
                  if (inDegree[i] == 0) {
                      q.offer(i);
                  }
              }
      
              // BFS遍历
              while (!q.isEmpty()) {
                  int qSize = q.size();
                  for (int i = 0; i < qSize; i++) {
                      int curIdx = q.poll();
                      int curNextIdx = edges[curIdx];
                      inDegree[curNextIdx]--; // 当前节点指向的结点的入度减1
                      // nums 存储每个节点的子节点数目
                      nums[curNextIdx] += nums[curIdx] + 1;
                      if (inDegree[curNextIdx] == 0) {
                          q.offer(curNextIdx);
                      }
                  }
              }
      
              List<List<Integer>> circles = new ArrayList<>(); // 存储所有环的列表
              List<Integer> hVal = new ArrayList<>(); // 存储每个环的内聚值H = L(环内节点数量) - V(指向环的节点数量)
              List<Integer> maxIdxInCircles = new ArrayList<>(); // 存储每个环中的最大节点索引
      
              // 找环
              for (int i = 0; i < n; i++) {
                  if (inDegree[i] == 0) {
                      continue;
                  }
                  int curIdx = i, v = 0, maxIdx = i;
                  List<Integer> curCircle = new ArrayList<>();
                  while (inDegree[curIdx] > 0) {
                      // nums 存储每个节点的子节点数目
                      v += nums[curIdx];
                      curCircle.add(curIdx);
                      inDegree[curIdx] = 0;
                      curIdx = edges[curIdx];
                      maxIdx = Math.max(maxIdx, curIdx);
                  }
                  circles.add(curCircle);
                  maxIdxInCircles.add(maxIdx);
                  // 计算内聚值,并加入列表
                  // 每个环的内聚值H = L(环内节点数量) - V(指向环的节点数量)
                  hVal.add(curCircle.size() - v);
              }
      
              Integer[] idx = new Integer[hVal.size()]; // 创建索引数组
              for (int i = 0; i < idx.length; ++i) idx[i] = i; // 初始化索引数组
      
              // 对索引数组进行排序,优先根据环的内聚值排序,其次看最大节点索引
              Arrays.sort(idx, (a, b) -> {
                  int valCompare = hVal.get(b).compareTo(hVal.get(a));
                  if (valCompare != 0) return valCompare;
                  return Integer.compare(maxIdxInCircles.get(b), maxIdxInCircles.get(a));
              });
      
              List<Integer> resCircle = circles.get(idx[0]);
      
              int startIdx = Collections.min(resCircle);
              for (int i = 0; i < resCircle.size(); i++) {
                  System.out.print(startIdx);
                  startIdx = edges[startIdx];
                  if (i != resCircle.size() - 1) {
                      System.out.print(" ");
                  }
              }
      
          }
      }
      
      • 1

      2024.04.24-暑期实习-第三题-塔子哥的微服务群组

      Information

      ID
      91
      Time
      1000ms
      Memory
      256MiB
      Difficulty
      7
      Tags
      # Submissions
      192
      Accepted
      31
      Uploaded By