자료구조
서론
프로그램 = 자료구조 + 알고리즘. 앞서 CPU가 명령어를 실행하는 방법과 운영체제가 자원을 관리하는 방법을 배웠습니다. 하지만 프로그램이 처리하는 핵심 대상은 데이터입니다 — 사용자 정보, 상품 목록, 소셜 관계... 이 데이터가 메모리에서 어떻게 조직되는지가 프로그램의 속도를 직접적으로 결정합니다. 어떤 프로그램은 수만 건의 데이터를 순식간에 처리하는데, 어떤 것은 수백 건만 처리해도 멈추는 이유가 궁금했던 적이 있나요? 답은 바로 자료구조의 선택에 있습니다.
이 글에서 무엇을 배우게 되나요?
이 장을 마치면 다음을 얻게 됩니다:
- 직관적 판단력: 요구사항을 보면 어떤 자료구조를 써야 할지 자동으로 떠오름
- 성능 분석 관점: 성능 병목이 자료구조 선택이 잘못된 것인지 알고리즘 효율이 낮은 것인지 판단
- 트레이드오프 사고: "공간을 희생하여 시간 얻기"와 "시간을 희생하여 공간 얻기"를 이해하고 완벽한 자료구조는 없음을 앎
- 코드 독해력: HashMap, Stack, Queue 같은 용어를 보아도 낯설지 않음
- 후속 학습 기초: 데이터베이스 인덱스, 캐시 시스템, 검색 엔진 등 기술의 기반
| 장 | 내용 | 핵심 개념 |
|---|---|---|
| 제1장 | 전경도 | 4대 자료구조, 분류 기준 |
| 제2장 | 선형 구조 | 배열, 연결 리스트, 스택, 큐 |
| 제3장 | 해시 테이블 | 해시 함수, 충돌 처리, O(1) 조회 |
| 제4장 | 트리 구조 | 이진 트리, 파일 시스템 트리, DOM 트리 |
| 제5장 | 그래프 구조 | 방향 그래프, 무방향 그래프, 순회 알고리즘 |
| 제6장 | 성능 비교 | 시간 복잡도, 공간 복잡도 |
| 제7장 | 선택 가이드 | 시나리오 분석, 의사결정 흐름 |
1. 전경도: 자료구조란 무엇인가?
책 무더기를 정리한다고 상상해 보세요:
- 바닥에 쌓기: 책을 찾으려면 한 권씩 뒤져야 함 — 가장 원시적인 저장 방식
- 번호별로 책장에 놓기: 해당 위치로 바로 가서 꺼냄 — 이것이 배열
- 카테고리별로 캐비닛 나누기: 먼저 캐비닛을 정하고 그 안에서 찾음 — 이것이 해시 테이블
- 책명순으로 다층 선반에 놓기: 매번 절반을 제외 — 이것이 트리
정리 방식에 따라 책을 찾는 효율이 천지 차이입니다. 자료구조는 데이터의 "정리 방식"입니다 — 데이터를 어떻게 저장하고, 어떻게 찾고, 어떻게 수정할지를 결정합니다.
모든 자료구조는 4대 범주로 나눌 수 있습니다:
| 유형 | 데이터 관계 | 대표 예시 | 생활 비유 |
|---|---|---|---|
| 선형 구조 | 일대일, 한 줄로 나열 | 배열, 연결 리스트, 스택, 큐 | 기차 객차, 줄 서기 |
| 해시 구조 | 키→값 매핑 | 해시 테이블, 사전, 집합 | 도서관 색인 카드 |
| 트리 구조 | 일대다, 계층 관계 | 이진 트리, B트리, 힙 | 가계도, 폴더 |
| 그래프 구조 | 다대다, 그물 관계 | 방향 그래프, 무방향 그래프 | 지하철 노선도, 소셜 네트워크 |
왜 이렇게 많은 종류를 배워야 하나요?
만능인 자료구조는 없기 때문입니다. 각 구조는 "검색 속도", "삽입 속도", "메모리 사용량" 사이의 트레이드오프입니다. 가방으로 가구를 옮기지 않고, 트럭으로 편지 한 통을 보내지 않는 것과 같습니다 — 올바른 도구를 선택하면事半功倍입니다.
2. 선형 구조: 가장 기본적인 조직 방식
선형 구조는 가장 직관적인 데이터 조직 방식입니다 — 데이터가 하나씩 차례대로 나열되며, 기차 객차와 같습니다. 하지만 "어떻게 연결하는가"와 "어떤 쪽에서 조작하는가"에 따라 네 가지 변형이 생기며, 각자 고유의 장기가 있습니다.
| Data structure | Access | Insert | Delete | Feature |
|---|---|---|---|---|
| 📊 Array | O(1) fast | O(n) slow | O(n) slow | Fixed size |
| 🔗 Linked list | O(n) slow | O(1) fast | O(1) fast | Variable size |
| 🥞 Stack | O(n) | O(1) fast | O(1) fast | One-end operations |
| 🚶 Queue | O(n) | O(1) fast | O(1) fast | Two-end operations |
2.1 배열 vs 연결 리스트: 두 가지 완전히 다른 저장 방식
배열과 연결 리스트는 가장 기본적인 두 가지 선형 구조이며, 핵심 차이는 메모리 배치에 있습니다:
| 비교 차원 | 배열 | 연결 리스트 |
|---|---|---|
| 메모리 배치 | 연속된 한 블록 | 곳곳에 흩어져 포인터로 연결 |
| n번째 접근 | 주소 바로 계산, O(1) | 처음부터 하나씩 찾기, O(n) |
| 중간 삽입 | 뒤의 것들을 모두 옮겨야, O(n) | 포인터 두 개만 변경, O(1) |
| 크기 | 생성 시 고정 | 언제든 늘릴 수 있음 |
| 생활 비유 | 번호가 있는 사물함 한 줄 | 보물 찾기 실마리 체인 |
언제 배열을 쓰고 언제 연결 리스트를 쓰나요?
- 데이터량이 정해져 있고 위치별 접근이 빈번 → 배열(예: 학생 성적표, 픽셀 매트릭스)
- 데이터량을 모르고 삽입/삭제가 빈번 → 연결 리스트(예: 재생 목록, 실행 취소 기록)
- 불확실? → 먼저 배열 사용. 대부분의 시나리오에서 배열의 캐시 친화성이 주는 성능 우위가 더 큽니다
2.2 스택과 큐: "규칙"이 추가된 선형 구조
스택과 큐는 본질적으로 배열이나 연결 리스트이지만, 조작 방식을 제한합니다. 기능이 줄어든 것 같지만, 바로 이 제한이 명확한 용도를 부여합니다:
| 구조 | 규칙 | 조작 | 비유 | 여러분의 코드에서 어디에? |
|---|---|---|---|---|
| 스택 | 후입선출 (LIFO) | push / pop | 접시 더미 | 함수 호출 스택, 브라우저 뒤로 가기, Ctrl+Z 되돌리기 |
| 큐 | 선입선출 (FIFO) | enqueue / dequeue | 표 사러 줄 서기 | 작업 스케줄링, 메시지 큐, 인쇄 대기열 |
왜 "제한"이 오히려 좋은가?
"접시 놓기"와 "접시 꺼내기" 두 가지 조작만 있는 스택을 상상해 보세요 — 순서를 절대 틀리지 않습니다. 제한이 확실성을 가져오고, 확실성이 신뢰성을 가져옵니다. 함수 호출 스택은 "후입선출"로 마지막에 호출된 함수가 가장 먼저 반환됨을 보장합니다. 중간의 함수에 임의로 접근할 수 있다면 프로그램은 엉망이 될 것입니다.
3. 해시 테이블: 가장 빠른 검색
선형 구조의 검색은 모두 충분히 빠르지 않습니다 — 배열은 순회해야 O(n), 정렬되어 있어도 이진 탐색으로 O(log n)입니다. O(1)로 바로 찾을 수 있는 구조는 없을까요? 있습니다, 바로 해시 테이블입니다.
3.1 해시 테이블의 핵심 사상
해시 테이블의 원리는 사실 매우 단순합니다:
- 키(예: "apple")를 제공합니다
- 해시 함수가 키를 숫자로 계산합니다 (예:
hash("apple") = 3) - 배열의 3번째 위치로 바로 가서 찾습니다 — 순회 불필요, 한 번에 끝
이것은 도서관의 색인 시스템과 같습니다 — 선반을 일일이 뒤지지 않고, 색인 카드를 확인하면 바로 책의 위치를 알 수 있습니다.
3.2 해시 충돌: 두 키가 부딪히면 어떻게 하나요?
두 개의 다른 키가 같은 인덱스를 계산할 수 있습니다 — 이것을 해시 충돌이라고 합니다. 두 권의 책 색인 번호가 같아 같은 위치를 가리키는 것과 같습니다.
| 해결 방법 | 원리 | 비유 |
|---|---|---|
| 체인법 | 같은 위치에 연결 리스트로 여러 값 저장 | 같은 캐비닛에 여러 권의 책 |
| 개방 주소법 | 충돌하면 빈 자리를 뒤로 찾아감 | 캐비닛이 꽉 차면 옆 캐비닛에 놓기 |
3.3 해시 테이블의 성능
| 조작 | 평균 | 최악(모두 충돌) |
|---|---|---|
| 검색 | O(1) | O(n) |
| 삽입 | O(1) | O(n) |
| 삭제 | O(1) | O(n) |
언제 성능이 저하되나요?
모든 키가 같은 인덱스로 매핑되면 해시 테이블이 연결 리스트로 퇴화하여 모든 조작이 O(n)이 됩니다. 방지법: 좋은 해시 함수 선택 + 동적 확장(부하율이 임계값을 초과하면 확장).
여러분의 코드에서 해시 테이블은 어디에나 있습니다
- JavaScript의
{}객체와Map→ 해시 테이블 - Python의
dict→ 해시 테이블 - Java의
HashMap→ 해시 테이블 - 데이터베이스의 인덱스 → 하위 계층도 해시 사용
user["name"] 또는 map.get("key")를 쓸 때마다 배경에서 해시 테이블이 일하고 있습니다.
4. 트리 구조: 계층 관계의 표현
해시 테이블은 검색이 빠르지만 데이터가 정렬되지 않습니다. 빠른 검색과 데이터 정렬 유지를 동시에 해야 한다면 트리 구조가 필요합니다.
트리의 핵심 특징: 각 노드는 여러 "자식"을 가질 수 있지만, "부모"는 하나뿐입니다(루트 노드 제외). 이러한 일대다 계층 관계는 현실 어디에나 있습니다.
4.1 이진 검색 트리: 정렬된 트리
이진 검색 트리에는 단순하지만 강력한 규칙이 있습니다: 왼쪽은 작고 오른쪽은 크다.
- 왼쪽 서브트리의 모든 값 < 루트 노드
- 오른쪽 서브트리의 모든 값 > 루트 노드
검색 시 매번 비교할 때마다 절반의 노드를 제외할 수 있어 시간 복잡도는 O(log n)입니다. 숫자 맞추기 게임과 같습니다 — "50보다 큰가 작은가?" "크네요." "75보다 큰가 작은가?" — 매번 절반을 제외합니다.
4.2 균형 트리: 퇴화 방지
이진 검색 트리에 문제가 있습니다: 데이터가 순서대로 삽입되면(1, 2, 3, 4, 5) 트리가 한 줄로 퇴화하여 검색이 다시 O(n)이 됩니다. 균형 트리는 자동으로 구조를 조정하여 이 문제를 피합니다:
| 유형 | 균형 전략 | 특징 | 대표적 응용 |
|---|---|---|---|
| AVL 트리 | 엄격한 균형(높이 차이 ≤ 1) | 검색 가장 빠름, 삽입/삭제 약간 느림 | 빈번한 검색이 필요한 시나리오 |
| 레드-블랙 트리 | 근사 균형 | 종합 성능 좋음 | Java TreeMap, Linux 커널 |
| B 트리 | 다중 균형, 한 노드에 여러 값 저장 | 디스크 I/O 감소 | 데이터베이스 인덱스 |
여러분의 코드에서 트리는 어디에 있나요?
- 파일 시스템: 폴더 중첩이 바로 트리 구조
- HTML DOM:
<html>→<body>→<div>→<p>가 바로 트리 - 데이터베이스 인덱스: B+ 트리로 백만 건 데이터 검색에 디스크 읽기 3-4회만 필요
- JSON/XML: 중첩된 데이터 형식의 본질이 바로 트리
5. 그래프 구조: 복잡한 관계의 네트워크
트리는 "일대다" 계층 관계만 표현할 수 있습니다. 하지만 현실의 많은 관계는 "다대다"입니다 — 친구에게도 친구가 있고, 도시 사이에는 여러 갈래 길이 있습니다. 임의의 노드 사이에 연결이 있을 수 있는 구조가 바로 그래프입니다.
5.1 그래프의 세 가지 형태
| 유형 | 특징 | 비유 | 대표적 응용 |
|---|---|---|---|
| 무방향 그래프 | 간선에 방향 없음, A→B는 B→A와 동일 | 위챗 친구(쌍방향) | 소셜 네트워크, 통신 네트워크 |
| 방향 그래프 | 간선에 방향 있음, A→B ≠ B→A | 웨이보 팔로우(단방향) | 웹페이지 링크, 의존 관계 |
| 가중 그래프 | 간선에 가중치(거리, 비용 등) | 도시 간 도로(거리수 있음) | 지도 내비게이션, 최단 경로 |
5.2 그래프의 순회
그래프의 순회는 선형 구조보다 복잡합니다. 사이클이 있을 수 있기 때문에(A→B→C→A) "방문한" 노드를 기록해야 합니다:
| 순회 방식 | 전략 | 비유 | 적용 시나리오 |
|---|---|---|---|
| BFS(너비 우선) | 모든 이웃을 먼저 방문, 그 다음 이웃의 이웃 | 물결 퍼짐 | 최단 경로, 레벨 순회 |
| DFS(깊이 우선) | 한 길을 끝까지, 막히면 돌아감 | 미로 찾기 | 경로 탐색, 연결성 판단 |
현실에서 그래프의 응용
- 지도 내비게이션: 도시가 노드, 도로가 간선, 내비게이션은 그래프에서 최단 경로를 찾는 것
- 소셜 네트워크: 사용자가 노드, 팔로우/친구가 간선, "알 수도 있는 사람"이 그래프 알고리즘으로 추천
- 패키지 관리자: npm/pip의 의존 관계가 방향 그래프,
npm install이 그래프의 위상 정렬
6. 성능 비교: 한 장의 표로 모든 자료구조 파악
여러 자료구조를 배웠으니, 성능 차이가 얼마나 나는지 봅시다. 아래의 대화형 비교로 직관을 구축해 보세요:
| Operation | Array | Linked list | Hash table | Tree |
|---|---|---|---|---|
| Access | O(1) | O(n) | O(1) | O(log n) |
| Search | O(n) | O(n) | O(1) | O(log n) |
| Insert | O(n) | O(1) | O(1) | O(log n) |
| Delete | O(n) | O(1) | O(1) | O(log n) |
핵심 성능 비교표:
| 자료구조 | 접근 | 검색 | 삽입 | 삭제 | 공간 |
|---|---|---|---|---|---|
| 배열 | O(1) | O(n) | O(n) | O(n) | O(n) |
| 연결 리스트 | O(n) | O(n) | O(1) | O(1) | O(n) |
| 스택/큐 | O(n) | O(n) | O(1) | O(1) | O(n) |
| 해시 테이블 | — | O(1) | O(1) | O(1) | O(n) |
| 이진 검색 트리 | — | O(log n) | O(log n) | O(log n) | O(n) |
| 그래프 | — | O(V+E) | O(1) | O(E) | O(V+E) |
이 표를 어떻게 읽나요?
- O(1): 데이터량이 아무리 커도 조작 시간이 일정 — 가장 빠름
- O(log n): 데이터량이 두 배가 되어도 시간은 한 단계만 증가 — 매우 빠름
- O(n): 데이터량이 두 배가 되면 시간도 두 배 — 보통
- O(V+E): 노드 수와 간선 수에 의존 — 그래프의 특수 표현
주의: 이는 모두 평균 경우입니다. 최악의 경우 해시 테이블은 O(n)으로 퇴화하고, 이진 검색 트리도 O(n)으로 퇴화할 수 있습니다.
7. 선택 가이드: 어떤 자료구조를 써야 할까?
여러 자료구조를 배웠으니, 실제 요구에 직면했을 때 어떻게 선택할까요? 핵심은 요구사항에서 출발하여 스스로에게 몇 가지 질문을 던지는 것입니다:
- 가장 빈번한 조작은 무엇인가? 검색? 삽입? 삭제? 순회?
- 데이터 사이의 관계는 무엇인가? 일대일? 일대다? 다대다?
- 데이터량은 얼마나 되는가? 수십 건과 수백만 건의 최적 선택이 완전히 다를 수 있음
- 정렬이 필요한가? 어떤 순서로 데이터를 순회해야 하는지
| Scenario need | Recommended structure | Time complexity |
|---|---|---|
| Random access | Array | O(1) |
| Fast lookup | Hash table | O(1) |
| Ordered lookup | Binary search tree | O(log n) |
| Frequent insertion/deletion | Linked list | O(1) |
| Undo operations | Stack | O(1) |
| Task scheduling | Queue | O(1) |
빠른 의사결정 흐름:
| 여러분의 요구 | 추천 구조 | 이유 |
|---|---|---|
| 위치별 빠른 접근 | 배열 | O(1) 임의 접근 |
| 중간 삽입/삭제 빈번 | 연결 리스트 | O(1) 삽입/삭제, 요소 이동 불필요 |
| 후입선출(되돌리기, 재귀) | 스택 | LIFO 의미가 자연스럽게 부합 |
| 선입선출(작업 큐) | 큐 | FIFO 의미가 자연스럽게 부합 |
| 키로 빠른 검색 | 해시 테이블 | O(1) 평균 검색 |
| 정렬된 데이터 + 빠른 검색 | 이진 검색 트리 | O(log n) 검색이며 정렬 유지 |
| 복잡한 다대다 관계 | 그래프 | 임의의 노드 간 연결 표현 가능 |
실제 개발에서의 경험 법칙
- 80%의 시나리오에서는 배열과 해시 테이블만으로 충분
- 정렬이 필요할 때 트리 고려
- 관계가 복잡할 때 그래프 고려
- 불확실? 가장 단순한 것부터, 성능 문제가 발생하면 교체.过早최적화는 만악의 근원
요약
자료구조는 프로그램의 골격입니다. 배열은 번호가 있는 사물함 한 줄처럼 위치로 꺼내는 것이 가장 빠르고; 연결 리스트는 보물 찾기 실마리 체인처럼 삽입/삭제가 가장 유연하고; 해시 테이블은 도서관 색인처럼 이름으로 찾는 것이 가장 빠르고; 트리는 가계도처럼 계층 관계를 표현하면서 정렬을 유지하고; 그래프는 지하철 노선도처럼 임의로 복잡한 그물 관계를 표현합니다. 최고의 자료구조는 없고, 가장 적합한 것만 있습니다 — 핵심은 각 구조의 장점과 대가를 이해하고, 실제 요구에 따라 트레이드오프하는 것입니다.
추가 읽기
| 주제 | 추천 자료 |
|---|---|
| 자료구조 시각화 | VisuAlgo - 다양한 자료구조와 알고리즘의 애니메이션 데모 |
| 알고리즘과 자료구조 | 《그림으로 배우는 알고리즘》- Aditya Bhargava, 입문자에게 적합 |
| 심화 이해 | 《데이터 구조와 알고리즘 분석》- Mark Allen Weiss |
| 문제 풀이 | LeetCode - 자료구조별 분류 연습 |
다음 단계
이제 자료구조의 핵심 지식을 마스터했습니다. 다음으로 계속 학습하세요: