본문 바로가기
Old Posts/Java

[Java] 컬렉션들의 시간복잡도 (Collection Big-O)

by A6K 2021. 7. 24.

자바를 이용해서 알고리즘 문제를 풀거나 큰 사이즈의 데이터를 다룰 때, 컬렉션들의 정확한 시간복잡도(Big-O)를 알고 사용하는 것이 중요하다. 자칫 불필요하게 느린 컬렉션이나 메소드를 사용할 경우 예상치 못한 성능저하를 만날 수 있기 때문이다.

List

  add() remove() get() contains()
ArrayList O(1) O(n) O(1) O(n)
LinkedList O(1) O(1) O(n) O(n)
CopyOnWriteArrayList O(n) O(n) O(1) O(n)

참고로 LinkedList의 삭제 연산인 remove() 메소드는 삭제할 노드에 대한 참조를 가지고 있다는 가정하에 O(1)임을 유의해야한다. 만약 처음부터 지울 데이터를 찾아야한다면 탐색 비용인 O(n)이 소요된다.

Set

  add() contains() next()
HashSet O(1) O(1) O(h/n)
LinkedHashSet O(1) O(1) O(1)
EnumSet O(1) O(1) O(1)
TreeSet O(log n) O(log n) O(log n)
CopyOnWriteArraySet O(n) O(n) O(1)
ConcurrentSkipList O(log n) O(log n) O(1)

HashSet에서 next() 메소드는 O(h/n) 시간 복잡도를 갖는다. h는 해시 버킷의 사이즈를 의미하고, n은 HashSet에 저장되는 데이터의 사이즈를 의미한다. h/n 이라는 값이 나오는 이유는 엘리먼트에 비해 해시버킷의 수가 늘어나면 해시버킷으로 사용되는 배열의 대부분은 비어있게 되고, 엘리먼트가 담겨있는 해시버킷을 찾기 위해 매번 비어있는 해시버킷을 방문해야하기 때문에 h가 들어간다. 또 한, 엘리먼트의 숫자가 늘어나면 해시 버킷이 비어있을 가능성이 줄어들게 되고, O(1)에 근접하게 된다. 이런 의미에서 h/n 이라는 시간복잡도가 나온 것으로 생각된다.

Queue

  offer() peak() poll() size()
PriorityQueue O(log n) O(1) O(log n) O(1)
LinkedList O(1) O(1) O(1) O(1)
ArrayDequeue O(1) O(1) O(1) O(1)
ConcurrentLinkedQueue O(1) O(1) O(1) O(n)
ArrayBlockingQueue O(1) O(1) O(1) O(1)
PriorityBlockingQueue O(log n) O(1) O(log n) O(1)
SynchronousQueue O(1) O(1) O(1) O(1)
DelayQueue O(log n) O(1) O(log n) O(1)
LinkedBlockingQueue O(1) O(1) O(1) O(1)

Map

  get() containsKey() next()
HashMap O(1) O(1) O(h/n)
LinkedHashMap O(1) O(1) O(1)
IdentityHashMap O(1) O(1) O(h/n)
WeakHashMap O(1) O(1) O(h/n)
EnumMap O(1) O(1) O(1)
TreeMap O(log n) O(log n) O(log n)
ConcurrentHashMap O(1) O(1) O(h/n)
ConcurrentSkipListMap O(log n) O(log n) O(1)

위에서 본 시간 복잡도는 Worst Case가 아닌 Amortized Analysis가 적용된 평균적인 값이다. 예를 들어 ArrayList의 add() 연산은 배열이 꽉 찾을 때 두 배 더 큰 새로운 배열에 엘리먼트들을 복사하는 최악의 경우가 있어서 O(n)으로 적어야 한다. 하지만 이 연산을 Amortize 하여 평균적으로 O(1)이라고 할 수 있는 것이다.

댓글