그리디 알고리즘?

매 순간 최선이라고 생각되는 선택을 하여 전체 문제의 최적해를 구하는 알고리즘 전략

탐욕법이라고 부르기도 한다.

 

특징

  • 탐욕적 선택 속성 : 매 순간 가장 최적의 선택을 하는 것이 최종 해답의 최적성을 보장하는 속성
  • 최적 부분 구조 : 전체 문제의 최적해가 부분 문제의 최적해들로 구성될 수 있는 구조

 

장점

  1. 빠른 시간 복잡도 : 보통 정렬 O(n log n) + 선형 스캔 O(n) 등으로 매우 효율적이다.
  2. 메모리 적음 : 상태 저장(테이블) 필요성이 적다.(대부분 상수 또는 선형)
  3. 구현 쉬움 : 코드가 짧고 디버깅이 쉽다.

 

단점

 

  1. 항상 최적이 아니다 : 잘못된 그리디 규칙은 전혀 최적해를 주지 못한다.
  2. 증명 필요 : 그리디 방법이 최적임을 보이려면 교환 주장(exchange argument) 등으로 증명해야 한다.
  3. 문제 인식 필요 : 문제의 구조를 잘 파악하지 못하면 그리디를 적용하면 틀린 경우도 있다.

 

문제 유형

● 활동 선택 문제

활동 번호 시작 시간 종료 시간
A1 1 2
A2 3 4
A3 0 6
A4 5 7
A5 8 9
activities = [(1,2), (3,4), (0,6), (5,7), (8,9)]
activities.sort(key=lambda x: x[1])  # end time 순 정렬

result = []
current_end = 0

for start, end in activities:
    if start >= current_end:
        result.append((start, end))
        current_end = end

print(result)  # [(1,2), (3,4), (5,7), (8,9)]

 

 

● 거스름돈 계산

 

 

목표: 최소 개수의 동전으로 특정 금액을 만들기

전략: 큰 동전부터 최대한 사용

 

단계 사용 동전 남은 금액 동전 개수
500원 사용 1260 → 1260 // 500 = 2개 1260 - 1000 = 260 2개
100원 사용 260 → 260 // 100 = 2개 260 - 200 = 60 4개
50원 사용 60 → 60 // 50 = 1개 60 - 50 = 10 5개
10원 사용 10 → 10 // 10 = 1개 10 - 10 = 0 ✅ 6개
coins = [500, 100, 50, 10]
target = 1260

count = 0
for coin in coins:
    count += target // coin  # 해당 동전을 최대 몇 개 쓸 수 있는가
    target %= coin           # 남은 잔돈 갱신

print(count)  # 출력: 6

 

 

'Etc > study' 카테고리의 다른 글

[4주차] 다익스트라 알고리즘  (0) 2025.10.19
[4주차] 유니온 파인드 알고리즘  (0) 2025.10.17
[4주차] 세그먼트 트리  (0) 2025.10.16
[3주차] 비선형 자료구조  (0) 2025.10.06
[2주차] 정렬 알고리즘  (0) 2025.09.30

다익스트라 알고리즘?

가중치가 있는 그래프에서 한 정점으로부터 다른 모든 정점까지의 최단 경로를 찾는 알고리즘

 

다익스트라 알고리즘 (출처 : 위키백과)

 

 

작동 순서

 

위 이미지에서 시작 정점인 a(1번 노드)에서 b(5번 노드) 정점으로 갈 때,

시작 정점 방문 정점 (현재 누적 가중치)
1번 노드 2번 노드 (7) 3번 노드 (7 + 10 = 17) 4번 노드 (7 + 10 + 11 = 28)
6번 노드 (7 + 10 + 9 = 26)
4번 노드 (7 + 15 = 22) 5번 노드 (7 + 15 + 6 = 28)
3번 노드 (9) 4번 노드 (9 + 11 = 20) 5번 노드 (9 + 11 + 6 = 26)
6번 노드 (9 + 2 = 11) 5번 노드 (9 + 2 + 9 = 20) >> 최단거리
6번 노드 (14) 5번 노드 (14 + 9 = 23)

 

최단 거리는 1번 노드 > 3번 노드 > 6번 노드 > 5번 노드 경로 임을 알 수 있다.

 

 

Python

작동 순서를 코드로 표현하면 다음과 같다.

import heapq

def dijkstra(start, graph):
    distances = {node: float('inf') for node in graph}
    distances[start] = 0
    parent = {node: None for node in graph}  # 경로 추적용
    queue = [(0, start)]

    while queue:
        current_dist, current_node = heapq.heappop(queue)

        if current_dist > distances[current_node]:
            continue

        for adjacent, weight in graph[current_node]:
            distance = current_dist + weight
            if distance < distances[adjacent]:
                distances[adjacent] = distance
                parent[adjacent] = current_node  # 어디서 왔는지 기록
                heapq.heappush(queue, (distance, adjacent))

    return distances, parent

def get_path(parent, start, end):
    path = []
    current = end
    while current is not None:
        path.append(current)
        current = parent[current]
    path.reverse()
    return path

# 그래프 (문제와 동일)
graph = {
    1: [(2, 7), (3, 9), (6, 14)],
    2: [(1, 7), (3, 10), (4, 15)],
    3: [(1, 9), (2, 10), (4, 11), (6, 2)],
    4: [(2, 15), (3, 11), (5, 6)],
    5: [(4, 6), (6, 9)],
    6: [(1, 14), (3, 2), (5, 9)]
}

# 실행
start, end = 1, 5
distances, parent = dijkstra(start, graph)
shortest_path = get_path(parent, start, end)

print(f"1번 → 5번 최단 거리: {distances[end]}")
print(f"1번 → 5번 최단 경로: {' -> '.join(map(str, shortest_path))}")

 

실행 결과

1번 → 5번 최단 거리: 20
1번 → 5번 최단 경로: 1 -> 3 -> 6 -> 5

'Etc > study' 카테고리의 다른 글

[5주차] 그리디 알고리즘  (0) 2025.10.22
[4주차] 유니온 파인드 알고리즘  (0) 2025.10.17
[4주차] 세그먼트 트리  (0) 2025.10.16
[3주차] 비선형 자료구조  (0) 2025.10.06
[2주차] 정렬 알고리즘  (0) 2025.09.30

유니온 파인드(Union-Find) 알고리즘?

 

서로소 집합(Disjoint Set) 자료구조라고도 불리며,
주로 그래프에서 사이클을 판별하거나, 자료들의 연결 정보를 알고 싶을 때 사용

 

핵심

연산 설명
Find(x) 원소 x가 속한 집합의 대표(root)를 찾는다.
Union(a, b) 원소 a가 속한 집합과 b가 속한 집합을 합친다.

 

 

작동원리

1. 초기화 : 각 노드가 자기 자신을 부모로 갖는다.
2. Find 함수 : 루트 노드(대표)를 찾는다
3. Union 함수 : 두 집합을 하나로 합친다

 

# 유니온 파인드 기본 구조 예제

# 1. 초기화: 각 노드가 자기 자신을 부모로 갖는다.
parent = [0, 1, 2, 3, 4, 5]  # 1~5번 노드 예시

# 2. Find 함수 — 루트 노드(대표)를 찾는다.
def find_parent(parent, x):
    if parent[x] != x:               # 부모가 자기 자신이 아니면
        parent[x] = find_parent(parent, parent[x])  # 재귀로 루트를 찾고 경로 압축
    return parent[x]

# 3. Union 함수 — 두 집합을 하나로 합친다.
def union_parent(parent, a, b):
    a = find_parent(parent, a)  # a의 루트 찾기
    b = find_parent(parent, b)  # b의 루트 찾기
    if a < b:
        parent[b] = a           # 작은 번호를 부모로
    else:
        parent[a] = b

# 4. 사용 예시
union_parent(parent, 1, 2)  # 1과 2 합치기
union_parent(parent, 2, 3)  # 2와 3 합치기

# 5. 결과 확인
print(find_parent(parent, 3))  # → 1 (3의 대표)
print(parent)  # 전체 부모 배열 출력 [0, 1, 1, 1, 4, 5]

'Etc > study' 카테고리의 다른 글

[5주차] 그리디 알고리즘  (0) 2025.10.22
[4주차] 다익스트라 알고리즘  (0) 2025.10.19
[4주차] 세그먼트 트리  (0) 2025.10.16
[3주차] 비선형 자료구조  (0) 2025.10.06
[2주차] 정렬 알고리즘  (0) 2025.09.30

트리가 어떤 것인지 다시 확인하기

https://ysh0129.tistory.com/106

 

[3주차] 비선형 자료구조

※ 이전에 선형 자료구조는 해당 링크https://ysh0129.tistory.com/104 [1주차] 자료구조자료구조?데이터를 효율적으로 저장하고, 관리하고, 처리하기 위한 구조 어떻게 담고, 꺼내고, 수정할지를 설계 필

ysh0129.tistory.com

 

목차

Ⅰ. 세그먼트 트리?

Ⅱ. 세그먼트 트리 핵심

  1. 트리 초기화 방법
  2. 질의값 구하는 방법
  3. 데이터 업데이트 방법

 

 

 

Ⅰ. 세그먼트 트리?

▶ 주어진 데이터의 구간 합과 데이터 업데이트를 빠르게 수행하기 위해 고안해낸 자료구조의 형태

(≒ 인덱스 트리)

 

세그먼트 트리로 구간합 인덱스 2~4 구간합 구하는 원리

 

Ⅱ. 세그먼트 트리 핵심

1. 트리 초기화 구조

 

● 크기 설정 : 원본 배열의 크기가 일 때, 세그먼트 트리를 저장할 배열(리스트)은 보통 의 크기로 준비

→  트리가 완전 이진 트리 형태를 유지하도록 충분한 공간을 확보

 

  초기화 함수 정의 : init , build

매개변수 설명
a 원본 데이터 배열
tree 세그먼트 트리를 저장할 배열
node 현재 트리의 노드 번호(index)
start, end 현재 노드가 담당하는 배열 a의 구간 인덱스

 

# 예시 배열
arr = [1, 2, 3, 4, 5]

# 세그먼트 트리 배열 (크기 4 * n)
n = len(arr)
tree = [0] * (4 * n)

# 세그먼트 트리 초기화 함수
def init(node, start, end):
    # 리프 노드인 경우
    if start == end:
        tree[node] = arr[start]
        return tree[node]
    
    mid = (start + end) // 2
    # 왼쪽, 오른쪽 자식 트리 초기화
    left_sum = init(node * 2, start, mid)
    right_sum = init(node * 2 + 1, mid + 1, end)
    
    # 현재 노드 값 = 왼쪽 + 오른쪽
    tree[node] = left_sum + right_sum
    return tree[node]

# 트리 초기화 실행
init(1, 0, n - 1)

# 결과 출력
print(tree)

 

구분 init build
기능 초기 데이터로 트리의 모든 노드 값을 채움
주로 사용되는 문맥 클래스의 초기화 과정을 나타낼 때
(내부 함수로 사용)
자료구조를 구축하는 행위 자체를 나타낼 때
(독립 함수로도 사용)
결론 세그먼트 트리를 처음 만들 때 배열 값을 바탕으로 트리를 채우는
동일한 재귀적 로직을 가진다. 이름만 다를 뿐, 기능상 큰 차이는 없다.

 

#init 함수

class SegmentTree:
    def __init__(self, arr):
        # ... (트리 크기 설정)
        self._init(0, n - 1, 1) # 트리를 초기화(구성)하는 내부 함수 호출

    def _init(self, start, end, node):
        # 재귀적으로 트리의 각 노드(구간 합, 최솟값 등)를 채우는 로직
        pass
        
 
#build 함수

	def build_segment_tree(arr):
    	# ... (트리 크기 설정)
    	tree = [0] * tree_size
    	_build(arr, tree, 0, n - 1, 1) # 트리를 구축하는 내부 함수 호출
    	return tree

	def _build(arr, tree, start, end, node):
    	# 재귀적으로 트리의 각 노드(구간 합, 최솟값 등)를 채우는 로직
    	pass

 

 

2. 질의값 구하는 방법

 

구간 질의는 주어진 범위 [L, R] 에 해당하는 값을 트리를 탐색하여 O(\log N) 시간 안에 계산

  질의값(조회) 함수 정의 : query 

 

매개변수 설명
L (Left Boundary) 사용자가 합을 구하고자 하는
구간의 시작 인덱스
R (Right Boundary) 사용자가 합을 구하고자 하는
구간의 끝 인덱스
start, end 현재 노드가 담당하는
각 시작과 끝 구간 인덱스
node 현재 탐색 중인 세그먼트 트리
배열의 인덱스
(보통 루트 노드에서 1부터 시작)
   

 

import math

class SegmentTree:
    def __init__(self, arr):
        self.n = len(arr)
        # 트리의 크기를 넉넉하게 4N으로 설정
        tree_size = 1 << (math.ceil(math.log2(self.n)) + 1)
        self.tree = [0] * tree_size
        self.arr = arr
        self._init(0, self.n - 1, 1) # 배열 인덱스: 0 ~ n-1, 트리의 루트 인덱스: 1

    # 트리 구축: arr[start:end+1] 구간의 합을 tree[node]에 저장
    def _init(self, start, end, node):
        if start == end: # 리프 노드
            self.tree[node] = self.arr[start]
            return self.tree[node]
        
        mid = (start + end) // 2
        # 왼쪽 자식 (2*node)과 오른쪽 자식 (2*node + 1)의 합을 현재 노드에 저장
        self.tree[node] = self._init(start, mid, node * 2) + self._init(mid + 1, end, node * 2 + 1)
        return self.tree[node]

    # 구간 합 질의: arr[L:R+1]의 합을 구함
    # L, R: 구하고자 하는 구간의 인덱스 (0-based)
    # start, end: 현재 노드가 담당하는 구간의 인덱스
    # node: 현재 노드의 인덱스
    def query(self, L, R, start, end, node):
        # 1. 완전 불일치: 쿼리 범위가 현재 노드 구간 밖에 있는 경우
        if R < start or L > end:
            return 0
        
        # 2. 완전 포함: 쿼리 범위가 현재 노드 구간을 완전히 포함하는 경우
        if L <= start and end <= R:
            return self.tree[node]
        
        # 3. 부분 포함: 왼쪽 자식과 오른쪽 자식으로 재귀 호출하여 합산
        mid = (start + end) // 2
        left_sum = self.query(L, R, start, mid, node * 2)
        right_sum = self.query(L, R, mid + 1, end, node * 2 + 1)
        
        return left_sum + right_sum

# --- 예시 실행 ---
A = [1, 2, 3, 4, 5, 6, 7, 8]
st = SegmentTree(A)

# 쿼리 예시: 인덱스 2부터 5까지 (A[2]~A[5])의 합 (3+4+5+6 = 18)
# L=2, R=5, 전체 구간은 start=0, end=7, 루트 노드는 node=1
result = st.query(2, 5, 0, st.n - 1, 1)
print(f"배열 {A}에서 인덱스 2부터 5까지의 구간 합: {result}")

# 쿼리 예시 2: 인덱스 0부터 7까지 (전체)의 합 (1+..+8 = 36)
result_all = st.query(0, st.n - 1, 0, st.n - 1, 1)
print(f"전체 구간 합: {result_all}")

 

3. 데이터를 갱신하는 방법

 

배열의 한 점(point)을 변경하고, 그 변경 사항이 영향을 미치는 모든 상위 노드의 값을 수정하는 과정

  갱신(업데이트) 함수 정의 : update 

매개변수 설명
idx 배열에서 값이 변경된 원본 인덱스
diff 변경된 값의 차이 (new_value - old_value)
start, end 현재 노드가 담당하는 구간
node 현재 트리 노드의 인덱스

 

def update(node, start, end, idx, diff):
    # 범위 밖
    if idx < start or idx > end:
        return
    # 트리 갱신
    tree[node] += diff
    if start != end:
        mid = (start + end) // 2
        update(node * 2, start, mid, idx, diff)
        update(node * 2 + 1, mid + 1, end, idx, diff)

# 예시: arr[2] (3 → 5 로 변경)
idx = 2
diff = 5 - arr[idx]
arr[idx] = 5
update(1, 0, n - 1, idx, diff)

print(query(1, 0, n - 1, 0, 4))  # 전체 합 17로 바뀜

 

'Etc > study' 카테고리의 다른 글

[4주차] 다익스트라 알고리즘  (0) 2025.10.19
[4주차] 유니온 파인드 알고리즘  (0) 2025.10.17
[3주차] 비선형 자료구조  (0) 2025.10.06
[2주차] 정렬 알고리즘  (0) 2025.09.30
[1주차] 자료구조  (0) 2025.09.29

※ 이전에 선형 자료구조는 해당 링크

https://ysh0129.tistory.com/104

 

[1주차] 자료구조

자료구조?데이터를 효율적으로 저장하고, 관리하고, 처리하기 위한 구조 어떻게 담고, 꺼내고, 수정할지를 설계 필요한 이유?▶ 효율성 : 같은 연산이라도 자료구조에 따라 속도가 다르다.▶ 편

ysh0129.tistory.com

 

비선형 자료구조?

▶ 한 줄로 선으로 이루어지지 않고, 데이터가 계층적/네트워크적, 여러 갈래로 복잡하게 연결된 구조 

 

1. 트리

    1-(1) 트리의 종류
    1-(2) 이진 트리 순회

  • 전위 순회
  • 중위 순회
  • 후위 순회

2. 그래프

    2-(1) 그래프의 개념과 표현 방식
    2-(2) 그래프 탐색 

  • 깊이 우선 탐색 (DFS)
  • 너비 우선 탐색 (BFS)
  • BFS 최단 거리

3. 트리와 그래프의 관계

 

 

 


 

1. 트리

 

▶ 노드들이 나무 가지처럼 연결된 계층적 자료구조

 

  • 노드(Node) : 자료 항목을 가지고 있는 트리를 구성하는 기본 요소(이미지에서 A~J를 의미)
  • 근 노드(Root node) : 트리에서 가장 맨 위에 위치하는 노드 (A에 해당)
  • 차수(Degree) : 트리에서 각 뻗어진 가지 수(A=2, F=1)
  • 잎사귀 노드(Leaf node) : 차수가 없는 노드 (E,G,H,I,J가 해당)
  • 트리의 깊이(Depth / Level) : 트리에서의 최대 깊이(H 노드는 3레벨, 루트인 A 노드는 0레벨)
  • 트리의 차수 : 전체의 트리에서 가장 많은 차수, 전체가 아닌 가장 가지수가 많은 부분(=2)
  • 자식 노드 : 노드에 연결된 서브트리의 루트노드들(D)
  • 부모 노드 : 노드에 연결된 한단계 상위 레벨 노드(D 기준으로 B)
  • 형제 노드(Sibling node) : 부모가 같은 노드(D,E 와 F,G)

 

1-(1) 트리의 종류

  • 정 이진트리(Full binary tree)
    각 내부 노드가 두 개의 자식 노드를 갖는 순서화된 트리
    ( 홀수 개의 자식 노드를 가질 수 없다. 2¹, 2², 2³, ... ,2ⁿ 구조 )
  • 완전 이진 트리(Complete binary tree)
    부모, 왼쪽 자식, 오른쪽 자식 순으로 채워지는 트리를 말한다.
    (마지막 레벨을 제외하고 모든 노드가 가득 차 있는트리 단, 마지막 레벨은 왼쪽부터 채워져야 한다.)
  • 포화 이진 트리(Perfect binary tree)
    정 이진 트리이면서 완전 이진 트리인 경우를 말한다.
    (마지막 레벨을 제외하고 Leaf node가 없다.)
  • 균형 이진 트리 (Balanced binary tree)
    모든 노드의 좌우 서브 트리 Level이 1이상 차이나지 않는 트리
    (예로 좌는 Level 1, 우는 Level 3인 경우 균형 이진 트리에 해당하지 않는다.)
  • 변질 이진 트리(Degenerate binary tree)
    각 부모 노드가 오직 한 개의 연관 자식 노드를 갖는 트리
    (
    성능 면에서 트리가 연결 리스트 데이터 구조처럼 동작)

 

1-(2) 이진 트리 순회

  • 전위 순회
     [루트 - 왼쪽 자식 - 오른쪽 자식] 순으로 순회 (루트 노드부터 시작 자식 왼쪽 노트로)
  • 중위 순회
    [왼쪽 자식 - 루트 - 오른쪽 자식] 순으로 순회 (최고 레벨 왼쪽 노드에서 시작 후 부모 노드로)
  • 후위 순회
    [왼쪽 자식 - 오른쪽 자식 - 루트] 순으로 순회 (최고 레벨 왼쪽 노드에서 시작 후 형제 노드로)

 

2. 그래프

 

2-(1) 그래프의 개념과 표현 방식

 

그래프?

▶ 정점(vertex = node) 와 연결하는 간선(edge)의 집합으로 이루어진 자료구조

 

그래프의 종류

 

 

그래프의 구현 방법

  인접 행렬 (Adjacency Matrix) 인접 리스트 (Adjacency List)
정의 정점(노드)의 개수가 개인 그래프를 크기의 2차원 배열로 나타내는 방법 정점 에 대해 배열의 번째 요소에 와 연결된 모든 정점들의 리스트를 저장합니다. (예: list[i] = {j, k, ...})
장점 간선 존재 여부확인이 빠름
구현이 단순함
메모리 효율성 좋음
인접 정점 탐색 속도 빠름
단점 메모리 낭비
인접 정점 탐색 속도 느림
간선 존재 여부 확인 속도가 느림
구현이 복잡함

 

정점(vertax)에 따라서 수가 적으면 인접행렬을 사용하고 대부분 인접리스트 사용

 

2-(2) 그래프의 탐색

DFS / BFS / BFS 최단거리

 

  깊이 우선 탐색 (DFS) 너비 우선 탐색 (BFS) BFS 최단 거리
방식 가능한 한 깊이(Depth) 내려간 후, 더 이상 갈 곳이 없으면 백트래킹 후 다른 경로를 탐색하는 방식 시작 노드에서 가까운 노드들을 먼저 탐색하고, 그다음으로 가까운 노드들을 탐색하는 방식, 
수평적으로 탐색
가중치 없는 그래프에서 시작 노드로부터의 최단 경로를 찾는 데 가장 효율적이고 정확한 알고리즘
자료구조 탐색할 노드를 저장하는 데
스택 또는 재귀 함수를 사용
탐색할 노드를 저장하는데
를 사용
 
주요 분야 경로 찾기, 사이클 감지, 위상 정렬 미로 찾기, 네트워크 탐색  

 

 


 

3. 트리와 그래프의 관계

트리는 그래프의 한종류이다. ( 트리⊆그래프 )

 

구분 그래프 트리
정의 정점과 간선의 집합 사이클이 없는 연결 그래프
사이클 있을 수도 있음 없음
연결성 연결/비연결 가능 항상 연결됨
간선 수 제한 없음 정점 수 - 1
방향성 방향/무방향 모두 가능 주로 방향 있음 (루트→자식)
예시 도로망, SNS 친구 관계 조직도, 폴더 구조, DOM 구조

 

 

마치며

개념 정리를 하면서 어떤 구조인지는 이해가 가지만

코테로 어떻게 적용하고 접근할지가 관건이다.

'Etc > study' 카테고리의 다른 글

[4주차] 유니온 파인드 알고리즘  (0) 2025.10.17
[4주차] 세그먼트 트리  (0) 2025.10.16
[2주차] 정렬 알고리즘  (0) 2025.09.30
[1주차] 자료구조  (0) 2025.09.29
[1주차] 시간 복잡도  (0) 2025.09.29

정렬 알고리즘

 원소들을 번호순이나 사전 순서와 같이 일정한 순서대로 열거하는 알고리즘

 

목차

 

1. 기본 정렬

1-(1) 버블 정렬 (Bubble Sort)

1-(2) 선택 정렬 (Selection Sort)

1-(3) 삽입 정렬 (Insertion Sort)

 

2. 심화 정렬

2-(1) 퀵 정렬 (Quick Sort)

2-(2) 병합 정렬 (Merge Sort)

2-(3) 기수 정렬 (Radix Sort)

 

3. 이진 탐색(Binary search)

 

 

 

 

 

 

1. 기본 정렬

  • 1-(1) 버블 정렬 (Bubble Sort)

인접한 두 원소를 비교해서 큰 값을 뒤로 보내는 방식.

특징: 가장 단순하지만 비효율적이다. [시간 복잡도 : O(n²)]

def bubble_sort(arr):
    n = len(arr)
    for i in range(n):
        for j in range(0, n - i - 1):  # 마지막은 이미 정렬됨
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
    return arr

print(bubble_sort([5, 3, 8, 4, 2]))

 

  • 1-(2) 선택 정렬 (Selection Sort)

매 단계마다 최솟값(또는 최댓값)을 선택해서 앞(또는 뒤)으로 보내는 방식

특징: 교환 횟수가 적다.  [시간 복잡도 : O(n²)]

def selection_sort(arr):
    n = len(arr)
    for i in range(n):
        min_idx = i
        for j in range(i + 1, n):
            if arr[j] < arr[min_idx]:
                min_idx = j
        arr[i], arr[min_idx] = arr[min_idx], arr[i]
    return arr

print(selection_sort([5, 3, 8, 4, 2]))

 

  • 1-(3) 삽입 정렬 (Insertion Sort)

앞에서부터 차례로, 적절한 위치에 삽입하면서 정렬.

특징: 거의 정렬된 배열에 매우 빠름.  [시간 복잡도 : 평균 O(n²), 최선 O(n) ]

def insertion_sort(arr):
    for i in range(1, len(arr)):
        key = arr[i]
        j = i - 1
        while j >= 0 and arr[j] > key:  # key보다 큰 원소들을 뒤로 민다
            arr[j + 1] = arr[j]
            j -= 1
        arr[j + 1] = key
    return arr

print(insertion_sort([5, 3, 8, 4, 2]))

 

2. 심화 정렬

  • 2-(1) 퀵 정렬 (Quick Sort)

*피벗(Pivot)을 기준으로 작은 값과 큰 값으로 분할하여 재귀적으로 정렬 ( *피벗 : 배열을 두 그룹으로 나누는 기준 값 )

특징: 평균적으로 가장 빠른 정렬 중 하나.  [시간 복잡도 : 평균 O(n log n), 최악 O(n²) ]

def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[len(arr) // 2]  # 가운데 원소를 피벗으로
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    return quick_sort(left) + middle + quick_sort(right)

print(quick_sort([5, 3, 8, 4, 2]))

 

  • 2-(2) 병합 정렬 (Merge Sort)

배열을 반으로 나누고 각각 정렬 후 합병(Merge)

특징: 안정 정렬(Stable Sort). 항상 O(n log n)  [시간 복잡도 : O(n log n)]

def merge_sort(arr):
    if len(arr) <= 1:
        return arr

    mid = len(arr) // 2
    left = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])

    return merge(left, right)

def merge(left, right):
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    result.extend(left[i:])
    result.extend(right[j:])
    return result

print(merge_sort([5, 3, 8, 4, 2]))

 

  • 2-(3) 기수 정렬 (Radix Sort)

자릿수를 기준으로 차례로 정렬 (보통 큐나 안정 정렬을 이용).

특징: 비교 기반이 아님. 특정 경우 매우 빠름.  [시간 복잡도 : O(d·n) ,d=자릿수 ]

def counting_sort(arr, exp):
    n = len(arr)
    output = [0] * n
    count = [0] * 10

    for num in arr:
        index = num // exp
        count[index % 10] += 1

    for i in range(1, 10):
        count[i] += count[i - 1]

    i = n - 1
    while i >= 0:
        index = arr[i] // exp
        output[count[index % 10] - 1] = arr[i]
        count[index % 10] -= 1
        i -= 1

    for i in range(n):
        arr[i] = output[i]

def radix_sort(arr):
    max_val = max(arr)
    exp = 1
    while max_val // exp > 0:
        counting_sort(arr, exp)
        exp *= 10
    return arr

print(radix_sort([170, 45, 75, 90, 802, 24, 2, 66]))

 

3. 이진 탐색(Binary search)

 

정렬된 배열에서 중앙값과 비교하며 절반씩 범위를 줄여 탐색.

조건: 반드시 정렬된 배열이어야 함. [시간복잡도 : O(log n)]

 

 

 

def binary_search(arr, target):
    left, right = 0, len(arr) - 1
    while left <= right:
        mid = (left + right) // 2
        if arr[mid] == target:
            return mid  # 찾은 경우 인덱스 반환
        elif arr[mid] < target:
            left = mid + 1
        else:
            right = mid - 1
    return -1  # 없는 경우

arr = [2, 3, 4, 5, 8]
print(binary_search(arr, 4))  # 2 (인덱스)
print(binary_search(arr, 7))  # -1 (없음)

 

마치며

다양한 정렬 들이 있다는 것을 정리하고

안정성과 시간복잡도가 적은 것은 병렬 정렬인 점을 알게 되었다. 

'Etc > study' 카테고리의 다른 글

[4주차] 유니온 파인드 알고리즘  (0) 2025.10.17
[4주차] 세그먼트 트리  (0) 2025.10.16
[3주차] 비선형 자료구조  (0) 2025.10.06
[1주차] 자료구조  (0) 2025.09.29
[1주차] 시간 복잡도  (0) 2025.09.29

자료구조?

데이터를 효율적으로 저장하고, 관리하고, 처리하기 위한 구조

어떻게 담고, 꺼내고, 수정할지를 설계

 

필요한 이유?

효율성 : 같은 연산이라도 자료구조에 따라 속도가 다르다.

편리성 : 상황에 맞는 자료구조를 쓰면 코드가 단순해진다.

 

분류

선형 자료구조 (Linear)
데이터가 일렬로 나열된 구조
비선형 자료구조 (Non-Linear)
데이터가 계층적/네트워크적으로 연결된 구조
배열(Array) : 빠른 인덱스 접근 트리(Tree) : 부모-자식 관계 (예: 이진 탐색 트리, 힙)
연결 리스트(Linked List) : 삽입/삭제 용이 그래프(Graph) : 노드와 간선으로 연결된 구조 (예: 지도, SNS 친구 관계)
스택(Stack) : LIFO (후입선출) 해시(Hash Table) : 키(key)로 데이터에 빠르게 접근
큐(Queue) : FIFO (선입선출)  
덱(Deque) : 양쪽에서 넣고 뺄 수 있음  

 

선형 자료구조 (Linear)

1. 배열(Array)

 

  • 인덱스를 이용해 O(1)로 접근 가능
  • 하지만 크기가 고정되어 있고, 중간 삽입/삭제는 O(n)

 

 

1 - (1) 구간합 (= 누적합, Prefix Sum)

  • 배열의 구간 합을 빠르게 계산하는 방법
  • 미리 누적합 배열을 만들어두면 O(1)로 구간 합 계산 가능

 

1 - (2) 투 포인터 (Two Pointers)

  • 두 개의 포인터(인덱스)를 움직이면서 문제 해결
  • 정렬된 배열에서 두 수의 합, 부분 배열 문제 등에 자주 사용

 

1 - (3) 슬라이딩 윈도우 (Sliding Window)

  • 일정한 크기의 창(윈도우)을 이동시키면서 연속된 부분 구간을 다루는 방식
  • 예: 연속된 K개 원소의 최대합

 

2. 연결 리스트 (Linked List)

 

 

  • 각 원소가 데이터 + 다음 노드 주소를 저장하는 구조
  • 장점: 삽입/삭제 O(1) (포인터 변경만 하면 됨)
  • 단점: 인덱스로 접근 불가 → 탐색 O(n)

 

3. 스택 (Stack)

 

  • LIFO (Last In First Out) 구조 → 나중에 들어온 게 먼저 나감
  • 주요 연산: push, pop, peek
  • 주로 괄호 검사, DFS, 실행 취소 기능(Undo)

 

4. 큐 (Queue)

 

  • FIFO (First In First Out) 구조 → 먼저 들어온 게 먼저 나감
  • 주요 연산: enqueue, dequeue
  • 주로 프로세스 스케줄링, BFS 탐색

 

5. 덱, 디큐 (Deque, Double-Ended Queue)

 

  • 양쪽 끝에서 삽입/삭제 가능한 큐
  • 스택+큐 기능을 모두 가짐
  • 주로 슬라이딩 윈도우 최댓값 문제, 양방향 탐색

 

마치며

정처산기를 준비하면서 스택과 큐는 많이 본 것도 있고,

배열에서, 구간합이나, 투포인터, 슬라이딩 윈도우는 새롭게 느껴졌다.

다음에는 비선형도 이해하지

 

 

 

 

'Etc > study' 카테고리의 다른 글

[4주차] 유니온 파인드 알고리즘  (0) 2025.10.17
[4주차] 세그먼트 트리  (0) 2025.10.16
[3주차] 비선형 자료구조  (0) 2025.10.06
[2주차] 정렬 알고리즘  (0) 2025.09.30
[1주차] 시간 복잡도  (0) 2025.09.29

시간 복잡도

문제를 해결하는데 걸리는 시간과 입력의 함수 관계, 알고리즘이 입력 크기(n)에 따라 실행되는 데 걸리는 시간을 수학적으로 표현한 것

 

필요한 이유?

같은 문제를 풀더라도 알고리즘마다 속도가 다르다.

▶ 입력 데이터가 커질수록 실행 속도 차이가 커진다.

∴ 처리속도와 효율성, 최적화를 위해서 필요하다.

 

표기법

▶ 시간 복잡도는 빅오(Big-O) 표기법으로 표현

 

가장 빠르게 증가하는 항만 남기고 상수 계수는 무시합니다.

ex) 2n² + 6n + 10 → O(n²) 으로 표현

 

복잡도 이름 예시

O(1) 상수 시간 배열에서 인덱스로 접근
O(log n) 로그 시간 이진 탐색
O(n) 선형 시간 배열 전체 순회
O(n log n) 로그 선형 시간 효율적인 정렬(퀵정렬, 병합정렬)
O(n²) 제곱 시간 이중 반복문 (버블 정렬, 삽입 정렬)
O(2^n) 지수 시간 모든 부분 집합 탐색 (DFS 백트래킹 일부)
O(n!) 팩토리얼 시간 순열 전부 탐색

 

배열 [1, 2, 3, ..., n]이 있다고 할 때:

  • arr[0] 접근 → O(1) => n은 arr[0] 단 한개를 의미하므로 = 1
  • arr 전체 순회 → O(n) => (1~n까지) n 전체를 의미
  • 이중 for문으로 모든 쌍 확인 → O(n²) => 1~n * 1~n = n²
  • 정렬 후 이진 탐색 → O(n log n) + O(log n) ≈ O(n log n)

 

시간 복잡도 그래프

 

위에 그래프만 봐도 O(n)까지는 서서히 올라가지만, 그 이상 부터는 급격하게 올라가는 것을 알 수 있다.

 

마치며

스터디를 위해서 시간복잡도에 대해서 개념을 알아봤다.

일단, 이러한 것이 있다는 걸 알고 스터디 진행하면서 다시 이해하자 

 

'Etc > study' 카테고리의 다른 글

[4주차] 유니온 파인드 알고리즘  (0) 2025.10.17
[4주차] 세그먼트 트리  (0) 2025.10.16
[3주차] 비선형 자료구조  (0) 2025.10.06
[2주차] 정렬 알고리즘  (0) 2025.09.30
[1주차] 자료구조  (0) 2025.09.29

+ Recent posts