자료구조: 앱의 속도를 결정하는 데이터 그릇
배열, 리스트, 해시맵, 스택, 큐, 트리, 그래프를 앱의 속도와 유지보수성을 결정하는 데이터 그릇으로 이해하는 실전 가이드입니다.
프로그램은 결국 데이터를 다룹니다. 이름, 가격, 예약, 메시지, 사용자 ID, 문서, 파일, 권한, 관계.
자료구조는 이 데이터를 담는 그릇입니다.
그릇이 중요한 이유는 단순합니다. 어떤 일은 쉬워지고, 어떤 일은 느려지고, 어떤 일은 나중에 발목을 잡습니다.
주방을 떠올려 보세요. 모든 양념을 박스 하나에 던져 넣을 수도 있고, 라벨 붙은 작은 통에 나눠 담을 수도 있습니다. 둘 다 "보관"이지만 "소금 어디 있어?"라는 질문 앞에서는 두 번째 주방이 압도적으로 빠릅니다.
그게 자료구조입니다.
실전 질문
"어떤 자료구조를 외워야 하지?"로 시작하지 마세요.
대신 이렇게 물어보세요.
- 특정 항목 하나를 빨리 찾아야 하나?
- 순서가 중요하나?
- 중간에 자주 넣거나 빼야 하나?
- 계층 구조를 표현해야 하나?
- 복잡한 연결 관계를 표현해야 하나?
답이 그릇을 가리킵니다.
배열과 리스트: 줄 세우기
실무에서 "배열"과 "리스트"는 자주 섞여 쓰입니다. JavaScript의 Array, Python의 list, Java의 ArrayList는 이름은 List지만 내부적으로는 **동적 배열(dynamic array)**입니다. 반면 CS 교과서에서 말하는 "리스트"는 흔히 **연결 리스트(Linked List)**를 의미합니다.
CS에서의 차이
**배열(Array)**은 번호가 붙은 연속된 사물함입니다.
- 23번 사물함이 필요하면 바로 열 수 있습니다. 인덱스 접근이 O(1)입니다.
- 중간에 뭔가를 끼워 넣으면 뒤의 것들을 한 칸씩 밀어야 합니다.
- 크기가 고정인 경우가 많습니다.
**연결 리스트(List)**는 손을 잡고 줄 선 사람들입니다.
- 중간에 사람을 끼우려면 양옆 사람의 손만 바꾸면 됩니다. 삽입/삭제는 O(1)이지만, 위치를 찾는 데는 O(n)이 걸립니다.
- 23번째 사람을 찾으려면 앞에서부터 한 명씩 세어야 합니다.
- 크기는 자유롭게 늘어납니다.
| 관점 | 배열 | 연결 리스트 |
|---|---|---|
| 메모리 구조 | 연속된 공간 | 흩어진 노드 + 포인터 |
| 인덱스 접근 | O(1), 바로 열기 | O(n), 앞에서부터 세기 |
| 중간 삽입/삭제 | O(n), 뒤를 밀어야 함 | O(1), 양옆 연결만 바꿈 |
| 크기 | 보통 고정 | 자유롭게 늘어남 |
| 메모리 사용 | 적고 예측 가능 | 포인터 저장으로 추가 메모리 |
실무 언어에서는?
| 언어 | 이름 | 실제로는 |
|---|---|---|
| JavaScript | Array | 동적 배열 |
| Python | list | 동적 배열 |
| Java | ArrayList | 동적 배열 |
| Java | LinkedList | 연결 리스트 |
| C++ | std::vector | 동적 배열 |
| C++ | std::list | 연결 리스트 |
언제 무엇을 쓰나요?
배열/동적 배열을 쓰는 경우
- 인덱스로 빠르게 접근해야 할 때.
- 데이터를 끝에 자주 추가/삭제할 때.
- 메모리를 연속적으로 쓰고 싶을 때.
- 예: 검색 결과, 상품 목록, 테이블 행, 정렬된 데이터.
연결 리스트를 쓰는 경우
- 중간 삽입/삭제가 매우 잦을 때.
- 인덱스 접근보다 순회가 많을 때.
- 크기를 미리 알 수 없고 자주 변할 때.
- 예: 큐/스택 구현, 실행 취소 기록, 메모리 블록 연결.
앱에서는 이런 형태로 자주 만납니다.
- 상품 목록.
- 검색 결과.
- 채팅 메시지.
- 테이블 행.
- 워크플로 단계.
해시맵: 이름표 달린 사물함
**해시맵(Hash Map)**은 실무에서 가장 자주 사랑받는 자료구조 중 하나입니다.
번호 대신 이름표가 붙은 사물함을 떠올리면 됩니다.
user_123cart_totalappointment_2026_06_25feature_flag_checkout
이름표를 알고 있으면 거의 바로 값을 찾을 수 있습니다.
해시맵은 거의 모든 곳에 나옵니다.
- 사용자 ID로 정보 찾기.
- API(앱끼리 요청과 응답을 주고받는 약속) 응답 캐싱.
- 단어가 몇 번 나왔는지 세기.
- 설정값을 이름으로 저장.
- 상태별로 레코드 묶기.
스택과 큐: 쌓을까, 줄 세울까
**스택(Stack)**은 책상 위에 쌓은 책입니다. 마지막에 올린 책을 가장 먼저 뺍니다.
이 방식을 LIFO, 즉 마지막에 들어온 것이 먼저 나가는 방식이라고 합니다.
스택은 이런 곳에 나옵니다.
- 브라우저 뒤로 가기.
- 실행 취소(Undo).
- 함수 호출.
- 중첩 구조 해석.
**큐(Queue)**는 카페 줄입니다. 먼저 온 사람이 먼저 주문합니다.
이 방식을 FIFO, 즉 먼저 들어온 것이 먼저 나가는 방식이라고 합니다.
큐는 이런 곳에 나옵니다.
- 이메일 발송.
- 백그라운드 작업.
- 프린트 대기열.
- 메시지 처리.
- 처리 대기 중인 AI 작업.
트리와 그래프: 가지와 네트워크
**트리(Tree)**는 계층입니다.
폴더 안의 폴더. 댓글의 답글. 회사 조직도. 서브메뉴가 있는 메뉴.
트리는 각 항목에 부모와 자식이 있을 때 유용합니다.
**그래프(Graph)**는 네트워크입니다.
SNS 친구 관계. 지하철역. 서로 링크된 웹페이지. 서로 관련된 상품.
그래프는 관계가 단순히 위에서 아래로 흐르지 않을 때 유용합니다. 하나가 여러 방향으로 여러 것과 연결될 수 있습니다.
한눈에 비교
| 자료구조 | 일상 비유 | 잘하는 일 | 약한 일 | 앱에서 쓰이는 곳 |
|---|---|---|---|---|
| 배열 | 번호 매겨진 사물함 | 위치로 빠르게 접근 | 중간 삽입/삭제 | 정렬된 목록, 테이블 행 |
| 리스트 | 줄 선 사람들 | 유연한 증가/변경 | 위치 직접 접근 | 자주 바뀌는 순서 |
| 해시맵 | 이름표 달린 사물함 | 키로 빠른 조회 | 순서 보장 | 사용자 조회, 캐시, 설정 |
| 스택 | 쌓아둔 책 | 마지막 것 먼저 처리 | 중간 접근 | Undo, 뒤로 가기 |
| 큐 | 카페 줄 | 먼저 온 것 먼저 처리 | 중간 접근 | 작업, 메시지, 대기열 |
| 트리 | 폴더 구조 | 계층 표현 | 교차 연결 | 메뉴, 댓글, 파일 |
| 그래프 | 지하철 노선도 | 복잡한 연결 | 탐색 비용 | SNS, 길찾기, 추천 |
AI가 만든 앱에서 자주 보이는 문제
AI는 프롬프트 안의 작은 예시 데이터에는 맞는 코드를 잘 씁니다. 문제는 예시 데이터가 보통 아주 작다는 것입니다.
주의할 패턴:
-
배열을 모든 곳에 사용 빠른 검색, 중복 제거, 포함 여부 확인이 필요한데도 전부 배열로 처리합니다.
-
array.find()반복 ID로 자주 찾는 데이터를 매번 목록에서 선형 검색합니다.Map(키로 바로 찾는 구조)이나 객체 딕셔너리가 더 적합한데 쓰지 않습니다. -
중복 확인을 목록으로 처리 이미 존재하는 값인지 확인할 때
includes()를 반복합니다.Set(중복 없는 집합)을 쓰면 더 단순하고 빠릅니다. -
관계형 데이터를 중첩 객체로만 저장 user → posts → comments → likes처럼 깊게 중첩해서 저장해 업데이트와 조회가 복잡해집니다.
-
정규화되지 않은 상태 같은 데이터가 여러 곳에 복사되어 있고, 하나를 수정하면 다른 곳에는 오래된 데이터가 남습니다.
-
트리 구조를 목록처럼 처리 카테고리, 메뉴, 조직도, 댓글 스레드 같은 계층 데이터를 매번 전체 순회로 찾습니다.
-
그래프 문제를 트리처럼 처리 친구 관계, 추천, 의존 관계, 작업 흐름처럼 순환이 가능한 구조에 방문 기록 없이 재귀를 돌립니다.
-
큐가 필요한 곳에
array.shift()사용 작업 대기열, 알림, 백그라운드 작업을array.shift()로 처리해서 데이터가 커질수록 느려집니다. -
스택/큐 개념 없이 재귀 남용 깊은 트리나 작업 흐름을 재귀로만 처리해 스택 오버플로 위험이 생깁니다.
-
우선순위 큐가 필요한 곳에 매번 정렬 가장 중요한 작업, 최신 이벤트, 최고 점수 항목을 찾기 위해 전체 목록을 매번 정렬합니다.
-
LRU/캐시 구조 없이 단순 객체 캐시 사용 캐시 크기 제한, 만료, 제거 정책 없이 데이터를 계속 쌓습니다.
-
인덱스 역할을 하는 자료구조 부재 검색, 필터, groupBy, count를 매번 원본 목록에서 다시 계산합니다.
-
중첩 객체 직접 수정 깊은 객체를 직접 바꿔서 React 상태 변경 감지, 실행 취소/다시 실행, 변경 비교가 꼬입니다.
-
대용량 데이터를 하나의 거대한 객체로 관리 부분 업데이트가 어려워지고, 작은 변경에도 전체 구조를 복사하거나 다시 렌더링합니다.
-
데이터 접근 방식과 자료구조가 맞지 않음 읽기가 많은지, 쓰기가 많은지, 순서가 중요한지, 빠른 검색이 중요한지 고려하지 않고 기본 목록/객체만 사용합니다.
자주 묻는 질문
자료구조란 무엇인가요?
자료구조는 프로그램이 정보를 효율적으로 저장, 검색, 변경, 연결할 수 있도록 만드는 정보의 모양입니다.
배열과 리스트 중 무엇을 써야 하나요?
인덱스로 빠르게 접근하고 주로 끝에서 추가/삭제해야 할 때는 배열이나 동적 배열을 씁니다. 중간 삽입/삭제가 매우 잦아 병목이 될 때만 연결 리스트를 고려하세요.
해시맵은 언제 쓰나요?
고유한 key로 값을 거의 즉시 찾아야 할 때 씁니다. 사용자 ID로 프로필 찾기, API 응답 캐싱, 이름으로 설정값 찾기 등이 예시입니다.
스택과 큐의 차이는 무엇인가요?
스택은 마지막에 넣은 것을 먼저 꺼내는 last-in-first-out 구조입니다. 큐는 먼저 온 것을 먼저 처리하는 first-in-first-out 구조입니다.
트리는 언제 리스트 대신 쓰나요?
데이터에 부모와 자식이 있는 계층 구조가 있을 때 씁니다. 폴다 구조, 댓글과 답글, 조직도 등이 예시입니다.
핵심 정리
자료구조는 시험용 지식이 아닙니다. 제품의 기억이 어떤 모양을 갖는지 결정합니다.
맞는 그릇을 고르면 코드가 자연스러워집니다. 틀린 그릇을 고르면 모든 기능이 싸움이 됩니다.
