public class Arrays, extending Object.NullPointerException, if the specified array reference is null, except where noted.int[][] Y = new int[8][10]; // row size : 8, col size : 10
A mathematical model of the data objects that make up a data type as well as the functions that operate on these objects.
= ADT란 데이터 타입을 이루는 데이터 객체들과, 이 객체들에 대해 수행되는 함수들로 구성된 수학적 모델이다.
Defined indirectly, by the operations that may be performed on it and by mathematical constraints on the effects of those operations.
= ADT는 직접적으로 구현을 보여주지 않고, 그 위에서 어떤 연산들을 수행할 수 있는지와, 그 연산들이 어떤 수학적 제약(결과 조건, 불변식 등)을 만족해야 하는지를 통해 간접적으로 정의된다.
즉, “추상 자료형(ADT)이란, 특정 데이터 객체들과 그 객체들에 대해 수행할 수 있는 연산 및 그 연산이 만족해야 하는 제약 조건으로 정의되는 수학적 모델이다.”
스택, 큐, 어레이 등의 자료구조에서 이 배열의 방향성을 나타내는 용어가 너무 헷갈리니 한 번 정리해보자.
- 쓰이는 용어는
front back top forward 등.ArrayList
- ArrayList에서 add할 때 n-i개의 원소들을 shift 'forward'할 때 이 'forward'는 인덱스가 증가하는 방향을 의미한다. 따라서 인덱스 i부터 끝까지 있는 원소들을 한 칸씩 오른쪽으로 옮긴다는 의미이다.
- 반대로 인덱스가 줄어드는 방향은 backward이다.
- ArrayList에서 단순히 add(o)를 하는 경우에는 A[n]에 추가가 된다. 따라서 인덱스가 큰 쪽이 back임을 알 수 있다.
Stack (LIFO)
보통 스택 자료구조에서는 front, back이라는 용어를 잘 쓰지 않는다. 그저 top이라는 용어만 존재. 하지만 실제 디버깅을 하는 과정에서는 리스트처럼 노출되므로 방향이 혼동될 수 있다.
Stack A = [1, 3, 5, 8, 9]이 때 top은 9.
이게 헷갈렸던 부분. 보통 Array나 Queue는 좌/우 기준으로 어디가 앞이고 뒤인지 알 수 있는데, 스택은 그게 애매해서 헷갈렸다.
-> 정리하자면, 통상적으로 배열의 좌측은 Front, 우측은 End이며, 스택에서는 우측이 Top이라고 보는 것이다. 왜냐, 어느 자료 구조든 원소를 '끝에 추가'할 때는 항상 오른쪽에 들어오기 때문.왜 '추가'는 항상 오른쪽?
배열 왼쪽(앞쪽)에서 삽입/삭제하면 원소들을 전부 한 칸씩 shift해야 해서 O(n) 시간이 걸린다. 반면, 배열 오른쪽 끝에서는 삽입/삭제가 단순히 크기만 조정하면 되니까 O(1)로 빠르다. 그래서 관례적으로 스택은 배열의 오른쪽 끝을 top으로 잡는다.Queue(FIFO)
그러므로, 위의 논리를 따라가면 Queue역시 굳이 다른 걸 추가로 구현할 필요 없이, pop만 좌측에서 되도록 하나 만들어주면 구조적으로 완성된다.
그래서 파이썬에서도 deque.popleft()가 BFS 구현에 필요한 '큐'의 핵심 기능으로 사용됐던 것.
굳이 O-notation에 'worst case'라는 말을 붙여야 하나? -> Average case, Best case와 구분하려고.
어떤 알고리즘은 상황별로 복잡도가 다름.
QuickSort → best: O(n log n), worst: O(n^2)
HashTable → average: O(1), worst: O(n)
이럴 땐 “O(n) in worst case”라고 말해줘야 정확히 전달됨.
Algorithm add(o)
input array A of n integers
output array A of n+1 integers
if n = A.length then
// 1. A보다 큰 사이즈의 배열 선언
S <- new array of size ...
// 2. A 원소들을 S에 복사
for i <- 0 to n-1 do
S[i] = A[i]
// 3. A를 S로 대체
A <- S
n <- n + 1 // 4. 배열의 마지막 + 1번째 인덱스 갱신
A[n-1] <- o // 5. n-1 인덱스에 o 삽입
n번의 add(o) operations를 진행할 때 소요되는 총 T(n)을 비교해보자.
1. empty array에서 시작
2. Amortized time : T(n)/n을 생각해보자.
왜 n으로 나눈 시간을 고려해야 하는가?
Amortized time : 여러 번의 연산을 평균냈을 때 한 연산당 걸리는 시간.
add(o)는 O(n)이라 느려보이지만, 실제로 대부분 O(1)이고, 배열이 꽉 찼을 때만 resize해주기 때문에 실제 시간은 T(n)/n이 합리적.