算法:有向图强连通分量 (2020网易互联网秋招笔试题4, 教授)

有向图强连通分量 - 2020网易互联网秋招笔试题4

  • 题目
  • 暴力1号(邻接矩阵)
  • 暴力2号(邻接表)
  • 2次DFS求解
  • 1次DFS求解

题目

网易互娱笔试题:
假设教授A欣赏B,B欣赏C,则认为A也欣赏C。
输入 第一行两个整数 n m,分别表示教授人数,欣赏关系数量
余下m行,每行两个整数 a b,表示教授a欣赏b。
输入样例:
5 6
1 3
2 1
3 2
3 5
4 5
5 4
输出:
4

暴力1号(邻接矩阵)

考试提交的版本,直接迭代求解邻接矩阵(超时

def solve(N:int, Edges:[[int]]):
    Map = [[False]*N for _ in range(N)]
    for u, v in Edges:
        Map[u-1][v-1] = True

    flag = True
    while flag:
        flag = False
        for i in range(N):
            for j in range(N):
                if Map[i][j]:
                    for k in range(N):
                        if k != j and k != i:
                            if Map[j][k] and not Map[i][k]:
                                flag = True
                                Map[i][k] = True

    count = 0
    for i in range(N-1):
        for j in range(i+1, N):
            if Map[i][j] and Map[j][i]:
                count += 1

    return count

暴力2号(邻接表)

考试实验的第二个版本,当然也超时了

class Node:
    def __init__(self, value):
        self.value = value
        self.next = set()
        self.last = set()

    def append(self, i:int, node_dict:{}):
        if i == self.value or i in self.next:
            return
        else:
            self.next.add(i)
            for j in node_dict[i].next:
                self.append(j,node_dict)
            for l in self.last:
                node_dict[l].append(i, node_dict)
    def appreciate(self, i):
        return i in self.next

def solve2(N:int, Edges:[[int]]):
    if len(Edges) <=1:
        return 0
    Node_dict = {}
    for U, V in Edges:
        u = U - 1
        v = V - 1
        if u not in Node_dict.keys():
            Node_dict[u] = Node(u)
        if v not in Node_dict.keys():
            Node_dict[v] = Node(v)
        Node_dict[u].append(v, Node_dict)
        Node_dict[v].last.add(u)
    count = 0
    for i in range(N-1):
        # print(i, Node_dict[i].next, Node_dict[i].last)
        for j in range(i+1, N):
            if Node_dict[i].appreciate(j) and Node_dict[j].appreciate(i):
                count += 1
                # print(i,j)
    return count

2次DFS求解

  1. 后序DFS,标记编号
  2. 从大编号DFS反图

我花了爆久才能想得通这个为什么是正确的。因为第一次是后序dfs,所以编号最大的一定是某个连通分量的根R,第二次求反图,如果能找到任何一个反向边,这个边一定连向某个编号比它小的点,也就意味着是第一次dfs时R的子树,反向边本身意味着R是它的子树,那么就这样构成了回路。所以通过求反图,就可以遍历到R所在的整个强连通分量

会不会存在一种情况,第一次后序dfs从不同根节点开始便利了几次,这样在前面dfs到的节点编号(假设是u)就会比后面小,但前面的节点(u)就可能并不是后面更大编号点(假设是v)的子树,而求反图恰恰发现v连向u,这样就不符合上述逻辑?
答:不会,假设反图中更大编号点v连向更小的点u,则在第一次dfs,u是在v之前遍历到的,且u具有指向v的边,那么在编号时,v点的编号一定比u小,与假设矛盾

class Node2:
    def __init__(self, value):
        self.value = value
        self.next = set()
        self.last = set()
        self.number = -1
        self.mark = False

    def to(self, s):
        self.next.update(s)
    def _from(self, s):
        self.last.update(s)

    def last_order_dfs(self, node_dict, number, have_past):
        if self.value in have_past:
            return number
        else:
            have_past.append(self.value)
            for next_node in self.next:
                number = node_dict[next_node].last_order_dfs(node_dict, number, have_past)
            self.number = number
            return number + 1

    def second_dfs(self, node_dict):
        count = 0
        if self.mark:
            return 0
        else:
            self.mark = True
            count += 1
            for last_node in self.last:
                count += node_dict[last_node].second_dfs(node_dict)
            return count

def solve3(N:int, Edges:[[int]]):
    Node_dict = {i+1:Node2(i+1) for i in range(N)}
    for u, v in Edges:
        Node_dict[u].next.add(v)
        Node_dict[v].last.add(u)

    number = 1
    second_search = {}
    have_past = []
    for index in range(1, N+1):
        number = Node_dict[index].last_order_dfs(Node_dict, number, have_past)
        second_search[Node_dict[index].number] = index
    branch_size = []
    for i in range(N, 0, -1):
        index = second_search[i]
        branch_size.append(Node_dict[index].second_dfs(Node_dict))
    ret = 0
    for size in branch_size:
        ret += int(comb(size,2))
    return ret

1次DFS求解

我看完缩点原理就以为自己会了,如果构成回路,就把回路上的点搜索为一个点,但是我根据这个原理就是实现的非常糟糕。
最后还是参考了https://byvoid.com/zhs/blog/scc-tarjan/
但其实还是不会灵活运用QAQ

class Node3:
    def __init__(self, value):
        self.value = value
        self.next = set()
        self.have_searched = False
        self.number = -1
        self.contain = 0

    def dfs(self, node_dict, count, have_past):
        if self.number !=  -1:
            return self.number, count, have_past

        count += 1
        self.number = count
        children = [count]
        have_past.append(count)

        for next_node in self.next:
            child, count, have_past = node_dict[next_node].dfs(node_dict, count, have_past)
            children.append(child)

        low = min(children)

        if low == self.number:
            count = 1
            while have_past[-count] != self.number:
                count += 1
            self.contain = count
            have_past = have_past[:-count]
        return low, count, have_past

def solve4(N:int, Edges:[[int]]):
    Node_dict = {i+1:Node3(i+1) for i in range(N)}
    for u, v in Edges:
        Node_dict[u].next.add(v)

    ret = 0
    for index in range(1,N+1):
        Node_dict[index].dfs(Node_dict, 1, [])
        ret += int(comb(Node_dict[index].contain, 2))
    return ret

你可能感兴趣的:(算法)