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은 다른 객체)