Array와 Linked List는 대표적인 선형 데이터 구조이다. 두 구조는 각각 장단점이 뚜렷하며, 사용 목적에 따라 선택해야 한다.
빠른 임의 접근 (Random Access)
let element = array[2] // O(1)
캐시 친화적 (Cache-friendly)
간단한 구조
크기 제한
삽입 및 삭제 비용
array.insert(3, at: 1) // O(n)
array.remove(at: 1) // O(n)
메모리 낭비
동적 크기 조절
빠른 삽입 및 삭제
// 삽입
newNode.next = current.next
current.next = newNode
// 삭제
current.next = current.next?.next
메모리 효율 (특정 상황)
느린 임의 접근
var current = head
while index > 0 {
current = current?.next
index -= 1
}
추가적인 메모리 사용
비효율적인 캐싱
복잡한 구현
| 특징 | Array | Linked List |
|---|---|---|
| 임의 접근 속도 | O(1) | O(n) |
| 삽입/삭제 속도 | O(n) (중간 요소) | O(1) (노드 접근 시) |
| 크기 조절 | 제한적 / 동적 배열은 O(n) | 동적 크기 (O(1)) |
| 메모리 사용 | 효율적 (연속적 메모리) | 비효율적 (포인터 저장 필요) |
| 캐시 효율 | 높음 | 낮음 |
| 구현 난이도 | 간단 | 복잡 |
Array를 선택할 때
Linked List를 선택할 때
결론:
Dynamic Array(동적 배열)
resize()와 같은 사례enqueue 연산은 대다수의 경우 O(1)이므로 평균적으로 Amortized O(1)Stack의 Push-Pop과 Multi-Pop
pop 연산이 여러 번 호출될 때, 최악의 경우 전체 스택을 비우게 될 수도 있음. 하지만 그 과정에서 각 요소는 한 번만 pop 되므로 Amortized O(1)분리 연결 리스트의 Union-Find
Aggregate Analysis
resize()의 비용 합산 후 평균 계산Accounting Method (회계 방식)
enqueue 때마다 약간의 비용을 미리 적립해 두고, 나중에 resize 비용으로 사용Potential Method (잠재적 에너지 방식)
private(set)은 변수나 프로퍼티의 읽기 접근은 공개적으로 허용하지만, 쓰기 접근은 선언된 파일 내에서만 가능하도록 설정하는 접근 제어자이다. 주로 외부에서는 값을 수정하지 못하도록 하면서 내부적으로만 값을 관리하고 싶을 때 사용된다.
struct Counter {
private(set) var count = 0
mutating func increment() {
count += 1
}
}
var myCounter = Counter()
print(myCounter.count) // 0 (읽기는 가능)
myCounter.increment()
print(myCounter.count) // 1
// myCounter.count = 10 // 오류! 외부에서는 수정 불가
=== & !==)===와 !==는 두 레퍼런스 타입(주로 클래스 인스턴스)이 같은 메모리 주소를 가리키고 있는지 비교할 때 사용한다. 두 ViewController 인스턴스가 같은지 확인할 때도 자주 사용되곤 한다.
==: 값이 같은지 비교 (Equatable 프로토콜 준수)===: 메모리 주소가 같은지 비교 (동일한 객체인지 확인)class MyClass {
var value: Int
init(value: Int) {
self.value = value
}
}
let obj1 = MyClass(value: 10)
let obj2 = obj1
let obj3 = MyClass(value: 10)
print(obj1 === obj2) // true (obj1과 obj2는 같은 객체를 참조)
print(obj1 === obj3) // false (obj1과 obj3은 다른 객체)
print(obj1 !== obj3) // true (obj1과 obj3은 다른 객체)