[자료구조] 동적배열 (Dynamic Array)
배열이란 같은 종류의 데이터들 일렬로 저장하는 자료구조이다.
배열에 저장된 각 데이터들은 번호(index)를 가지고 있다.
하지만 이러한 배열만으로는 비효율적이거나 한계가 있는 작업이 있기 때문에 동적배열이나 연결 리스트 등과 같은
자료 구조를 만들어 사용한다.
(동적배열이든 연결 리스트든 모두 기본적으로 배열을 이용하여 만들어낸 자료구조이다.)
동적배열(Dynamic Array) 이란?
동적 배열이란 크기가 고정되지 않은 배열을 의미한다. (이름 그대로 단순하다!)
보통 우리가 흔히 말하는 배열은 동적(Dynamic)의 반대인 정적(Static) 즉, 정적배열을 의미한다. 정적배열은 크기가 고정되어 있어 데이터를 크기 만큼만 저장할 수 있다.
대표적으로 C++ 의 vector 나 자바의의 ArrayList 등이 있다.
동적배열이 필요한 이유
동적배열은 이름에서 나타나듯이 배열의 크기를 동적으로 늘려서 사용하고 싶을 때 필요하다. (이유도 단순하다!)
배열은 크기가 고정해야 되기 때문에 이를 해결하기 위해 동적배열이 고안된 것이다.
처음부터 자신이 사용할 배열의 크기를 알면 좋겠지만 크기를 너무 크게 잡으면 메모리가 낭비되고, 그렇다고 크기를 작게 잡으면 매번 배열을 새로 만들어서 기존의 배열을 옮겨 담아야 해서 번거롭다.
동적배열의 시간복잡도
1. 특정 위치의 데이터를 반환하거나 변경하는 작업을 시간복잡도 O(1) 에 처리할 수 있다.
(배열과 공통)
동적배열은 배열을 이용하여 구현되어 있기 때문에 데이터들이 메모리의 연속된 위치에 저장되어 있고, 각 데이터들은 번호(Index)를 가지고 있다. 그래서 특정 위치의 데이터를 호출하거나 변경하는 O(1)에 처리할 수 있다.
resize() 호출수(K) = 이 된다.
즉, 데이터를 1,000(N) 개를 넣을 때 배열의 재할당 크기를 10(M) 이라고 하면
해서 재할당(K)은 100번 일어난다.
(데이터를 10개 넣고나면 배열이 가득 차서 배열의 크기를 새로 늘려줘야 하기 때문)
여기서 M 은 상수 이기 때문에 N 이 커질수록 M 값이 미치는 영향은 작아진다.
따라서 resize() 의 시간복잡도(K)는 O(N) 이 된다.
( M이 10이든 100이든 상수는 결국 시간복잡도에 영향을 주지 못한다. )
배열을 재할당 할 때 마다 새 배열에 복사되는 데이터의 수는 각 M개, 2M개, , 개 이다.
N개의 데이터를 넣으면서 복사되는 데이터의 수를 모두 더하면 (1+2+3++K)M 이므로
이 된다.
즉, N번의 append() 를 수행하는 데에는 수행시간이 총 만큼 소요되고 이를 N 으로 나누어서 평균을 내면 O(N) 이 되어 append() 평균 수행시간은 O(N) 이 되버린다.
하지만 배열의 크기를 기존 배열 크기의 2배씩 늘리게 되면
배열을 재할당 할 때 마다 새 배열에 복사되는 데이터의 수는 각 1개, 2개, 4개, 8개, , 개 이다.
N개의 데이터를 넣으면서 복사되는 데이터의 수를 모두 더하면 1+2+4+8+ + 이므로
등비수열의 합 공식 에 따라 이 된다.
( 1 은 이기 때문에 K번 재할당 했으면 마지막은 K-1 번째가 된다. )
그림으로 이해해보자면 배열의 크기가 2배씩 증가하기 때문에 마지막인 1 번째 부터 K-2 번째 까지의 합이 K-1 번째 값과
거의 같다.
K-1 번째 resize() 의 시간복잡도는 O(N) 일 것이기 때문에 결국은 1 번째 부터 K-1 번째 까지 데이터를 복사한 수행시간은
기껏 해야 O(2N) 일 것이고 결국 append()의 총 수행시간은 O(N) 이 된다.
따라서 배열의 크기가 2배씩 증가할 때 append()의 평균수행 시간은 O(N)을 N으로 나눈 O(1)이라고 할 수 있다.