πŸ”΅Coding Test/Algorithm

[ 이것이 μ½”λ”©ν…ŒμŠ€νŠΈλ‹€ with 파이썬 ] 기타 κ·Έλž˜ν”„ 이둠

🐢 개발개발 🐾 2022. 6. 15. 10:27

μ„œλ‘œμ†Œ 집합 (Disjoint Sets) : 곡톡 μ›μ†Œκ°€ μ—†λŠ” 두 집합을 μ˜λ―Έν•©λ‹ˆλ‹€.

 

 μ„œλ‘œμ†Œ λΆ€λΆ„ μ§‘ν•©λ“€λ‘œ λ‚˜λˆ„μ–΄μ§„ μ›μ†Œλ“€μ˜ 데이터λ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•œ μžλ£Œκ΅¬μ‘°μž…λ‹ˆλ‹€.

 μ„œλ‘œμ†Œ 집합 μžλ£Œκ΅¬μ‘°λŠ” 두 μ’…λ₯˜μ˜ 연산을 μ§€μ›ν•©λ‹ˆλ‹€.

    • ν•©μ§‘ν•©(Union) : 두 개의 μ›μ†Œκ°€ ν¬ν•¨λœ 집합을 ν•˜λ‚˜μ˜ μ§‘ν•©μœΌλ‘œ ν•©μΉ˜λŠ” μ—°μ‚°μž…λ‹ˆλ‹€.

    • μ°ΎκΈ°(Find) : νŠΉμ •ν•œ μ›μ†Œκ°€ μ†ν•œ 집합이 μ–΄λ–€ 집합인지 μ•Œλ €μ£ΌλŠ” μ—°μ‚°μž…λ‹ˆλ‹€.

 μ„œλ‘œμ†Œ 집합 μžλ£Œκ΅¬μ‘°λŠ” ν•©μΉ˜κΈ° μ°ΎκΈ°(Union Find) 자료ꡬ쑰라고 λΆˆλ¦¬κΈ°λ„ ν•©λ‹ˆλ‹€.

 

• μ—¬λŸ¬ 개의 ν•©μΉ˜κΈ° 연산이 μ£Όμ–΄μ‘Œμ„ λ•Œ μ„œλ‘œμ†Œ 집합 자료ꡬ쑰의 λ™μž‘ 과정은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

    1. 합집합(Union) 연산을 ν™•μΈν•˜λ©°, μ„œλ‘œ μ—°κ²°λœ 두 λ…Έλ“œ A, Bλ₯Ό ν™•μΈν•©λ‹ˆλ‹€.

        1)  A와 B의 루트 λ…Έλ“œ A', B' λ₯Ό 각각 μ°ΎμŠ΅λ‹ˆλ‹€.

        2)  A'λ₯Ό B'의 λΆ€λͺ¨λ…Έλ“œλ‘œ μ„€μ •ν•©λ‹ˆλ‹€.

    2. λͺ¨λ“  합집합(Union) 연산을 μ²˜λ¦¬ν•  λ•ŒκΉŒμ§€ 1번의 과정을 λ°˜λ³΅ν•©λ‹ˆλ‹€. 

 

 

1. 기본적인 μ„œλ‘œμ†Œ 집합 κ΅¬ν˜„ 방법 

# 기본적인 μ„œλ‘œμ†Œ 집합 μ•Œκ³ λ¦¬μ¦˜

# νŠΉμ • μ›μ†Œκ°€ μ†ν•œ 집합을 μ°ΎκΈ°
def find_parent(parent, x):
    # 루트 λ…Έλ“œκ°€ μ•„λ‹ˆλΌλ©΄, 루트 λ…Έλ“œλ₯Ό 찾을 λ•ŒκΉŒμ§€ μž¬κ·€μ μœΌλ‘œ 호좜
    if parent[x] != x:
        return find_parent(parent, parent[x])
    return x

# 두 μ›μ†Œκ°€ μ†ν•œ 집합을 ν•©μΉ˜κΈ°
def union_parent(parent, a, b):
    a = find_parent(parent, a)
    b = find_parent(parent, b)
    if a < b:
        parent[b] = a
    else:
        parent[a] = b

# λ…Έλ“œμ˜ κ°œμˆ˜μ™€ κ°„μ„ (union μ—°μ‚°)의 개수 μž…λ ₯λ°›κΈ°
v, e  = map(int, input().split())
parent = [0] * (v+1) # λΆ€λͺ¨ ν…Œμ΄λΈ” μ΄ˆκΈ°ν™”

# λΆ€λͺ¨ ν…Œμ΄λΈ”μ—μ„œ, λΆ€λͺ¨λ₯Ό 자기 μžμ‹ μœΌλ‘œ μ΄ˆκΈ°ν™”
for i in range(1, v+1):
    parent[i] = i

# union 연산을 각각 μˆ˜ν–‰
for i in range(e):
    a, b = map(int, input().split())
    union_parent(parent, a, b)

# 각 μ›μ†Œκ°€ μ†ν•œ 집합 좜λ ₯
print('각 μ›μ†Œκ°€ μ†ν•œ 집합: ', end='')
for i in range(1, v+1):
    print(find_parent(parent, i), end=' ')

print()

# λΆ€λͺ¨ ν…Œμ΄λΈ” λ‚΄μš© 좜λ ₯
print('λΆ€λͺ¨ ν…Œμ΄λΈ”: ', end='')
for i in range(1, v+1):
    print(parent[i], end=' ')

 

합집합(Union) 연산이 편ν–₯되게 μ΄λ£¨μ–΄μ§€λŠ” 경우 μ°ΎκΈ°(Find) ν•¨μˆ˜κ°€ λΉ„νš¨μœ¨μ μœΌλ‘œ λ™μž‘ν•©λ‹ˆλ‹€.

μ΅œμ•…μ˜ κ²½μš°μ—λŠ” μ°ΎκΈ°(Find) ν•¨μˆ˜κ°€ λͺ¨λ“  λ…Έλ“œλ₯Ό λ‹€ ν™•μΈν•˜κ²Œ λ˜μ–΄ μ‹œκ°„ λ³΅μž‘λ„κ°€ O(V)μž…λ‹ˆλ‹€.

 

경둜 μ••μΆ• 

μ°ΎκΈ°(Find) ν•¨μˆ˜λ₯Ό μ΅œμ ν™”ν•˜κΈ° μœ„ν•œ λ°©λ²•μœΌλ‘œ 경둜 μ••μΆ•(Path Compression)을 μ΄μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    • μ°ΎκΈ°(Find) ν•¨μˆ˜λ₯Ό μž¬κ·€μ μœΌλ‘œ ν˜ΈμΆœν•œ 뒀에 λΆ€λͺ¨ ν…Œμ΄λΈ” 값을 λ°”λ‘œ κ°±μ‹ ν•©λ‹ˆλ‹€.

# νŠΉμ • μ›μ†Œκ°€ μ†ν•œ 집합을 μ°ΎκΈ°
def find_parent(parent, x):
    # 루트 λ…Έλ“œκ°€ μ•„λ‹ˆλΌλ©΄, 루트 λ…Έλ“œλ₯Ό 찾을 λ•ŒκΉŒμ§€ μž¬κ·€μ μœΌλ‘œ 호좜
    if parent[x] != x:
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

경둜 μ••μΆ• 기법을 μ μš©ν•˜λ©΄ 각 λ…Έλ“œμ— λŒ€ν•˜μ—¬ μ°ΎκΈ°(Find) ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œ 이후에 ν•΄λ‹Ή λ…Έλ“œμ˜ λ£¨νŠΈλ…Έλ“œκ°€ λ°”λ‘œ λΆ€λͺ¨ λ…Έλ“œκ°€ λ©λ‹ˆλ‹€.

λ™μΌν•œ μ˜ˆμ‹œμ— λŒ€ν•΄μ„œ λͺ¨λ“  합집합(Union) ν•¨μˆ˜λ₯Ό μ²˜λ¦¬ν•œ ν›„ 각 μ›μ†Œμ— λŒ€ν•˜μ—¬ μ°ΎκΈ°(Find) ν•¨μˆ˜λ₯Ό μˆ˜ν–‰ν•˜λ©΄ λ‹€μŒκ³Ό 같이 λΆ€λͺ¨ ν…Œμ΄λΈ”μ΄ κ°±μ‹ λ©λ‹ˆλ‹€.

기본적인 방법에 λΉ„ν•˜μ—¬ μ‹œκ°„λ³΅μž‘λ„κ°€ κ°œμ„ λ©λ‹ˆλ‹€.

 

1-1. μ„œλ‘œμ†Œ 집합을 ν™œμš©ν•œ 사이클 νŒλ³„

 μ„œλ‘œμ†Œ 집합은 무방ν–₯ κ·Έλž˜ν”„ λ‚΄μ—μ„œ 사이클을 νŒλ³„ν•  λ•Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

    • 참고둜 λ°©ν–₯ κ·Έλž˜ν”„μ—μ„œμ˜ 사이클 μ—¬λΆ€λŠ” DFSλ₯Ό μ΄μš©ν•˜μ—¬ νŒλ³„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 μ‚¬μ΄ν΄ νŒλ³„ μ•Œκ³ λ¦¬μ¦˜μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

    1. 각 간선을 ν•˜λ‚˜μ”© ν™•μΈν•˜λ©° 두 λ…Έλ“œμ˜ 루트 λ…Έλ“œλ₯Ό ν™•μΈν•©λ‹ˆλ‹€.

         1) 루트 λ…Έλ“œκ°€ μ„œλ‘œ λ‹€λ₯΄λ‹€λ©΄ 두 λ…Έλ“œμ— λŒ€ν•˜μ—¬ 합집합(Union) 연산을 μˆ˜ν–‰ν•©λ‹ˆλ‹€.

         2) 루트 λ…Έλ“œκ°€ μ„œλ‘œ κ°™λ‹€λ©΄ 사이클(Cycle)이 λ°œμƒν•œ κ²ƒμž…λ‹ˆλ‹€.

    2. κ·Έλž˜ν”„μ— ν¬ν•¨λ˜μ–΄ μžˆλŠ” λͺ¨λ“  간선에 λŒ€ν•˜μ—¬ 1번 과정을 λ°˜λ³΅ν•©λ‹ˆλ‹€.

# νŠΉμ • μ›μ†Œκ°€ μ†ν•œ 집합을 μ°ΎκΈ°
def find_parent(parent, x):
    # 루트 λ…Έλ“œκ°€ μ•„λ‹ˆλΌλ©΄, 루트 λ…Έλ“œλ₯Ό 찾을 λ•ŒκΉŒμ§€ μž¬κ·€μ μœΌλ‘œ 호좜
    if parent[x] != x:
        # 루트 λ…Έλ“œλ₯Ό κ²Œμ† κ°±μ‹ μ‹œμΌœμ€€λ‹€.
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

# 두 μ›μ†Œκ°€ μ†ν•œ 집합을 ν•©μΉ˜κΈ°
def union_parent(parent, a, b):
    a = find_parent(parent, a)
    b = find_parent(parent, b)
    if a < b:
        parent[b] = a
    else:
        parent[a] = b

# λ…Έλ“œμ˜ κ°œμˆ˜μ™€ κ°„μ„ (union μ—°μ‚°)의 개수 μž…λ ₯λ°›κΈ°
v, e = map(int, input().split())
parent = [0]*(v+1) # λΆ€λͺ¨ν…Œμ΄λΈ” μ΄ˆκΈ°ν™”

# λΆ€λͺ¨ ν…Œμ΄λΈ”μƒμ—μ„œ, λΆ€λͺ¨λ₯Ό 자기 μžμ‹ μœΌλ‘œ μ΄ˆκΈ°ν™”
for i in range(1, v+1):
    parent[i] = i

cycle = False # 사이클 λ°œμƒ μ—¬λΆ€

for i in range(e):
    a, b = map(int, input().split())
    # 사이클이 λ°œμƒν•œ 경우 μ’…λ£Œ
    if find_parent(parent, a) == find_parent(parent, b):
        cycle = True
        break
    # 사이클이 λ°œμƒν•˜μ§€ μ•Šμ•˜λ‹€λ©΄ 합집합(union) μˆ˜ν–‰
    else:
        union_parent(parent, a, b)

if cycle:
    print("사이클이 λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.")
else:
    print("사이클이 λ°œμƒν•˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")

 μ‹ μž₯트리  : κ·Έλž˜ν”„μ—μ„œ λͺ¨λ“  λ…Έλ“œλ₯Ό ν¬ν•¨ν•˜λ©΄μ„œ 사이클이 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” λΆ€λΆ„ κ·Έλž˜ν”„λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€.

    • λͺ¨λ“  λ…Έλ“œκ°€ ν¬ν•¨λ˜μ–΄ μ„œλ‘œ μ—°κ²°λ˜λ©΄μ„œ 사이클이 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” 쑰건은 트리의 쑰건이기도 ν•©λ‹ˆλ‹€.

 

μ΅œμ†Œμ‹ μž₯ 트리 

  μ΅œμ†Œν•œμ˜ λΉ„μš©μœΌλ‘œ κ΅¬μ„±λ˜λŠ” μ‹ μž₯ 트리λ₯Ό μ°Ύμ•„μ•Ό ν•  λ•Œ μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒμš”?

  예λ₯Ό λ“€μ–΄ N개의 λ„μ‹œκ°€ μ‘΄μž¬ν•˜λŠ” μƒν™©μ—μ„œ 두 λ„μ‹œ 사이에 λ„λ‘œλ₯Ό 놓아 전체 λ„μ‹œκ°€ μ„œλ‘œ 연결될 수 있게 λ„λ‘œλ₯Ό μ„€μΉ˜ν•˜λŠ” 경우λ₯Ό μƒκ°ν•΄λ΄…μ‹œλ‹€.

    •  두 λ„μ‹œ A, Bλ₯Ό μ„ νƒν–ˆμ„ λ•Œ Aμ—μ„œ B둜 μ΄λ™ν•˜λŠ” κ²½λ‘œκ°€ λ°˜λ“œμ‹œ μ‘΄μž¬ν•˜λ„λ‘ λ„λ‘œλ₯Ό μ„€μΉ˜ν•©λ‹ˆλ‹€.

 

크루슀칼 μ•Œκ³ λ¦¬μ¦˜ 

  λŒ€ν‘œμ μΈ μ΅œμ†Œ μ‹ μž₯ 트리 μ•Œκ³ λ¦¬μ¦˜μž…λ‹ˆλ‹€.

 

  κ·Έλ¦¬λ”” μ•Œκ³ λ¦¬μ¦˜μœΌλ‘œ λΆ„λ₯˜λ©λ‹ˆλ‹€.

  κ΅¬μ²΄μ μΈ λ™μž‘κ³Όμ •μ€ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

 

    1. κ°„μ„  데이터λ₯Ό λΉ„μš©μ— 따라 μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬ν•©λ‹ˆλ‹€.

    2. 간선을 ν•˜λ‚˜μ”© ν™•μΈν•˜λ©° ν˜„μž¬μ˜ 간선이 사이클을 λ°œμƒμ‹œν‚€λŠ”μ§€ ν™•μΈν•©λ‹ˆλ‹€.

        1) 사이클이 λ°œμƒν•˜μ§€ μ•ŠλŠ” 경우 μ΅œμ†Œ μ‹ μž₯νŠΈλ¦¬μ— ν¬ν•¨μ‹œν‚΅λ‹ˆλ‹€.

        2) 사이클이 λ°œμƒν•˜λŠ” 경우 μ΅œμ†Œ μ‹ μž₯νŠΈλ¦¬μ— ν¬ν•¨μ‹œν‚€μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

    3. λͺ¨λ“  간선에 λŒ€ν•˜μ—¬ 2번의 과정을 λ°˜λ³΅ν•©λ‹ˆλ‹€.

 

# νŠΉμ • μ›μ†Œκ°€ μ†ν•œ 집합을 μ°ΎκΈ°
def find_parent(parent, x):
    # 루트 λ…Έλ“œκ°€ μ•„λ‹ˆλΌλ©΄, 루트 λ…Έλ“œλ₯Ό 찾을 λ•ŒκΉŒμ§€ μž¬κ·€μ μœΌλ‘œ 호좜
    if parent[x] != x:
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

# 두 μ›μ†Œκ°€ μ†ν•œ 집합을 ν•©μΉ˜κΈ°
def union_parent(parent, a, b):
    a = find_parent(parent, a)
    b = find_parent(parent, b)
    if a < b:
        parent[b] = a
    else:
        parent[a] = b

# λ…Έλ“œμ˜ κ°œμˆ˜μ™€ κ°„μ„ (union μ—°μ‚°)의 개수 μž…λ ₯λ°›κΈ°
v, e = map(int, input().split())
parent = [0] * (v+1) # λΆ€λͺ¨ ν…Œμ΄λΈ” μ΄ˆκΈ°ν™”

# λͺ¨λ“  간선을 담을 λ¦¬μŠ€νŠΈμ™€ μ΅œμ’…λΉ„μš©μ„ 담을 λ³€μˆ˜
edges = []
result = 0

# λΆ€λͺ¨ ν…Œμ΄λΈ” μƒμ—μ„œ, λΆ€λͺ¨λ₯Ό 자기 μžμ‹ μœΌλ‘œ μ΄ˆκΈ°ν™”
for i in range(1, v+1):
    parent[i] = i

# λͺ¨λ“  간선에 λŒ€ν•œ 정보λ₯Ό μž…λ ₯λ°›κΈ°
for _ in range(e):
    a, b, cost = map(int, input().split())
    # λΉ„μš©μˆœμœΌλ‘œ μ •λ ¬ν•˜κΈ° μœ„ν•΄μ„œ νŠœν”Œμ˜ 첫 번째 μ›μ†Œλ₯Ό λΉ„μš©μœΌλ‘œ μ„€μ •
    edges.append((cost, a, b))

# 간선을 λΉ„μš©μˆœμœΌλ‘œ μ •λ ¬
edges.sort()

# 간선을 ν•˜λ‚˜μ”© ν™•μΈν•˜λ©°
for edges in edges:
    cost, a, b = edges
    # 사이클이 λ°œμƒν•˜μ§€ μ•ŠλŠ” κ²½μš°μ—λ§Œ 집합에 포함
    if find_parent(parent, a) != find_parent(parent, b):
        union_parent(parent, a, b)
        result += cost

print(result)

 

  크루슀칼 μ•Œκ³ λ¦¬μ¦˜μ€ κ°„μ„ μ˜ κ°œμˆ˜κ°€ E개일 λ•Œ, O(ElogE)의 μ‹œκ°„ λ³΅μž‘λ„λ₯Ό κ°€μ§‘λ‹ˆλ‹€.

  크루슀칼 μ•Œκ³ λ¦¬μ¦˜μ—μ„œ κ°€μž₯ λ§Žμ€ μ‹œκ°„μ„ μš”κ΅¬ν•˜λŠ” 곳은 κ°„μ„ μ˜ 정렬을 μˆ˜ν–‰ν•˜λŠ” λΆ€λΆ„μž…λ‹ˆλ‹€.

    • ν‘œμ€€ 라이브러리λ₯Ό μ΄μš©ν•΄ E개의 데이터λ₯Ό μ •λ ¬ν•˜κΈ° μœ„ν•œ μ‹œκ°„ λ³΅μž‘λ„λŠ” O(ElogE) μž…λ‹ˆλ‹€.

 

κ°•μ˜ 및 좜처