본문 바로가기

STUDY/자료구조

[자료구조] stack, queue, 정렬&비정렬 리스트 시간복잡도 정리

시간복잡도와 공간복잡도

알고리즘을 평가할 때는 두가지 요소로 측정한다. 첫째는 시간복잡도, 둘째는 공간복잡도

시간 복잡도는 말 그대로 알고리즘을 수행하는데 걸리는 시간이다. 즉, 수행시간이다.

공간 복잡도는 알고리즘의 메모리 사용량을 말한다.

 

알고리즘을 평가할 때 고려해야할 점은 알고리즘이 최악의 경우로 돌아가게 되는 것을 기준으로 해야한다는 것이다. 그 이유는 worst case를 사용해야 다른 요소들이 순수한 알고리즘만 평가하는 데 방해를 하지 않기 때문이다.

예를 들어, 리스트에서 해당되는 값을 찾아내는 알고리즘이라고 하자. 이럴 경우 찾으려는 아이템이 리스트의 첫번째에 위치하는 경우면 바로 찾을 수 있고 시간도 절약되니까 좋겠지만, 아이템이 리스트의 어느 위치에 있을지는 아무도 모르기 때문에 평가를 하기에는 적합하지 않다. 따라서 최악의 경우로 평가를 한다.

오늘은 다양한 자료구조들의 함수 별 시간 복잡도에 대해서만 알아보겠다.

자료구조 클래스들의 member function은 이전 포스트들에서 구현한 멤버 함수들을 기준으로 설명하겠다.

 

stack: josushell.tistory.com/3?category=427367

queue: josushell.tistory.com/4?category=427367

비정렬 리스트: josushell.tistory.com/5?category=427367

정렬 리스트: josushell.tistory.com/6?category=427367


Big O notation (빅오 표기법)

알고리즘의 성능을 표기할 때 다른 표기법들도 많은데 왜 굳이 big O를 사용할까

그것은 바로 빅오표기법이 알고리즘의 상한선을 기준으로 하기 때문이다.

 

예를 들어 어떤 알고리즘의 시간 복잡도를 나타낸 함수가 

 

 라고 하면 

 

Big O notation으로 표기한 시간 복잡도는 

 

가 된다.

 

최고차항을 제외한 나머지는 버리고, 즉 상한선을 기준으로 표기한다.

 

빅오 표기법은 두가지 특징이 있다.

1. 상수항은 무시한다

  -> 시간 복잡도 함수가 2N인 경우 빅오 표기법으로는 O(N)이다.

 

2. 최고차항만 표기한다.

  -> 앞서 말했듯이 영향력이 없는 항들은 제외하고 최고차항만 표기한다.


Stack의 시간복잡도

stack memebr function Array Based  Linked List
생성자 O(1) O(1)
소멸자 O(1) O(N)
full O(1) O(1)
empty O(1) O(1)
push O(1) O(1)
pop O(1) O(1)
top O(1) O(1)

O(1) - 상수시간: 알고리즘이 문제를 해결하기 위해서 한 단계만 거침

O(N) - 직선시간: 입력값과 문제 해결을 위한 단계가 1:1

 

Array based stack은 단순히 top 숫자 연산으로 이루어지기 때문에 시간 복잡도가 O(1)이다.

Linked List stack은 소멸자가 중요하다. heap영역의 모든 new를 할당해제 해줘야하기 때문이다. 따라서 데이터가 N개 있을 때 N번의 delete가 필요하기 때문에 시간 복잡도는 O(N)이다. 나머지는 전부 연산이 한번씩 이루어진다. 이 때 연산의 기준은 코드의 줄 수가 아니라 함수가 끝날 때까지 몇번의 작업을 하는가에 달려있다.

 


Queue의 시간복잡도

queue member function Array Based Linked List
생성자 O(1) O(1)
소멸자 O(1) O(N)
full O(1) O(1)
empty O(1) O(1)
push O(1) O(1)
pop O(1) O(1)

stack과 동일한 이유로 소멸자만 O(N)이고 나머지는 상수시간이다.

 

 


비정렬 리스트의 시간복잡도

unsorted member function Array Based Linked List
생성자 O(1) O(1)
소멸자 O(1) O(N)
full O(1) O(1)
empty O(1) O(1)
length O(1) O(1)
reset O(1) O(1)
get O(1) O(1)
retrieve O(N) O(N)
insert O(1) O(1)
deleted O(N) O(N)

Array based 비정렬 리스트에서  retrieve deleted 이 함수들은 데이터를 찾는 과정이 필요하다. 찾아서 반환 혹은 삭제를 해야하기 때문에 최악의 경우는 데이터가 N개일 때 N번을 찾는 것이다. 따라서 시간 복잡도는 최악의 경우인 O(N)이다.

 


정렬 리스트의 시간복잡도

sorted member function Array Based Linked List
생성자 O(1) O(1)
소멸자 O(1) O(N)
full O(1) O(1)
empty O(1) O(1)
length O(1) O(1)
reset O(1) O(1)
get O(1) O(1)
retrieve O(N) / O(log N) O(N)
insert O(N) O(N)
deleted O(N) O(N)

O(log N) - 로그시간: 문제를 풀 때 특정 요인에 의해 필요한 단계들이 줄어듬

 

오늘의 포인트는 정렬 리스트

지난 포스트에서 Array based 정렬 리스트는 이진탐색을 사용해서 구현했다. 따라서 이진탐색으로 retrieve를 구현한다면 시간 복잡도는 O(log N), 아니면 O(N)이다.

왜 이진탐색의 시간 복잡도가 O(log N)인지 증명이 궁금하다면 josushell.tistory.com/6 참고

 

여기서 질문이 나올 수 있다.

▶ insert, deleted 또한 이진탐색으로 구성했는데 왜 시간 복잡도는 O(log N)이 아닌 O(N)인가?

insert와 deleted를 이진탐색으로 구현한 것은 추가 혹은 삭제할 데이터의 위치를 찾은 것이다.

insert의 경우, 추가할 위치를 찾은 후에는 그 위치의 뒤에 있는 데이터들을 전부 한 칸씩 밀어내서 추가할 데이터의 자리를 마련해야한다. 한 마디로 리스트들이 방 빼줘야함

deleted의 경우에도 마찬가지로, 삭제할 아이템의 위치를 찾았다면 그 뒤에 있는 데이터들을 한 칸씩 앞당겨야한다.

그래서 최악의 경우를 따지자면 추가할 위치가 맨 처음이라 데이터를 N번 뒤로 밀어내는 경우, 삭제할 데이터가 맨 처음이라 데이터를 N번 당겨와야 하는 경우이므로 O(N)이다.

원래는 N+logN 이지만 최고차항만 표기한다는 빅오 표기법에 따라 어짜피 O(N)인 것이다. 

 

Linked List 로 구현한 정렬 리스트도 비정렬 리스트와 마찬가지인 이유로 소멸자, retrieve 함수 모두 시간 복잡도가 O(N)이다.