본문으로 건너뛰기
김신건의 로그

자료구조 (Data Structures)

· 수정 · 📖 약 5분 · 1,930자/단어 #algorithm #data-structure
avl-tree, AVL Tree, data structures, 자료구조, DS

정의

자료구조 (Data Structure) 는 데이터를 효율적으로 저장하고 접근하기 위한 조직화 방법. 문제 풀이에서는 시간 복잡도와 공간 복잡도의 trade-off 를 정확히 파악해 적절한 자료구조를 선택하는 것이 핵심.

흔히 배열, 연결 리스트, 스택, 큐, 트리, 해시 테이블, 힙, 세그먼트 트리, 펜윅 트리 등을 포괄.

문제 상황과 동기

동일한 연산 (삽입, 삭제, 검색, 갱신) 도 자료구조에 따라 복잡도가 천차만별:

  • 배열: 인덱스 접근 O(1), 검색 O(N), 삽입/삭제 O(N).
  • 연결 리스트: 삽입/삭제 O(1) (노드 포인터를 이미 안다면), 검색 O(N).
  • 해시 테이블: 평균 O(1) 삽입/검색, 최악 O(N), 순서 없음.
  • 이진 탐색 트리 (BST): 평균 O(log N), 최악 O(N), 균형 트리 (AVL, Red-Black) 는 최악 O(log N).
  • 세그먼트 트리: 구간 쿼리 + 점 갱신 O(log N), 메모리 O(N).
  • 펜윅 트리 (BIT): 구간 쿼리 + 점 갱신 O(log N), 메모리 O(N), 구현 간단, 역원 필요.

핵심 통찰: 연산 빈도와 범위 에 따라 최적 자료구조가 결정된다. 예: 단순 스택은 LIFO, 큐는 FIFO, 우선순위 큐는 힙으로.

시각화

기본 자료구조 비교

자료구조접근검색삽입삭제공간비고
배열O(1)O(N)O(N)O(N)O(N)정적 크기, 캐시 친화
동적 배열O(1)O(N)O(1) amortizedO(N)O(N)vector, ArrayList
연결 리스트O(N)O(N)O(1)O(1)O(N)포인터 오버헤드
스택O(1) top-O(1) pushO(1) popO(N)LIFO
O(1) front-O(1) enqueueO(1) dequeueO(N)FIFO
해시 테이블-O(1) avgO(1) avgO(1) avgO(N)순서 없음, 최악 O(N)
BSTO(log N) avgO(log N) avgO(log N) avgO(log N) avgO(N)최악 O(N), 균형 필요
AVL / Red-BlackO(log N)O(log N)O(log N)O(log N)O(N)균형 보장
힙 (우선순위 큐)O(1) min-O(log N)O(log N)O(N)최댓값/최솟값만 O(1)
세그먼트 트리--O(log N)-O(N)구간 쿼리 + 점 갱신
펜윅 트리 (BIT)--O(log N)-O(N)구간 합, 역원 필요

핵심 아이디어

1. 배열 vs 연결 리스트

  • 배열: 메모리 연속 배치, 캐시 히트율 높음, 인덱스 접근 O(1), 중간 삽입/삭제 O(N).
  • 연결 리스트: 노드 포인터로 연결, 삽입/삭제 O(1) (포인터를 알면), 랜덤 접근 O(N).

PS 에서는 배열이 압도적으로 유리 (캐시 + 인덱스 접근). 연결 리스트는 LRU 캐시, 큰 원소 중간 삽입 등 특수 상황만.

2. 해시 vs 균형 트리

  • 해시 (unordered_map, dict, HashMap): 평균 O(1), 순서 없음, 최악 O(N) (해시 충돌).
  • 균형 트리 (map, TreeMap, set): 최악 O(log N), 순서 보장, 범위 쿼리 가능.

문제 조건:

  • “존재 여부만” → 해시.
  • “k번째 작은 값”, “l 이상 r 이하 개수” → 균형 트리 or 세그먼트 트리.

3. 세그먼트 트리 vs 펜윅 트리

둘 다 구간 쿼리 + 점 갱신 O(log N), 메모리 O(N).

항목세그먼트 트리펜윅 트리
구현 난이도보통 (재귀 or iterative)쉬움 (비트 연산)
연산임의 결합 (min, max, gcd)+, XOR (역원 필요)
메모리 상수4NN
lazy propagation가능불가능 (구간 갱신 복잡)
PS 선호범용합/XOR 빠름

PS 에서는 합 쿼리는 펜윅, min/max/gcd 는 세그먼트 트리.

4. 균형 트리 종류

트리균형 조건회전 빈도구현 난이도비고
AVL|h(L) - h(R)| ≤ 1높음어려움검색 빠름
Red-Black최장 경로 ≤ 2 × 최단낮음매우 어려움삽입/삭제 빠름, C++ map/set
B-Tree다진 트리, 키 범위-복잡DB 인덱스
Splayamortized O(log N)높음보통캐시 친화
Treap랜덤 우선순위기댓값 O(log N)보통split/merge 간단

PS 에서는 C++ std::set, std::map (Red-Black) 이 대부분. 직접 구현은 거의 안 함.

알고리즘

자료구조는 알고리즘이 아니므로 pseudocode 대신 연산별 복잡도와 선택 기준 을 정리.

선택 기준:
1. 연산 종류: 삽입/삭제 빈도, 검색 빈도, 구간 쿼리 필요?
2. 순서: 정렬 상태 유지 필요?
3. 메모리: N 크기, 메모리 제약?
4. 구현: 시간 제약, 라이브러리 사용 가능?

예시:
- "Q개 (key, value) 삽입 후 key로 value 검색" → 해시 테이블 O(N+Q).
- "Q개 삽입 + k번째 작은 값" → 균형 트리 O(Q log Q) or 세그트리.
- "배열, 구간 합 Q번" → [[Prefix Sum|누적 합]] O(N+Q) or 펜윅.
- "배열, 구간 합 + 점 갱신" → 펜윅 or 세그트리 O((N+Q) log N).
- "배열, 구간 min/max" → 세그트리 or [[Sparse Table|희소 배열]].

구현

자료구조 선택 예시: 배열 vs 해시 vs 균형 트리.

// 배열 vs 해시 vs 균형 트리
#include <bits/stdc++.h>
using namespace std;
int main() {
  int n; cin >> n;
  // 1. 배열: 인덱스 접근 O(1), 검색 O(N)
  vector<int> arr(n);
  for (auto& v : arr) cin >> v;
  cout << "arr[0] = " << arr[0] << "\n";

  // 2. 해시: 삽입/검색 평균 O(1)
  unordered_set<int> hs(arr.begin(), arr.end());
  cout << "hs.count(arr[0]) = " << hs.count(arr[0]) << "\n";

  // 3. 균형 트리: 삽입/검색 O(log N), 순서 보장
  set<int> st(arr.begin(), arr.end());
  cout << "st.size() = " << st.size() << "\n";
  cout << "*st.begin() = " << *st.begin() << "\n";
}
stdin
5
3 1 4 1 5
결과
arr[0] = 3
hs.count(arr[0]) = 1
st.size() = 4
*st.begin() = 1

복잡도

자료구조별 복잡도는 위 비교 표 참고. 일반적으로:

항목
배열 접근O(1)
해시 평균O(1) 삽입/검색, O(N) 최악
균형 트리O(log N) 삽입/삭제/검색
세그트리/펜윅O(log N) 쿼리/갱신
O(log N) 삽입/삭제, O(1) min/max

변형

1. 특수 자료구조

  • Disjoint Set (Union-Find): 집합 합치기 + 같은 집합 판정, amortized O(α(N)) ≈ O(1).
  • Trie: 문자열 집합, 검색 O(L) (L = 문자열 길이).
  • Segment Tree with Lazy Propagation: 구간 갱신 O(log N).
  • Persistent Segment Tree: 버전별 쿼리, PST 참고.
  • Fenwick Tree 2D: 2차원 구간 합, 메모리 O(NM).
  • Sparse Table: 불변 배열, 구간 min/max O(1) 쿼리, 희소 배열.

2. 고급 활용

  • 좌표 압축 + 세그트리: 값 범위 10^9, 개수 N=10^5 → 좌표 압축 후 세그트리.
  • 오프라인 쿼리 + 정렬: 쿼리 순서 바꿔 효율 증대.
  • SQRT Decomposition: 세그트리 대신 블록 √N 개로 나눔, 구현 간단.

함정

1. 해시 테이블 최악 케이스

unordered_map 은 특정 입력 (해킹 테스트 케이스) 에서 O(N) 로 퇴화. Codeforces 등에서 TLE 발생 가능. 대책:

  • reserve() + custom hash.
  • map (Red-Black) 으로 fallback.

2. 세그트리 메모리 오버

배열 크기 N 에 대해 세그트리는 4N 메모리 필요. N=10^6 면 int 기준 16MB. vector<int>(4*N) 선언 필수.

3. 펜윅 트리 역원

펜윅은 역원이 필요한 연산 (덧셈, XOR) 만 가능. min/max 는 불가능 → 세그트리.

4. 균형 트리 중복

std::set 은 중복 불허, std::multiset 은 중복 허용. 문제 조건 확인 필수.

5. 배열 vs 동적 배열

C++ int arr[N] 은 컴파일 타임 상수 N 필요. 런타임 N 은 vector<int>(N) 또는 동적 할당.

BOJ 연습 문제

자료구조는 범위가 넓어 대표 문제만 나열. 각 자료구조별 문제는 해당 wiki 참고.

번호제목정답률링크
BOJ 10828스택-kokoa-lab
BOJ 10845-kokoa-lab
BOJ 1927최소 힙-kokoa-lab
BOJ 7662이중 우선순위 큐-kokoa-lab
BOJ 2042구간 합 구하기 (세그트리/펜윅)-kokoa-lab
BOJ 1717집합의 표현 (Union-Find)-kokoa-lab
BOJ 14425문자열 집합 (Trie/해시)-kokoa-lab

참고

이 글의 용어 (7개)
누적 합 (Prefix Sum)algorithm
정의 누적 합 (Prefix Sum) 은 배열 에 대해 (또는 1-indexed ) 을 미리 계산해 두고, 임의 구간 합 을 O(1) 에 구하는 정형. 문제 풀이에서 "구간 N …
분리 집합 (Disjoint Set, Union-Find)algorithm
정의 분리 집합 (Disjoint Set, Union-Find) 은 서로 겹치지 않는 집합들을 관리하며, 다음 두 연산을 거의 상수 시간 (amortized O(α(N)), α는…
세그먼트 트리 (Segment Tree)algorithm
정의 세그먼트 트리 (Segment Tree) 는 배열의 구간 쿼리 (range query) 와 점 갱신 (point update) 를 모두 O(log N) 에 처리하는 이진 트…
트라이 (Trie, 접두사 트리)algorithm
정의 트라이 (Trie) 는 Edward Fredkin 이 1960년 제안한 문자열 집합을 저장하는 트리 자료구조. 각 노드는 문자 하나, 간선은 다음 문자로의 전이. 루트에서 …
희소 배열 (Sparse Table)algorithm
정의 희소 배열 (Sparse Table) 은 정적 배열에서 결합 법칙을 만족하는 idempotent 연산 (min, max, gcd, lcm 등) 의 구간 쿼리를 O(1) 시간…
Fenwick Tree (Binary Indexed Tree): 구간 합 O(log N)algorithm
정의 Fenwick Tree (또는 BIT, Binary Indexed Tree) 는 배열의 prefix sum 을 O(log N) 에 갱신·조회 하는 자료구조입니다. Peter…
Persistent Segment Tree (영속 세그): 시점별 스냅샷algorithm
정의 Persistent Segment Tree 는 매 갱신마다 이전 버전을 그대로 유지하면서 새 버전을 만드는 세그먼트 트리입니다. 각 버전은 루트 포인터 로 식별됩니다. 핵심…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기