배열 (Array)
배열은 여러 개의 데이터를 순서대로 저장하는 자료구조로, 인덱스를 통해 빠르게 접근하고 여러 데이터를 효율적으로 관리할 수 있다.
일반적인 배열
일반적인 배열은 같은 타입의 데이터를 메모리상에 연속적으로 저장하는 자료구조이다.
// 인덱스 0 1 2 3 4
// 값 1 2 3 4 5
[1, 2, 3, 4, 5];배열은 각 요소를 순서대로 저장하고,
프로그램은 배열의 시작 위치를 기준으로 인덱스를 계산해 원하는 값에 접근한다.
배열은 시작 위치를 알고 있기 때문에, 원하는 인덱스의 값을 빠르게 가져올 수 있다.
배열의 인덱스 접근
배열에서 특정 인덱스의 값을 읽는 작업은 배열의 길이와 상관없이 빠르게 처리된다.
const numbers = [10, 20, 30, 40, 50];
console.log(numbers[0]); // 10
console.log(numbers[3]); // 40배열의 길이가 5이든 100이든, numbers[3]처럼 특정 인덱스에 접근하는 작업은 한 번에 처리할 수 있다.
따라서 배열의 인덱스 접근은 O(1)의 시간 복잡도를 가진다.
이처럼 배열은 읽기와 쓰기, 즉 특정 위치의 값을 참조하거나 수정하는 작업에서 좋은 성능을 보인다.
배열의 삽입과 삭제
배열은 인덱스로 접근하는 작업은 빠르지만,
데이터를 삽입하거나 삭제하는 작업은 상대적으로 비효율적일 수 있다.
앞에 데이터 추가하기
const numbers = [10, 20, 30];
numbers.unshift(0);
console.log(numbers); // [0, 10, 20, 30]배열의 앞에 데이터를 추가하면 기존 요소들의 인덱스가 하나씩 뒤로 밀린다.
기존 요소들의 위치를 다시 조정해야 하므로 배열의 길이가 길수록 더 많은 작업이 필요하다.
따라서 배열의 앞에 데이터를 추가하는 작업은 보통 O(n)이다.
앞에서 데이터 삭제하기
const numbers = [10, 20, 30];
numbers.shift();
console.log(numbers); // [20, 30]배열의 첫 번째 요소를 삭제하면 뒤에 있던 요소들이 앞으로 한 칸씩 이동한다.
이 경우에도 배열의 길이에 따라 이동해야 하는 요소가 많아지므로 시간 복잡도는 보통 O(n)이다.
JavaScript 배열의 특징과 내부 구조
JavaScript의 배열은 일반적인 배열처럼 인덱스를 사용해 값을 다룰 수 있다.
const numbers = [10, 20, 30];
console.log(numbers[0]); // 10하지만 JavaScript 배열은 일반적인 배열보다 더 유연하게 동작한다.
const arr = [1, "hello", true, null];
console.log(arr); // [1, "hello", true, null]일반적인 배열은 보통 같은 타입의 데이터를 연속된 메모리 공간에 저장하지만,
JavaScript 배열은 서로 다른 타입의 값도 함께 저장할 수 있다.
const numbers = [10, 20, 30];
numbers.push(40);
console.log(numbers); // [10, 20, 30, 40]또한 배열의 길이를 동적으로 변경할 수 있다.
JavaScript 배열의 내부 최적화
JavaScript 엔진은 배열의 형태에 따라 내부 최적화 방식을 달리한다.
일반 배열(Fast Array): 0부터 순서대로 정수 인덱스를 가진 경우 → 일반적인 배열처럼 최적화해시 배열(Dictionary Mode): 인덱스가 듬성듬성 비어 있거나 일반적인 배열 형태에서 벗어나면,
JavaScript 엔진은 배열을 객체에 가까운 방식으로 처리할 수 있다.
const fastArray = [10, 20, 30, 40]; // Fast Array로 최적화됨
const slowArray = [];
slowArray[0] = 10;
slowArray[1000] = 20; // Dictionary Mode로 처리됨💡 push와 pop
JavaScript 배열에서는 push()와 pop()을 사용해 배열의 마지막에 데이터를 추가하거나 제거할 수 있다.
push()
push()는 배열의 마지막에 요소를 추가한다.
const numbers = [10, 20, 30];
numbers.push(40);
console.log(numbers); // [10, 20, 30, 40]배열의 마지막에 데이터를 추가하는 작업은 보통 O(1)이지만, JavaScript 배열의 내부 용량을 초과하면 재할당으로 인해 O(n)이 될 수 있다. 여러 번 반복하면 평균적으로 O(1)이므로 amortized O(1)이다.
pop()
pop()은 배열의 마지막 요소를 제거한다.
const numbers = [10, 20, 30];
numbers.pop();
console.log(numbers); // [10, 20]마지막 요소를 제거하는 작업도 다른 요소들의 인덱스를 다시 조정할 필요가 없기 때문에 보통 O(1)이다.
배열의 장단점
장점
배열의 가장 큰 장점은 인덱스를 통한 빠른 접근이다.
- 특정 위치의 값을 읽거나 수정하는 작업은
O(1)의 시간 복잡도를 가진다.
단점
배열은 데이터를 삽입하거나 삭제할 때 비효율적일 수 있다.
- 앞/중간 삽입·삭제:
O(n)시간 복잡도 (요소 이동 필요) - 메모리 낭비: 동적 할당 시 미리 예약된 메모리가 있을 수 있음
- 타입 유연성: JavaScript는 타입 혼용으로 인한 엔진 최적화 어려움
배열의 시간 복잡도
배열에서 자주 사용하는 작업의 시간 복잡도는 다음과 같다.
| 작업 | 예시 | 시간 복잡도 |
|---|---|---|
| 인덱스로 접근 | arr[index] | O(1) |
| 인덱스로 수정 | arr[index] = value | O(1) |
| 마지막에 추가 | push() | 평균 O(1) |
| 마지막에서 삭제 | pop() | O(1) |
| 앞에 추가 | unshift() | O(n) |
| 앞에서 삭제 | shift() | O(n) |
| 중간에 추가/삭제 | splice() | O(n) |
| 전체 순회 | for, for...of | O(n) |
| 값 찾기 | includes(), indexOf() | O(n) |