Python 노트: 그리고 집합 (DSU) 구조 안내

  • Python 노트: 그리고 집합 (DSU) 구조 소개
  • 1. 그리고 집합 이 무엇 인지 알 아 본다
  • 2. 집합 원리
  • 3. 코드 수집 실현
  • 1. 일반 코드 구현
  • 2. 최 적 화 된 DSU 구조
  • 1. 나무 구조 조정
  • 2. 찾 을 때마다 노드 정보 업데이트

  • 4. Leetcode 예제 분석
  • 1. Leetcode 547. Friend Circles
  • 2. Leetcode 721. Accounts Merge
  • 3. Leetcode 128. Longest Consecutive Sequence
  • 4. Leetcode 1579. Remove Max Number of Edges to Keep Graph Fully Traversable

  • 5. 링크 참조

  • 1. 집합 이 무엇 인지 알 아 본다.
    디 스 조 인 트 세트 유 니 온 (Disjoint Set Union) 은 서로 교차 하지 않 는 집합 간 통합 과 검색 기능 을 처리 하 는 트 리 구조 로 이에 대응 하 는 결합 - 검색 알고리즘 (Union Find Algorithm) 에 맞 춰 교차 하지 않 는 집합 간 통합 과 검색 기능 의 시간 복잡 도 를 O (l o g N) O (logN) O (logN) O (logN) 는 물론 O (1) O (1) O (1) O (1) O (1) 로 대폭 줄 일 수 있다.
    2. 집합 원리
    그리고 집합 을 조사 하 는 핵심 사상 은 모든 상호 관련 된 집합 을 하나의 대표 요소 로 표징 하 는 것 이다. 그러면 두 요소 가 대응 하 는 대표 요 소 를 찾 아 일치 하 는 지 판단 하면 그들 이 같은 집합 에 속 하 는 지 여 부 를 판단 할 수 있다.마찬가지 로 서로 교차 하지 않 는 두 개의 집합 이 필요 하 다 면 그 중의 한 대표 요 소 를 다른 대표 요 소 를 가리 키 고 그들 로 하여 금 같은 대표 요 소 를 사용 하 게 하면 된다.
    더 상세 한 원리 설명 은 아래 의 참고 링크 3 에서 칼럼 의 설명 을 참고 할 수 있 습 니 다. 그 는 수집 원리 에 대한 설명 을 매우 상세 하 게 했 습 니 다. 저 는 주로 이 칼럼 을 통 해 관련 내용 을 배우 고 수집 하 는 것 입 니 다.
    3. 집합 코드 구현
    1. 일반 코드 구현
    다음은 일반적인 검색 집합 을 위 한 간단 한 코드 를 드 리 겠 습 니 다.
    class DSU:
        def __init__(self, N):
            self.root = [i for i in range(N)]
            
        def find(self, k):
            if self.root[k] == k:
                return k
            return self.find(self.root[k])
        
        def union(self, a, b):
            x = self.find(a)
            y = self.find(b)
            if x != y:
                self.root[y] = x
            return
    

    상기 코드 는 가장 일반적인 집합 구조 이다.
    2. 최 적 화 된 DSU 구조
    그러나 일반적으로 알고리즘 효율 을 높이 기 위해 우 리 는 작은 trick 를 추가 하기 도 한다.예 를 들 면:
    1. 트 리 구조 조정
    알고리즘 의 효율 을 더욱 잘 최적화 하기 위해 우 리 는 나무 구 조 를 제어 하여 가능 한 한 평면 화 시 켜 체인 구조 가 나타 나 깊이 가 너무 깊 지 않도록 할 수 있다. 우 리 는 나무의 깊이 를 기록 하 는 방식 으로 나무 구 조 를 최적화 시 켜 알고리즘 효율 을 최적화 시킨다.
    class DSU:
        def __init__(self, N):
            self.root = [i for i in range(N)]
            self.depth = [1 for i in range(N)]
            
        def find(self, k):
            if self.root[k] == k:
                return k
            return self.find(self.root[k])
        
        def union(self, a, b):
            x = self.find(a)
            y = self.find(b)
            xh = self.depth[x]
            yh = self.depth[y]
            if x == y:
                return
            if xh >= yh:
                self.root[y] = x
                self.depth[x] = max(self.depth[x], self.depth[y]+1)
            else:
                self.root[x] = y
    

    2. 찾 을 때마다 노드 정보 업데이트
    다른 상용 trick 는:
  • 우 리 는 검색 이 완 료 될 때마다 노드 의 부모 노드 정 보 를 맨 위 에 있 는 부모 노드, 즉 전체 집합 의 대표 원 으로 업데이트 할 수 있다. 그러면 다음 에 찾 는 시간 복잡 도 는 바로 O (1) O (1) O (1) O (1) 로 떨어진다.
  • class DSU:
        def __init__(self, N):
            self.root = [i for i in range(N)]
            
        def find(self, k):
            if self.root[k] == k:
                return k
            self.root[k] = self.find(self.root[k])
            return self.root[k]
        
        def union(self, a, b):
            x = self.find(a)
            y = self.find(b)
            if x != y:
                self.root[y] = x
            return
    

    4. Leetcode 예제 분석
    다음은 leetcode 의 예 제 를 통 해 구조의 실제 용법 을 고찰 하고 찾 아 보 겠 습 니 다.
    1. Leetcode 547. Friend Circles
    이 문 제 는 가장 전형 적 인 것 이 고 집합 사용 장면 을 조사 하 는 것 이다. 우 리 는 직접 조합 구 조 를 사용 하면 이 문 제 를 풀 수 있다.
    직접 코드 를 제시 하여 다음 과 같이 실현 합 니 다.
    class DSU:
        def __init__(self, N):
            self.root = [i for i in range(N)]
            
        def find(self, k):
            if self.root[k] == k:
                return k
            return self.find(self.root[k])
        
        def union(self, a, b):
            x = self.find(a)
            y = self.find(b)
            if x != y:
                self.root[y] = x
            return
    
    class Solution:
        def findCircleNum(self, M: List[List[int]]) -> int:
            n = len(M)
            dsu = DSU(n)
            for i in range(n):
                for j in range(i+1, n):
                    if M[i][j] == 1:
                        dsu.union(i, j)
            group = set()
            for i in range(n):
                group.add(dsu.find(i))
            return len(group)
    

    2. Leetcode 721. Accounts Merge
    이 문 제 는 위의 문제 에 비해 다소 복잡 해 보이 지만 비교적 뚜렷 한 DSU 구조 입 니 다. 우 리 는 숫자 가 아니 라 account 에 따라 dsu 관 계 를 구축 한 다음 에 서로 다른 대표 account 에 따라 해당 하 는 계 정 소유 자 를 찾 으 면 됩 니 다.
    코드 를 제시 하여 다음 과 같이 실현 합 니 다.
    class DSU:
        def __init__(self):
            self.dsu = {
         }
        
        def find(self, account):
            if account not in self.dsu:
                self.dsu[account] = account
                return account
            if account == self.dsu[account]:
                return account
            self.dsu[account] = self.find(self.dsu[account])
            return self.dsu[account]
        
        def union(self, x, y):
            a1 = self.find(x)
            a2 = self.find(y)
            self.dsu[a2] = a1
            return 
    
    class Solution:
        def accountsMerge(self, accounts: List[List[str]]) -> List[List[str]]:
            mapping = {
         }
            dsu = DSU()
            for it in accounts:
                name = it[0]
                key_account = it[1]
                mapping[key_account] = name
                for account in it[2:]:
                    mapping[account] = name
                    dsu.union(key_account, account)
            res = defaultdict(list)
            for account in mapping:
                key_account = dsu.find(account)
                res[key_account].append(account)
            ans = [[mapping[k]] + sorted(v) for k, v in res.items()]
            return ans
    

    3. Leetcode 128. Longest Consecutive Sequence
    이 문제 의 DSU 구 조 는 비교적 뚜렷 하 다. 바로 배열 의 모든 요소 n 을 대상 으로 n - 1 과 n + 1 도 dsu 에 나타 나 는 지 확인 하 는 것 이다. 만약 에 있 으 면 이 몇 가지 요 소 를 연결 하고 반대로 연결 하지 않 는 다.
    유일 하 게 주의해 야 할 것 은 이 문제 가 집행 효율 에 대한 요구 가 비교적 높 기 때문에 우 리 는 dsu 의 나무 모양 구 조 를 최적화 시 켜 가능 한 한 평면 화 시 켜 야 한 다 는 것 이다.
    다음 과 같은 코드 를 제공 합 니 다.
    class DSU:
        def __init__(self):
            self.dsu = {
         }
            
        def find(self, n):
            if n not in self.dsu:
                return None
            if n == self.dsu[n]:
                return n
            self.dsu[n] = self.find(self.dsu[n])
            return self.dsu[n]
        
        def union(self, x, y):
            xr = self.find(x)
            yr = self.find(y)
            if xr is None or yr is None:
                return
            self.dsu[yr] = xr
            return
        
        def add(self, n):
            if n not in self.dsu:
                self.dsu[n] = n
            return
        
    class Solution:
        def longestConsecutive(self, nums: List[int]) -> int:
            if nums == []:
                return 0
            dsu = DSU()
            nums = list(set(nums))
            for n in nums:
                dsu.add(n)
                dsu.union(n, n-1)
                dsu.union(n, n+1)
            counter = defaultdict(int)
            for n in nums:
                counter[dsu.find(n)] += 1
            return max(counter.values())
    

    4. Leetcode 1579. Remove Max Number of Edges to Keep Graph Fully Traversable
    이 문 제 는 leetcode Weekly Contest 205 의 마지막 문제 로 당시 풀 지 못 했 으 나 지금 은 DSU 구 조 를 대충 배 운 뒤 다시 살 펴 보 자.
    주체 의 사상 은 그 당시 와 일치 합 니 다. 특정한 변 으로 연 결 된 두 점 이 같은 집합 에 속 했 을 때 우 리 는 이 변 을 버 리 고 반대로 이 변 을 보존 합 니 다. 마지막 으로 하나의 전체 연결 도 를 구성 할 수 있 는 지, 할 수 있다 면 모두 몇 개의 변 을 삭 제 했 습 니 다.
    DSU 구 조 를 통 해 우 리 는 이 문 제 를 신속하게 해결 하고 우리 가 실현 한 python 코드 를 다음 과 같이 제시 했다.
    from copy import deepcopy
    
    class DSU:
        def __init__(self, n):
            self.dsu = [i for i in range(n+1)]
            
        def find(self, x):
            if x == self.dsu[x]:
                return x
            self.dsu[x] = self.find(self.dsu[x])
            return self.dsu[x]
        
        def union(self, x, y):
            xr = self.find(x)
            yr = self.find(y)
            self.dsu[yr] = xr
            return
    
    class Solution:
        def maxNumEdgesToRemove(self, n: int, edges: List[List[int]]) -> int:
            alice = []
            bob = []
            both = []
            for t, x, y in edges:
                if t == 1:
                    alice.append((x, y))
                elif t == 2:
                    bob.append((x, y))
                else:
                    both.append((x, y))
            dsu = DSU(n)
            counter3 = 0
            for x, y in both:
                if dsu.find(x) == dsu.find(y):
                    continue
                dsu.union(x, y)
                counter3 += 1
            dsu1 = deepcopy(dsu)
            counter1 = 0
            for x, y in alice:
                if dsu1.find(x) == dsu1.find(y):
                    continue
                dsu1.union(x, y)
                counter1 += 1
            dsu2 = deepcopy(dsu)
            counter2 = 0
            for x, y in bob:
                if dsu2.find(x) == dsu2.find(y):
                    continue
                dsu2.union(x, y)
                counter2 += 1
    
            if counter1 + counter3 != n-1 or counter2 + counter3 != n-1:
                return -1
            else:
                return len(edges) + counter3 - 2*n +2
    

    5. 링크 참조
  • Disjoint Set Union (DSU) 및 집합 및 응용
  • 및 수집 (Disjoint Set)
  • 알고리즘 학습 노트 (1): 그리고 집합
  • 위 키 백과: 그리고 집합
  • 좋은 웹페이지 즐겨찾기