Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- Python
- group by
- softeer
- level 3
- dfs
- Lv. 1
- Dynamic Programming
- 소프티어
- SQL
- 오블완
- programmers
- Lv. 0
- 티스토리챌린지
- join
- C언어
- 너비 우선 탐색
- LEVEL 2
- select
- 자바스크립트
- DP
- javascript
- Java
- bfs
- Lv. 2
- 동적계획법
- 프로그래머스
- Lv. 3
- 파이썬
- 깊이 우선 탐색
- SQL 고득점 KIT
Archives
- Today
- Total
몸과 마음이 건전한 SW 개발자
[요약] 자료 구조 - 4 [그래프, 신장 트리, 균형 이진 트리, AVL, 히프] 본문
※ 주의 ※
이 블로그는 어디까지나 CS관련 지식을 정리하는 것이 목적입니다. 제가 이해한 내용이 잘못 된 것 같다면 댓글로 남겨주세요. 여러분의 관심이 저의 지식 함양에 도움이 됩니다.
>> 그래프의 개념과 종류
- 그래프는 다양한 객체와 객체 간의 관계를 표현하는 데 적합한 자료구조입니다. 일반적인 선형 자료구조나 트리 자료구조로는 표현하기 어려운 복잡한 관계를 모델링할 수 있습니다. 그래프는 다음과 같은 기본 개념과 종류로 나뉩니다:
- 그래프의 기본 개념
- 정점 (Vertex): 그래프에서 객체를 나타내는 요소입니다.
- 간선 (Edge): 정점 간의 연결 관계를 나타내는 요소입니다.
- 그래프 G = (V, E): 여기서 V는 그래프의 정점들의 집합, E는 정점을 연결하는 간선들의 집합입니다.
- 그래프는 다양한 상황을 모델링할 수 있으며, 예를 들어, 교통 노선도, 사회적 네트워크, 수도 배관 시스템, 분자 구조 등이 있습니다.
>> 그래프의 구현 방법
1. 인접행렬 (Adjacency Matrix)
- 개념: 2차원 배열을 사용하여 그래프를 표현합니다.
- 구성:
- n x n 정방행렬: n은 그래프의 정점 수입니다.
- 행 번호와 열 번호: 그래프의 정점을 나타냅니다.
- 행렬 값: 두 정점이 인접하면 1, 아니면 0입니다.
- 무방향 그래프: 행 i의 합과 열 i의 합이 같으며, 이 값은 정점 i의 차수와 같습니다.
- 방향 그래프: 행 i의 합은 정점 i의 진출 차수, 열 i의 합은 정점 i의 진입 차수입니다.
예시 (Python):
def create_adjacency_matrix(vertices, edges):
matrix = [[0] * len(vertices) for _ in range(len(vertices))]
for (u, v) in edges:
matrix[u][v] = 1
matrix[v][u] = 1 # For undirected graph
return matrix
2. 인접리스트 (Adjacency List)
- 개념: 각 정점에 대해 인접한 정점들의 리스트를 연결하여 그래프를 표현합니다.
- 구성:
- 각 정점은 인접 정점들과 연결된 단순 연결 리스트를 가집니다.
- 각 정점에 대한 헤드 포인터 배열을 사용하여 인접 리스트를 구현합니다.
- 장점: 메모리 사용이 더 효율적이며, 동적 그래프의 경우 삽입과 삭제가 용이합니다.
예시 (Python):
from collections import defaultdict
def create_adjacency_list(vertices, edges):
adj_list = defaultdict(list)
for (u, v) in edges:
adj_list[u].append(v)
adj_list[v].append(u) # For undirected graph
return adj_list
>> 그래프의 종류
- 무방향 그래프 (Undirected Graph): 간선에 방향이 없는 그래프입니다. 간선이 양쪽 정점을 연결합니다.
- 방향 그래프 (Directed Graph): 간선에 방향이 있는 그래프입니다. 간선이 특정 방향으로 정점을 연결합니다.
- 가중 그래프 (Weighted Graph): 간선에 가중치가 있는 그래프입니다. 가중치는 간선의 비용이나 거리 등을 나타냅니다.
- 비가중 그래프 (Unweighted Graph): 간선에 가중치가 없는 그래프입니다. 모든 간선의 가중치는 동일하다고 간주됩니다.
- 단순 그래프 (Simple Graph): 자기 루프가 없고, 모든 간선이 두 정점을 연결하는 그래프입니다.
- 완전 그래프 (Complete Graph): 모든 정점이 서로 연결되어 있는 그래프입니다.
>> 그래프의 순회: 깊이 우선 탐색과 너비 우선 탐색
- 그래프의 탐색은 정점들을 방문하는 방법을 정의하며, 두 가지 주요 방법이 있습니다: 깊이 우선 탐색(DFS)과 너비 우선 탐색(BFS). 각 방법은 특정 상황에서 유용하며, 알고리즘의 동작 방식과 활용도가 다릅니다.
1. 깊이 우선 탐색 (Depth-First Search, DFS)
- 개념: 시작 정점에서 가능한 한 깊이 탐색을 진행하다가 더 이상 갈 곳이 없으면, 가장 마지막에 만났던 갈림길로 되돌아와 다른 방향으로 탐색을 계속하는 방법입니다.
- 구현:
- 스택 사용: DFS는 후입선출(LIFO) 구조의 스택을 사용하여 마지막에 만난 갈림길에서부터 탐색을 재개합니다. 재귀 호출을 사용하거나 명시적 스택을 사용할 수 있습니다.
- 순서: 시작 정점에서 출발하여 인접한 정점을 차례로 깊이 탐색 후, 모든 정점을 방문합니다.
예시 (Python):
def dfs(graph, start, visited=None):
if visited is None:
visited = set()
visited.add(start)
for neighbor in graph[start]:
if neighbor not in visited:
dfs(graph, neighbor, visited)
return visited
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F', 'G'],
'D': ['B'],
'E': ['B', 'H'],
'F': ['C'],
'G': ['C'],
'H': ['E']
}
print(dfs(graph, 'A'))
2. 너비 우선 탐색 (Breadth-First Search, BFS)
- 개념: 시작 정점에서 인접한 정점을 모두 방문하고, 그 다음 단계에서는 방문했던 정점에서 다시 인접한 정점을 차례로 방문하는 방식입니다. 가까운 정점부터 방문합니다.
- 구현:
- 큐 사용: BFS는 선입선출(FIFO) 구조의 큐를 사용하여 가까운 정점부터 차례로 탐색합니다.
- 순서: 시작 정점에서 출발하여 인접한 모든 정점을 먼저 방문한 후, 그 다음 단계의 정점들을 방문합니다.
예시 (Python):
from collections import deque
def bfs(graph, start):
visited = set()
queue = deque([start])
while queue:
vertex = queue.popleft()
if vertex not in visited:
visited.add(vertex)
queue.extend(neighbor for neighbor in graph[vertex] if neighbor not in visited)
return visited
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F', 'G'],
'D': ['B'],
'E': ['B', 'H'],
'F': ['C'],
'G': ['C'],
'H': ['E']
}
print(bfs(graph, 'A'))
>> 신장 트리 (Spanning Tree)
- 개념: 무방향 그래프에서 모든 정점을 포함하면서 사이클이 없는 연결 그래프입니다. 정점의 개수(n)에 대해 간선의 개수는 n-1입니다.
- 생성:
- 깊이 우선 탐색 신장 트리: DFS를 사용하여 신장 트리를 생성합니다.
- 너비 우선 탐색 신장 트리: BFS를 사용하여 신장 트리를 생성합니다.
- 최소 비용 신장 트리 (Minimum Spanning Tree, MST)
- 개념: 무방향 가중치 그래프에서 신장 트리를 구성하는 간선들의 가중치 합이 최소가 되도록 구성된 신장 트리입니다. 가중치는 간선의 비용, 거리, 시간 등을 나타냅니다.
- 알고리즘:
- 크루스칼의 알고리즘 (Kruskal's Algorithm): 모든 간선을 가중치에 따라 정렬한 후, 사이클이 형성되지 않도록 간선을 선택하여 신장 트리를 구성합니다.
- 프림의 알고리즘 (Prim's Algorithm): 하나의 정점에서 시작하여, 가장 적은 가중치의 간선을 선택하여 트리를 확장합니다.
크루스칼의 알고리즘 예시 (Python):
class UnionFind:
def __init__(self, n):
self.parent = list(range(n))
self.rank = [0] * n
def find(self, u):
if self.parent[u] != u:
self.parent[u] = self.find(self.parent[u])
return self.parent[u]
def union(self, u, v):
root_u = self.find(u)
root_v = self.find(v)
if root_u != root_v:
if self.rank[root_u] > self.rank[root_v]:
self.parent[root_v] = root_u
elif self.rank[root_u] < self.rank[root_v]:
self.parent[root_u] = root_v
else:
self.parent[root_v] = root_u
self.rank[root_u] += 1
def kruskal(n, edges):
edges.sort(key=lambda x: x[2])
uf = UnionFind(n)
mst = []
for u, v, weight in edges:
if uf.find(u) != uf.find(v):
uf.union(u, v)
mst.append((u, v, weight))
return mst
edges = [
(0, 1, 10),
(0, 2, 6),
(0, 3, 5),
(1, 3, 15),
(2, 3, 4)
]
print(kruskal(4, edges))
프림의 알고리즘 예시 (Python):
import heapq
def prim(n, edges):
adj = [[] for _ in range(n)]
for u, v, weight in edges:
adj[u].append((weight, v))
adj[v].append((weight, u))
mst = []
min_heap = [(0, 0)]
visited = set()
while min_heap:
weight, u = heapq.heappop(min_heap)
if u not in visited:
visited.add(u)
mst.append((weight, u))
for edge_weight, v in adj[u]:
if v not in visited:
heapq.heappush(min_heap, (edge_weight, v))
return mst[1:]
edges = [
(0, 1, 10),
(0, 2, 6),
(0, 3, 5),
(1, 3, 15),
(2, 3, 4)
]
print(prim(4, edges))
>> 이진 탐색 트리의 탐색, 삽입, 삭제 연산
1. 이진 탐색 트리 (Binary Search Tree, BST)
- 이진 탐색 트리는 각 노드가 다음과 같은 성질을 가지는 트리입니다:
- 모든 노드는 왼쪽 서브트리에 있는 모든 노드보다 큰 값
- 모든 노드는 오른쪽 서브트리에 있는 모든 노드보다 작은 값
- 이진 탐색 트리에서의 주요 연산은 탐색, 삽입, 삭제입니다.
- 탐색 연산
- 목표: 주어진 값이 트리에 존재하는지 확인합니다.
- 절차:
- 현재 노드의 값과 검색할 값을 비교합니다.
- 검색할 값이 현재 노드의 값보다 작으면 왼쪽 서브트리로 이동합니다.
- 검색할 값이 현재 노드의 값보다 크면 오른쪽 서브트리로 이동합니다.
- 현재 노드가 None일 때까지 이 과정을 반복합니다.
예시 (Python):
class Node:
def __init__(self, key):
self.left = None
self.right = None
self.value = key
def search(root, key):
if root is None or root.value == key:
return root
if root.value < key:
return search(root.right, key)
return search(root.left, key)
- 삽입 연산
- 목표: 주어진 값을 이진 탐색 트리에 삽입합니다.
- 절차:
- 먼저 탐색을 통해 삽입할 위치를 찾습니다. 탐색 중 같은 값이 발견되면 삽입을 건너뜁니다.
- 적절한 위치에 새 노드를 삽입합니다.
예시 (Python):
def insert(root, key):
if root is None:
return Node(key)
if key < root.value:
root.left = insert(root.left, key)
else:
root.right = insert(root.right, key)
return root
- 삭제 연산
- 목표: 주어진 값을 트리에서 삭제합니다.
- 절차:
- 삭제할 노드를 탐색하여 찾습니다.
- 노드 삭제에는 세 가지 경우가 있습니다:
- 삭제할 노드가 리프 노드: 노드를 직접 삭제합니다.
- 삭제할 노드가 하나의 자식만 있는 경우: 삭제할 노드를 삭제하고, 그 자식을 삭제된 노드의 자리에 삽입합니다.
- 삭제할 노드가 두 자식을 가진 경우: 오른쪽 서브트리에서 최소값을 가진 노드를 찾아서 삭제할 노드의 값으로 대체하고, 그 노드를 삭제합니다.
예시 (Python):
def minValueNode(node):
current = node
while current.left is not None:
current = current.left
return current
def deleteNode(root, key):
if root is None:
return root
if key < root.value:
root.left = deleteNode(root.left, key)
elif key > root.value:
root.right = deleteNode(root.right, key)
else:
if root.left is None:
return root.right
elif root.right is None:
return root.left
temp = minValueNode(root.right)
root.value = temp.value
root.right = deleteNode(root.right, temp.value)
return root
>> 균형 이진 탐색 트리와 AVL 트리
- 균형 이진 탐색 트리는 노드들의 균형을 유지하여 탐색, 삽입, 삭제 연산의 시간 복잡도를 O(log n)으로 보장하는 트리입니다.
- AVL 트리
- AVL 트리는 각 노드의 왼쪽 서브트리와 오른쪽 서브트리의 높이 차이가 1 이하인 균형 이진 탐색 트리입니다. 주요 특징은 다음과 같습니다:
- 균형 인수: 각 노드의 왼쪽 서브트리와 오른쪽 서브트리의 높이 차이. 이 값은 -1, 0, +1이 될 수 있습니다.
- 균형 유지: 삽입 및 삭제 연산 후 균형을 맞추기 위해 회전 연산을 수행합니다.
- AVL 트리는 각 노드의 왼쪽 서브트리와 오른쪽 서브트리의 높이 차이가 1 이하인 균형 이진 탐색 트리입니다. 주요 특징은 다음과 같습니다:
- AVL 트리의 회전 연산
- 회전 연산은 AVL 트리의 균형을 맞추기 위해 사용됩니다. 기본적으로 단일 회전과 이중 회전이 있습니다.
- 단순 회전:
- LL 회전: 삽입이나 삭제로 인해 왼쪽 서브트리가 너무 높아질 때 발생합니다. 현재 노드의 왼쪽 자식 노드로 회전합니다.
- RR 회전: 삽입이나 삭제로 인해 오른쪽 서브트리가 너무 높아질 때 발생합니다. 현재 노드의 오른쪽 자식 노드로 회전합니다.
- 이중 회전:
- LR 회전: 왼쪽 서브트리의 오른쪽 자식이 높아질 때 발생합니다. 먼저 왼쪽 자식 노드에 대해 RR 회전을 수행한 후, 현재 노드에 대해 LL 회전을 수행합니다.
- RL 회전: 오른쪽 서브트리의 왼쪽 자식이 높아질 때 발생합니다. 먼저 오른쪽 자식 노드에 대해 LL 회전을 수행한 후, 현재 노드에 대해 RR 회전을 수행합니다.
회전 연산 예시 (Python):
def rightRotate(y):
x = y.left
T2 = x.right
x.right = y
y.left = T2
return x
def leftRotate(x):
y = x.right
T2 = y.left
y.left = x
x.right = T2
return y
def balance(node):
if not node:
return node
balance_factor = height(node.left) - height(node.right)
if balance_factor > 1:
if height(node.left.left) >= height(node.left.right):
return rightRotate(node)
else:
node.left = leftRotate(node.left)
return rightRotate(node)
if balance_factor < -1:
if height(node.right.right) >= height(node.right.left):
return leftRotate(node)
else:
node.right = rightRotate(node.right)
return leftRotate(node)
return node
def height(node):
if not node:
return 0
return 1 + max(height(node.left), height(node.right))
>> 히프(Heap)의 개념과 추상 자료형
- 히프의 개념
- 히프는 완전 이진 트리를 기반으로 하는 자료구조로, 각 노드의 키값이 특정한 조건을 만족하는 트리입니다. 주로 두 가지 형태가 있습니다:
- 최대 히프 (Max-Heap):
- 각 노드의 키값이 자식 노드의 키값보다 크거나 같습니다.
- 루트 노드는 히프에서 가장 큰 키값을 가집니다.
- 최소 히프 (Min-Heap):
- 각 노드의 키값이 자식 노드의 키값보다 작거나 같습니다.
- 루트 노드는 히프에서 가장 작은 키값을 가집니다.
- 최대 히프 (Max-Heap):
- 완전 이진 트리는 모든 레벨이 완전히 채워져 있으며, 마지막 레벨은 왼쪽부터 차례로 채워지는 트리입니다.
- 히프의 추상 자료형 (Abstract Data Type, ADT)
- 히프의 추상 자료형은 다음과 같습니다:
- 데이터:
- 원소 n개로 구성된 완전 이진 트리.
- 각 노드의 키값은 자식 노드의 키값보다 크거나 같거나 작거나 같습니다 (최대 히프 또는 최소 히프에 따라 다름).
- 연산:
- 생성: 빈 히프를 생성합니다.
- 공백 검사: 히프가 비어 있는지 검사합니다.
- 삽입: 새로운 원소를 히프에 삽입합니다.
- 삭제: 히프에서 원소를 삭제합니다.
- 히프의 삽입 연산
- 완전 이진 트리의 조건을 유지:
- 새로운 원소를 히프의 마지막 위치에 추가하여 완전 이진 트리의 구조를 유지합니다.
- 크기 조건을 만족:
- 새로 추가된 원소를 적절한 위치로 이동시켜 히프의 속성을 유지합니다.
- 부모 노드와의 비교를 통해 히프 속성을 만족하도록 조정합니다 (상향식 조정).
- 완전 이진 트리의 조건을 유지:
예시 (Python):
def heapify_up(heap, index):
parent = (index - 1) // 2
if index > 0 and heap[index] > heap[parent]: # 최대 히프의 경우
heap[index], heap[parent] = heap[parent], heap[index]
heapify_up(heap, parent)
def insert(heap, value):
heap.append(value)
heapify_up(heap, len(heap) - 1)
- 히프의 삭제 연산
- 루트 노드의 원소 삭제:
- 루트 노드를 삭제하고, 히프의 마지막 원소를 루트 위치로 이동시킵니다.
- 원소 개수 조정:
- 원소의 개수를 n-1로 줄여서 조정합니다.
- 히프 속성 유지:
- 루트 노드에서 시작하여 적절한 위치로 이동시킵니다 (하향식 조정).
- 루트 노드의 원소 삭제:
예시 (Python):
def heapify_down(heap, index):
size = len(heap)
largest = index
left = 2 * index + 1
right = 2 * index + 2
if left < size and heap[left] > heap[largest]: # 최대 히프의 경우
largest = left
if right < size and heap[right] > heap[largest]: # 최대 히프의 경우
largest = right
if largest != index:
heap[index], heap[largest] = heap[largest], heap[index]
heapify_down(heap, largest)
def delete_root(heap):
if len(heap) == 0:
return None
root_value = heap[0]
heap[0] = heap.pop() # 루트 노드의 값을 삭제하고 마지막 노드를 루트로 이동
heapify_down(heap, 0)
return root_value
>> 순차 자료구조를 이용한 히프의 구현
- 히프는 1차원 배열을 사용하여 구현할 수 있습니다. 이 배열에서의 인덱스 관계를 통해 부모 노드와 자식 노드를 쉽게 찾을 수 있습니다.
- 부모 노드 인덱스: i번째 노드의 부모 노드는 인덱스 (i - 1) // 2에 위치합니다.
- 왼쪽 자식 노드 인덱스: i번째 노드의 왼쪽 자식 노드는 인덱스 2 * i + 1에 위치합니다.
- 오른쪽 자식 노드 인덱스: i번째 노드의 오른쪽 자식 노드는 인덱스 2 * i + 2에 위치합니다.
'CS 지식 > 자료 구조' 카테고리의 다른 글
[요약] 자료 구조 - 5 [정렬, 검색, 해싱] (0) | 2024.09.16 |
---|---|
[요약] 자료 구조 - 3 [스택, 큐, 데크, 트리, 이진 트리] (2) | 2024.09.15 |
[요약] 자료 구조 - 2 [순차(행렬), 선형(1, 2, 3차원 배열), 연결(단순, 원형, 이중, 다항식) 자료 구조] (5) | 2024.09.12 |
[요약] 자료 구조 - 1 [정의, 분류, 표현, 알고리즘, 배열, 포인터, 구조체, 재귀] (0) | 2024.07.31 |
[자료 구조] 정의, 분류, 표현 (0) | 2024.06.26 |