최근 사고력의 향상을 위해서 꾸준하게 알고리즘 문제를 풀어보고 있다. 당연 필자가 가장 자신있는 언어라고 생각하는 파이썬을 응용하고 있는데 시간초과가 생각보다 많이 발생했다. O(1)
로 접근할 수 있는 요소를 O(n)
으로 접근하는 등 기본기의 부족으로 인함으로 보였다. 그리하여 파이썬의 각 자료형의 연산에 대한 복잡도를 어지간하면 숙지해 놓으려고 한다.
출처 : https://www.ics.uci.edu/~pattis/ICS-33/lectures/complexitypython.txt
리스트
자료형 리스트의 각 연산별 복잡도
O(1)
연산자 | 예시 | 주석 |
Index | my_list[i] | 연결 리스트라 당연히 O(1)이 아닐거라 생각했다. 그래서 일부러 딕셔너리로 해결한적도 많았는데... 파이썬을 너무 얕본 듯 하다. |
Store | my_list[i] = 0 | 이하 동문... |
Length | len(my_list) | 아마도 내부적으로 길이 변수를 가지고 있는 듯 하다. |
Append | my_list.append(x) | |
Pop | my_list.pop() | 스택으로 문제를 해결할 방안을 찾을 수 있다면 좋은 성능으로 해결할 수 있을 듯 보인다. |
Clear | my_list.clear() | my_list = [] 으로 빈 리스트를 생성하는 것도 동일한 복잡도를 가진다. |
O(N)
연산자 | 예시 | 주석 |
Check | my_list1 == my_list2 | 이것은 충분히 이정도의 복잡도가 걸릴걸 예상하고 있었다. 😀 |
Insert | my_list[a:b] = ... | - |
Delete | del my_list[x] | 잠깐 del이 O(N) 이었다고? 리스트를 그간 연결 리스트라고 생각했는데 순차 리스트였나? 인덱스 교체를 위한 작업때문인 것으로 생각되는데... 여하지간 문제풀때 del을 남발하는 경우도 많았는데 주의해야 겠다. |
Containment | x in my_list | - |
Copy | my_list.copy() | - |
Remove | my_list.remove(x) | - |
Pop | my_list.pop(x) | x라는 값을 pop하기 위해서는 정확히 N-x 만큼의 복잡도가 걸린다. |
Extream value | my_list.reverse() | - |
Iteration | for x in my_list | break 나 return 이 없는 경우 최악 |
O(?)
연산자 | 예시 | 복잡도 | 주석 |
Slice | my_list[a:b] | O(b-a) | - |
Extend | my_list.extend(...) | O(len(...)) | my_list1 + my_list2도 동일한 복잡도를 가질 것으로 생각된다. 배열의 길이만큼 append가 발생한다고 생각하면 기억하기 쉬울 것 같다. |
Construction | list(...) | O(len(...)) | - |
Sort | my_list.sort() | O(N Log N) | 키/역순은 영향을 주지 않는다. 파이썬의 정렬은 각 자료의 상태에 따라서 적절한 정렬 알고리즘을 사용한다고 들었는데 자세한 내용은 모르겠다! (당당) |
Multiply | x*my_list | O(k N) | - |
집합
자료형 집합의 각 연산별 복잡도
O(1)
연산자 | 예시 | 주석 |
Length | len(my_set) | - |
Add | my_set.add(5) | - |
Containment | x in my_set | - |
Remove | my_set.remove(...) | - |
Discard | my_set.discard(...) | - |
Pop | my_set.pop() | - |
Clear | my_set.clear() | my_set = set() 로 빈 집합을 생성해도 동일한 복잡도를 가진다. |
O(N)
연산자 | 예시 | 주석 |
Iteration | for x in my_list | break 나 return 이 없는 경우 최악의 복잡도를 가진다. |
Copy | my_list.copy() | - |
O(?)
연산자 | 예시 | 복잡도 | 주석 |
Construction | set(...) | O(len(...)) | - |
Check | s != t | O(len(s)) | - |
<= / < | s <= t | O(len(s)) | - |
>= / > | s >= t | O(len(t)) | - |
Union | s | t | O(len(s)+len(t)) | - |
Intersection | s & t | O(len(s)+len(t)) | - |
Difference | s - t | O(len(s)+len(t)) | - |
Symmetric Diff | s ^ t | O(len(s)+len(t)) | - |
딕셔너리
자료형 딕셔너리의 각 연산별 복잡도
O(1)
연산자 | 예시 | 주석 |
Index | my_dict[k] | - |
Store | my_dict[k] = v | - |
Length | len(my_dict[k]) | - |
Delete | del my_dict[k] | - |
Get / Set | my_dict.get(k) | - |
Pop | my_dict.pop(k) | - |
Pop item | my_dict.popitem() | - |
Celar | my_dict.clear() | my_dict = dict() 로 비어있는 딕셔너리를 생성해도 동일한 복잡도를 가진다. |
View | my_dict.keys() | my_dict.values()도 동일한 복잡도를 가진다. |
O(N)
연산자 | 예시 | 주석 |
Iteration | for key in my_dict | all forms: keys, values, items |
O(?)
연산자 | 예시 | 복잡도 | 주석 |
Construction | dict(...) | O(len(...)) | - |
앞으로 문제를 풀때는 최대한 이 표를 기억하며 O(1)
의 연산자로 풀이할 방안을 찾도록 해야겠다. 😅
Ghost