배열 (Array)
한눈에 보기
같은 타입의 데이터를 연속 메모리에 저장하는 기본 선형 자료구조인 배열을 다룬다. O(1) 인덱스 접근의 원리, 연산별 시간 복잡도, 동적 배열의 확장과 분할 상환 분석, 캐시 지역성까지 정리했다.
최종 업데이트 2026.09.17
배열(Array)은 같은 타입의 데이터를 메모리의 연속된 공간에 순서대로 저장하는 가장 기본적인 선형 자료구조다. 배열은 인덱스(index)를 이용해 임의의 위치에 상수 시간으로 접근할 수 있다는 강력한 장점을 갖는 대신, 크기가 고정되어 있고 중간 삽입과 삭제에 비용이 큰 구조다. 거의 모든 프로그래밍 언어가 배열을 기본 제공하며, 리스트, 해시 테이블, 힙 같은 다른 자료구조의 내부 구현에도 널리 쓰인다.
개념과 메모리 구조
배열의 핵심은 연속성이다. 크기가 n인 배열을 선언하면 런타임은 요소 하나의 크기에 n을 곱한 만큼의 메모리를 한 덩어리로 확보한다. 예를 들어 4바이트 정수 10개를 담는 배열은 40바이트의 연속 공간을 차지하고, i번째 요소의 주소는 시작 주소에 i 곱하기 4를 더한 값으로 즉시 계산된다.
이 주소 계산 공식 덕분에 배열은 임의 접근(random access)이 가능하다. 어떤 요소를 읽든 탐색 과정 없이 곱셈과 덧셈 한 번으로 위치를 알아내므로 접근 시간이 O(1)이다. 반면 연결 리스트(Linked List)처럼 노드가 흩어져 있는 구조는 원하는 위치까지 포인터를 따라가야 하므로 이런 접근이 불가능하다.
연속성은 캐시 효율에도 크게 기여한다. 현대 CPU는 메모리를 캐시 라인(cache line) 단위로 읽어오는데, 배열을 앞에서부터 순회하면 다음에 읽을 데이터가 이미 캐시에 올라와 있을 확률이 높다. 이를 공간 지역성(spatial locality)이라고 하며, 같은 O(n) 순회라도 배열이 연결 리스트보다 실제로 훨씬 빠른 이유가 된다.
주요 연산과 시간 복잡도
배열의 연산별 비용은 연속 구조라는 특성에서 그대로 유도된다.
| 연산 | 시간 복잡도 | 설명 |
|---|---|---|
| 인덱스 접근 | O(1) | 주소 계산만으로 즉시 접근한다 |
| 탐색(값 기준) | O(n) | 정렬되지 않았다면 처음부터 순차 확인한다 |
| 탐색(정렬된 경우) | O(log n) | 이진 탐색(binary search)을 적용할 수 있다 |
| 끝에 삽입 | O(1) | 빈 자리가 있다면 마지막 위치에 쓰기만 하면 된다 |
| 중간 삽입 | O(n) | 뒤쪽 요소를 전부 한 칸씩 밀어야 한다 |
| 중간 삭제 | O(n) | 뒤쪽 요소를 전부 한 칸씩 당겨야 한다 |
중간 삽입과 삭제가 비싼 이유는 배열이 빈틈을 허용하지 않기 때문이다. 앞쪽에 요소 하나를 넣으려면 그 뒤의 모든 요소를 이동시켜야 하므로 최악의 경우 n번의 이동이 발생한다.
정적 배열과 동적 배열
선언 시점에 크기가 고정되는 정적 배열(static array)은 크기를 바꿀 수 없다. 이 한계를 보완한 것이 동적 배열(dynamic array)로, 자바(Java)의 ArrayList, 파이썬(Python)의 list, C++의 vector가 대표적이다.
동적 배열은 내부적으로 실제 크기보다 넉넉한 용량(capacity)을 미리 확보해 두고, 요소가 가득 차면 보통 기존 용량의 1.5배에서 2배 크기의 새 배열을 할당한 뒤 기존 요소를 전부 복사한다. 복사가 일어나는 순간에는 O(n)이 들지만, 확장 빈도가 기하급수적으로 줄어들기 때문에 삽입 한 번당 평균 비용은 O(1)로 수렴한다. 이를 분할 상환 분석(amortized analysis)이라고 한다. 확장 시점을 예측하기 어렵다면 예상 크기를 미리 지정해 재할당 자체를 피하는 것이 실무에서 흔한 최적화다.
코드 예시
다음은 자바에서 배열의 접근, 순회, 중간 삽입을 보여주는 예시다. 중간 삽입에서 뒤쪽 요소를 밀어내는 반복문이 O(n) 비용의 정체다.
int[] arr = new int[5];
arr[0] = 10;
arr[1] = 20;
arr[2] = 40;
// 인덱스 접근: O(1)
int x = arr[2]; // 40
// 인덱스 2 자리에 30을 삽입: 뒤 요소를 한 칸씩 민다. O(n)
for (int i = 3; i > 2; i--) {
arr[i] = arr[i - 1];
}
arr[2] = 30; // 결과: 10, 20, 30, 40, ...
장단점과 활용
배열은 구조가 단순해 오버헤드가 거의 없고, 인덱스 접근이 O(1)이며, 메모리 연속성 덕분에 순회 성능이 뛰어나다. 반면 크기 변경이 어렵고, 중간 삽입과 삭제가 느리며, 큰 배열은 그만큼 큰 연속 공간을 요구한다는 단점이 있다.
따라서 배열은 데이터 개수가 대체로 고정되어 있고 조회가 압도적으로 많은 경우에 적합하다. 조회보다 중간 수정이 잦다면 연결 리스트나 트리 계열 자료구조를 검토하는 것이 낫다. 또한 배열은 그 자체로 쓰이는 것 못지않게 스택(Stack), 큐(Queue), 해시 테이블(hash table), 힙(Heap)의 기반 저장소로 쓰인다는 점에서 모든 자료구조 학습의 출발점이 된다.
면접에서 이렇게 물어본다
- 배열의 인덱스 접근이 O(1)인 이유를 메모리 주소 계산 관점에서 설명해 보라.
- 배열과 연결 리스트의 순회 성능이 같은 O(n)인데도 실제 속도 차이가 나는 이유는 무엇인가.
- 동적 배열이 확장될 때 내부에서 어떤 일이 일어나며, 삽입의 분할 상환 시간 복잡도가 O(1)인 이유는 무엇인가.
- 배열의 중간에 요소를 삽입하면 어떤 비용이 발생하는지 설명하고, 이 비용을 피할 수 있는 자료구조를 제시해 보라.
- 정렬된 배열에서 특정 값을 찾는 가장 효율적인 방법과 그 시간 복잡도를 설명해 보라.
자료구조에서 같이 읽기
- Array vs ArrayList vs LinkedList배열, ArrayList, LinkedList의 내부 저장 방식과 연산별 시간 복잡도를 비교한다. 캐시 지역성 때문에 실무에서 ArrayList가 유리한 이유와 상황별 선택 기준을 정리했다.
- AVL 트리와 레드 블랙 트리 (AVL Tree & Red-Black Tree)자가 균형 이진 탐색 트리인 AVL 트리와 레드 블랙 트리를 비교한다. 균형 인수와 회전, 탐색과 삽입 비용, TreeMap이 레드 블랙 트리를 쓰는 이유를 정리했다.
- B 트리와 B+ 트리 (B-Tree & B+ Tree)한 노드에 여러 키를 담아 높이를 낮춘 B 트리와 리프에 데이터를 모아 연결한 B+ 트리를 다룬다. 디스크 I/O 최소화 원리와 데이터베이스 인덱스가 B+ 트리를 쓰는 이유를 정리했다.
- 이진 탐색 트리 (Binary Search Tree)왼쪽은 작은 값, 오른쪽은 큰 값이라는 규칙으로 평균 O(log n) 탐색을 만드는 이진 탐색 트리를 다룬다. 삽입과 삭제 과정, 편향 시 퇴화 문제와 자가 균형 트리, 해시 테이블과의 선택 기준을 정리했다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기