# 연결리스트
+--------+ +--------+ +--------+ +--------+
| | | | | | | |
| node 0 |--->| node 1 |--->| node 2 |--->| node 3 |
| | | | | | | |
+--------+ +--------+ +--------+ +--------+
연결리스트는 각 원소가 노드로 이루어져 있다. 각 노드는 다음 원소를 참조하고 있으며 양방향 연결 리스트의 경우 이전 노드를 가리킨다. 반드시 head와 tail 위치를 트래킹해야한다. 범위를 벗어나는 경우 nil을 참조하도록 한다.
+--------+ +--------+ +--------+ +--------+
head --->| |--->| |--->| |--->| |---> nil
| node 0 | | node 1 | | node 2 | | node 3 |
nil <---| |<---| |<---| |<---| |<--- tail
+--------+ +--------+ +--------+ +--------+
# 연결리스트 성능
선형 자료구조이기때문에 대부분 O(n) 시간이 소요된다. 대부분의 작업이 배열보다 느리지만 특정 노드에 대한 삽입과 삭제 연산이 빠르다. 노드의 레퍼런스만 바꿔주면 되기 때문에 O(1)시간이 소요된다.
# 연결리스트 구현
- 노드를 정의한다.
- 노드로 이루어진 전체 연결리스트를 정의한다.
class Node{
// 제네릭
var value: T
var next: Node?
// weak var previous: Node?
init(value: T){
self.value = value
}
}
class LinkedList{
var head: Node?
var last: Node? {
// guard var 바인딩임에 주의하자
guard var node = head else {
return nil
}
while let next = node.next else {
node = next
}
return node
}
var count: Int{
guard var node = head else {return 0}
var count = 1
while let nextNode = node.next {
node = nextNode
count += 1
}
return count
}
func isEmpty() -> Bool {
return head == nil
}
func first() -> Node?{
return head
}
func append(value: T){
let newNode = Node(value: value)
// if let 라스트노드 언래핑
if let lastNode = last {
newNode.next = nil
lastNode.next = newNode
} else {
head = newNode
}
}
func node(atIndex: Int) -> Node{
if index == 0 {
return head!
}else{
var node = head!.next
for _ in 1 ..< atIndex{
node = node?.next
if(node == nil){
break
}
}
return node!
}
}
subscript(index: Int) -> T{
let node = self.node(atIndex: index)
return node.value
}
func insert(_ node: Node, atIndex index: Int){
let newNode = node
if index == 0{
newNode.next = head
head = newNode
} else {
let previousNode = node(atIndex: index - 1)
let nextNode = previousNode.next
newNode.next = nextNode
previousNode.next = newNode
}
}
func removeAll(){
head = nil
}
// remove 메서드는 양방향 연결리스트일때 구현이 쉽다.
// previous 속성이 있어야함.
public func remove(node: Node) -> T {
let prev = node.previous
let next = node.next
if let prev = prev {
prev.next = next
} else {
head = next
}
next?.previous = prev
node.previous = nil
node.next = nil
return node.value
}
}
- last노드(계산속성) - 헤드 노드를 옵셔널 바인딩으로 언래핑했을때
nil
이라면 해당 연결리스트는 빈 리스트이다.- 노드의 next가 존재하면 무한반복문을 계속 순회하며 nil이 나오면 해당 노드가 last이므로 리턴한다.
- guard var 바인딩임에 주의하자.
- count(계산속성) - 헤드노드가 nil이면 0을 리턴하고 nil이 아니면 node.next를 바인딩하여 카운트를 1씩 증가시켜 최종 값을 리턴한다.
- count속성을 트래킹하면 O(n)에서 O(1)로 속도를 증가시킬 수 있다.
- isEmpty(메서드) - 헤드노드가 nil이면 빈 리스트이다.
- first(메서드) - head노드를 리턴한다.
- append(메서드) - 파라미터로 전달된 값으로 노드를 하나 생성하고 라스트노드의 next레퍼런스를 새로운 노드로 설정한다. 새로운 노드의 next는 nil로 초기화한다.
- 라스트노드가 없다면 빈 리스트이므로 헤드노드를 새 노드로 초기화한다.
순환참조
양방향 연결리스트 구현 시 클래스 인스턴스간 순환참조 문제로 인해 메모리 누수가 발생한다. 반드시 약한참조로 선언하자.