크래프톤 정글/TIL

[크래프톤 정글 5기] week02 알고리즘 주차 아홉번째 날, 분할 정복, 뮤터블, 그래프, 트리, DFS/BFS, 이진검색트리, 서로소집합

양선규 2024. 3. 29. 22:59
728x90
반응형

분할 정복(Divide and Conquer)

- 여러 알고리즘의 기본이 되는 해결 방법, 엄청나게 크고 방대한 문제를 작은 단위로 쪼개어, 결과적으로 큰 문제를 해결하는 방법

- 퀵 정렬, 병합 정렬, 이분 탐색 등이 대표적 예시이다.

- Divide를 제대로 나누면 Conquer은 자동으로 쉬워지기 때문에 Divide를 잘 설계하는 것이 매우 중요하다.

- 분할 정복에서는 재귀 알고리즘이 많이 사용되는데, 이 부분에서 분할 정복의 효율을 깎아내릴 수 있다.

 

분할 정복의 설계

1. Divide(분할)

- 원래의 문제를 분할하여, 비슷한 유형의 하위 문제로 더 이상 분할이 불가능할 때 까지 나눈다.

2. Conquer(정복)

- 각 하위 문제를 재귀적으로 해결한다. 더 이상 나눌 수 없는 단위일 때의 탈출 조건(해결법)을 설정하고 해결한다.

3. Combine(조합)

- 정복한 문제들의 답을 통합하여, 원래 문제의 답을 얻는다.

 

뮤터블(mutable, 가변)

- 변경이 가능한 객체 ( 특정 주소를 계속 가리키고 있다 )

- 생성 후 자유롭게 값을 변경, 추가, 삭제 등이 가능하다

- 변수의 값을 변경하면 객체 그 자체를 업데이트 한다.

- call by reference(참조에 의한 호출)

 

이뮤터블(immutable, 불변)

- 변경이 불가능한 객체 ( 값에 따라 가리키는 주소 자체가 달라진다 )

- 생성 후 값 변경, 추가, 삭제 등이 불가능하다.

- 숫자, string, tuple

- 변수의 값을 변경하면 다른 객체를 생성하고 그 객체에 대한 참조로 업데이트 된다. ( 다른 주소를 가리킨다 )

- 만약 두 변수가 같은 값을 가지고 있다면, 두 변수는 같은 주소를 가리킨다. 변경하려는 값이 이미 메모리에 존재한다면, 생성하지 않고 그 주소를 가리킨다.

- call by value(값에 의한 호출)

 

그래프(Graph)

- 정점(vertex)들의 집합과 이들을 연결하는 간선(edge)로 이루어진 자료 구조

- 간선(edge)은 아크(arc)로 불리기도 하며, 정점(vertex)는 노드(node)라고도 불린다.

- 각 노드들은 트리와 다르게 계층 구조가 아니다.

 

그래프와 트리의 차이점

- 그래프는 트리를 포함한다.

- 트리는 비순환 방식, 그래프는 순환/비순환 선택 가능 ( 싸이클 허용 여부에 따라 갈린다 )

- 트리는 방향이 있음, 그래프는 방향 여부 선택 가능

- 트리는 계층 모델, 그래프는 네트워크 모델

 

싸이클(순환, Cycle)

- 자기 자신에서 출발해서 자기 자신으로 들어오는 간선

- 싸이클을 허용하면 순환 그래프, 아니라면 비순환 그래프이다.

 

모델(구조)

- 그래프의 구조를 나타낸다.

- 계층 모델(구조) : 트리처럼 상하 계층 구조가 있는 그래프

- 네트워크 모델(구조) : 네트워크처럼 이리저리 얽혀있는 경우

 

그래프의 종류 3가지

방향 그래프(Directed Graph)

- 노드 간 한쪽 방향으로밖에 접근할 수 없다면 방향 그래프이다.

무방향 그래프(Undirected Graph)

- 노드 간 양방향으로 접근할 수 있다면 무방향 그래프이다.

가중 그래프(Weighted Graph)

- 간선에 가중치(비용)가 적용된 그래프. ( 거리, 비용, 가중치 등 표현은 다양하다 )

- 방향/무방향 둘 다 적용될 수 있다.

 

 

 

트리 구조와 관련 용어

트리

루트 : 가장 위에 위치하는 노드 (8번 노드)

리프 : 자식이 없는 노드(1, 4, 7, 13번 노드)

내부 노드(비단말 노드) : 리프를 제외한 노드(루트, 3, 6, 10, 14번 노드)

부모 : 어떤 노드와 연결된 위쪽노드

자식 : 어떤 노드와 연결된 아래쪽노드

형제 : 부모가 같은 노드

조상 : 어떤 노드에서 가지를 타고 위로 올라가면 만나는 모든 노드

자손 : 어떤 노드에서 가지를 타고 아래로 내려가면 만나는 모든 노드

레벨 : 루트에서 얼마나 멀리 떨어져 있는지를 나타내는 지표. 루트부터 0으로 시작해 가지를 타고 내려갈 때마다 1씩 증가. ex) 3: 1레벨, 13: 3레벨

차수 : 각 노드가 갖는 자식의 수. ex) 3: 2, 14: 1

- 모든 노드의 차수가 n 이하인 트리를 “n진 트리라고 한다.

- 모든 노드의 차수가 3 이하 : 삼진 트리

- 모든 노드의 차수가 2 이하 : 이진 트리

높이 : 루트에서 가장 멀리 있는 리프까지의 거리. -> 리프 레벨의 최댓값

서브트리 : 어떤 노드를 루트로 정의하고, 그 자손으로 구성된 트리를 서브트리 라고 한다.

ex) 3번이 루트라면 1과 6, 4, 7이 서브 트리

빈 트리((NULL)트리) : 노드와 가지가 전혀 없는 트리

 

순서 트리와 무순서 트리

- 순서 트리 : “형제 노드 간순서 관계가 있는 트리( 형제 간 정렬된 트리)

- 무순서 트리 : “형제 노드 간순서 관계가 없는 트리( 형제 간 정렬 안 된 트리)

기준을 순서 트리로 잡냐 무순서 트리로 잡냐에 따라 트리의 비교가 갈릴 수 있다.

 

순서 트리의 검색

BFS(너비 우선 탐색, Breadth-First-Search)

- 폭 우선 검색, 가로 검색, 수평 검색이라고도 함

- 낮은 레벨부터(루트부터) 왼쪽에서 오른쪽으로 검사한다.

- 가까운 노드부터 탐색하는 알고리즘

- 일반적으로 큐를 이용하고 파이썬에서는 deque를 사용하면 된다.

- O(N)의 시간복잡도를 가지며, 일반적인 경우 DFS보다 조금 빠르다

 

 

 

BFS 동작 방식

BFS1
BFS2

1. 탐색 시작 노드를 큐에 삽입하고 방문 처리

2. 큐에서 노드를 꺼내고, 해당 노드의 인접 노드 중 방문하지 않은 노드를 모두 큐에 삽입하고 방문 처리( 키가 낮은 노드를 먼저 삽입한다 )

3. 다시 선입선출 방식으로 노드를 하나씩 꺼내, 인접 노드를 큐에 넣고 방문처리 하는 걸 반복한다.

 

BFS 연습 코드 ( 파이썬 )

from collections import deque

graph = [
    [],
    [2, 3, 8],
    [1, 7],
    [1, 4, 5],
    [3, 5],
    [3, 4],
    [7],
    [2, 6, 8],
    [1, 7]
]

visited = [False] * 9

def BFS(graph, start, visited):
    queue = deque([start])
    visited[start] = True

    while queue:
        v = queue.popleft()
        print(v, end='')

        for i in graph[v]:
            if visited[i] == False:
                queue.append(i)
                visited[i] = True

BFS(graph, 1, visited)                                                                                  

 

graph : 노드와 간선 연결 정보를 저장한다. 인덱스는 출발 노드 번호, 값은 연결된 노드이다. 인덱스 번호와 맞추기 위해 노드갯수 + 1 하였다.

ex) 1번 노드는 2, 3, 8번과 연결되어 있다

그리고 이 코드는 무방향(양방향) 그래프 기준이다.

visited : 방문 여부를 저장할 리스트. 인덱스 번호와 맞추기 위해 노드갯수 + 1 하였다.

 

먼저 deque에 시작 노드 번호를 추가하고, 시작 노드를 방문처리한다.

이후 모든 노드를 방문할 때까지 while 반복문을 돌며,

큐에 있는 노드를 꺼낸 후 방문을 알리는 출력을 하고,

해당 노드의 인접 노드 정보를 가져와 아직 방문하지 않았다면 deque에 추가하고 방문처리한다.

반복이 끝나면 이런 결과가 나온다.

BFS 결과 출력

12387456은 노드 방문 순서이다. 이 코드를 응용해서 다른 BFS 문제들도 풀 수 있다.

 

DFS(깊이 우선 탐색, Depth-First-Search)

- 세로 검색, 수직 검색이라고도 함

- 리프에 도달할 때까지 아래로 내려가는 것을 우선하는 검색 방법

- 리프에 도달했다면 다시 부모 노드로 돌아간 후 다른 자식을 찾아 리프까지 내려가는 걸 반복

- 멀리 있는 노드부터 탐색하는 알고리즘

BFS DFS

어느 시점에 실제로 노드를 방문하는지에 따라, DFS는 세 종류의 스캔으로 나뉜다.

전위 순회(preorder)

- 노드 방문 -> 왼쪽 자식 -> 오른쪽 자식

중위 순회(inorder)

- 왼쪽 자식 -> 노드 방문 -> 오른쪽 자식

후위 순회(postorder)

- 왼쪽 자식 -> 오른쪽 자식 -> 노드 방문

순회 순서가 헷갈릴 수 있는데, 트리를 서브트리 단위로 나누어 순회하면 덜 헷갈린다.

 

균형 검색 트리(이진 균형 검색 트리)

- AVL 트리(AVL tree)

- 레드 블랙 트리(red-black tree)

 

균형 검색 트리(이진이 아닌 트리)

- B 트리(B tree)

- 2-3 트리(2-3 tree)

 

이진 "검색" 트리의 특징 ( 그냥 이진 트리 아님 )

- 왼쪽 서브트리 노드의 키값은 자신(부모)의 노드 키값보다 작아야 함

- 오른쪽 서브트리 노드의 키값은 자신(부모)의 노드 키값보다 커야 함

- 따라서 키값이 같은 노드는 없다

- 구조가 단순함

- 중위 순회의 DFS를 통해, 노드값을 오름차순으로 얻을 수 있음

- 이진 검색과 비슷한 방식으로 아주 빠르게 검색할 수 있음

- 노드를 삽입하기 쉬움

 

서로소 집합(Disjoint Sets)

- 공통 원소가 없는 두 집합

- 서로소 집합 자료구조는 몇몇 그래프 알고리즘에서 매우 중요하게 사용된다. ex) 크루스칼 알고리즘( 최소 스패닝 트리를 구하는 알고리즘이다 )

 

서로소 집합 자료구조

- 서로소 부분 집합들로 나누어진 원소들의 데이터를 처리하기 위한 자료구조

- 서로소 집합 자료구조는, unionfind 2개의 연산으로 조작할 수 있음

- union : 2개의 원소가 포함된 집합을 하나의 집합으로 합치는 연산

- find : 특정 원소가 속한 집합이 어떤 집합인지 알려주는 연산

- 스택이 push/pop 으로 이루어졌던 것 처럼, 서로소 집합 자료구조는 unionfind 연산으로 구성된다.

- 서로소 집합 자료구조는 union-find 자료구조라고 불리기도 한다

- 서로소 관계인지 확인한다는 것은, 각 집합이 어떤 원소를 공통으로 가지고 있는지 확인하는 것과 같다.

728x90
반응형