🎉 berenickt 블로그에 온 걸 환영합니다. 🎉
CS
자료구조&알고리즘-js
04-연결(linked)-list

1. Linked List

추가와 삭제가 반복되는 로직이라면 어떻게 해야될까?

  • 배열을 이용하면 시간복잡도가 굉장히 커져 권장되지 않습니다.
  • 배열은 탐색이 많을 떄 유용한 자료구조이다.

추가와 삭제가 많을 떄 유용한 자료구조는 연결 리스트이다.

  • 연결 리스트는 각 요소를 포인터로 연결하여 관리하는 선형 자료구조다.
  • 각 요소는 노드(Node)라고 부르며 데이터 영역포인터 영역으로 구성된다.

Data Structure_4_1


1.2 배열과 차이점

1.2.1 메모리 차이

  • 배열은 순차적인 데이터가 들어가기에 메모리 영역을 연속적으로 사용
  • 연결 리스트는 순차적이지 않기에 각 데이터가 퍼져있음
    • 퍼져있는 메모리의 영역을 알기 위해 포인터를 사용

Data Structure_4_2

1.2.2 배열 요소 추가 및 삭제

Data Structure_4_3

1.2.3 연결 리스트 요소 추가 및 삭제

Data Structure_4_4


2. 연결 리스트(Singly Linked List)

  • Head에서 Tail까지 단방향으로 이어지는 연결 리스트
  • 가장 단순한 형태인 연결리스트

Data Structure_4_5


2.1 요소 찾기

‘4’를 찾는다면?

  1. HEAD 포인터를 찾는다.
  2. HEAD 포인터를 참고하여, 다음 요소인 HEAD를 찾습니다.
  3. 그 요소가 찾는 4인지 확인하고, 아니라면 포인터 영역을 통해 다음 요소로 넘어갑니다.
  4. 2~3번을 반복

Data Structure_4_6


2.2 요소 추가

‘3’을 1과 4 데이터 중간에 추가한다면?

  1. 3의 포인터 영역을 4를 가르킨다.
  2. 2의 포인터 영역을 3을 가르킨다.

Data Structure_4_7


2.3 요소 삭제

‘2’를 삭제한다면?

  1. 삭제할 요소의 이전 포인터 영역을 삭제할 요소의 다음 데이터 영역을 가르키게 한다.
  2. 삭제할 요소를 메모리 상에서 지운다.

Data Structure_4_8


3. 이중 연결 리스트(Doubly Linked List)

  • 양방향으로 이어지는 연결 리스트
  • 포인터가 2개가 존재
  • Singly Linked List보다 자료구조의 크기가 조금 더 크다.

Data Structure_4_9


3.1 요소 추가

‘3’을 2와 4 중간에 추가한다면?

(1) 추가할 요소의 다음 노드를 4를 가르키게 한다.

Data Structure_4_10

(2) 2의 다음 노드를 추가할 요소를 가르키게 한다.

Data Structure_4_11

(3) 4의 이전 노드를 추가할 요소를 가르키게 한다.

Data Structure_4_12

(4) 추가할 요소의 이전 노드를 2를 가르키게 한다.

Data Structure_4_13


3.2 요소 삭제

‘2’를 삭제한다면?

(1) 삭제할 요소의 이전 노드를 삭제할 요소의 다음을 가르키게 한다.

Data Structure_4_14

(2) 삭제할 요소의 다음의 이전 노드를 삭제할 요소의 이전을 가르키게 한다.

Data Structure_4_15

(3) 삭제할 요소를 메모리 상에서 지운다.

Data Structure_4_16


4. 환형 연결리스트(Circular Linked List)

  • Singly 혹은 Doubly Linked List에서 Tail이 Head로 연결되는 연결리스트
  • 메모리를 아껴쓸 수 있다.
  • 원형 큐 등을 만들때도 사용된다.

Data Structure_4_17


5. 구현

1
class Node {
2
// 생성자: new 키워드로 객체를 생성할때 자동으로 호출되는 함수
3
constructor(value) {
4
this.value = value // 값
5
this.next = null // 포인터
6
}
7
}
8
9
class SinglyLinkedList {
10
constructor() {
11
this.head = null // head 포인터
12
this.tail = null // tail 포인터
13
}
14
15
// 📝 해당하는 값 찾기
16
find(value) {
17
let currNode = this.head // 입력받은 연결리스트의 Head 포인터 가져오기
18
// 입력한 값을 찾을 때까지 다음 노드로 순회
19
while (currNode.value !== value) {
20
currNode = currNode.next
21
}
22
return currNode // 값을 찾으면 해당 노드를 반환
23
}
24
25
// 📝 끝에 추가
26
append(newValue) {
27
const newNode = new Node(newValue) // 입력받은 값으로 노드 생성
28
// 헤드가 비어있다면
29
if (this.head === null) {
30
// head와 tail에 생성한 노드를 가리킴
31
this.head = newNode
32
this.tail = newNode
33
} else {
34
// 값이 존재하면, 다음 노드로 생성한 노드를 가리킴
35
// tail은 생성한 노드를 가리킴
36
this.tail.next = newNode
37
this.tail = newNode
38
}
39
}
40
41
// 📝 중간에 추가
42
insert(node, newValue) {
43
const newNode = new Node(newValue) // 입력받은 값으로 노드 생성
44
newNode.next = node.next // (생성한 노드의 다음)을 (입력받은 노드의 다음)을 가리킴
45
node.next = newNode // (입력받은 노드의 다음)을 (새로 생성한 노드)를 가리킴
46
}
47
48
// 📝 삭제
49
remove(value) {
50
let prevNode = this.head // 입력받은 연결리스트의 Head 포인터 가져오기
51
// 입력한 값을 찾을 때까지 다음 노드로 순회
52
while (prevNode.next.value !== value) {
53
prevNode = prevNode.next
54
}
55
// 값을 찾고 다음이 null이 아니라면
56
if (prevNode.next !== null) {
57
// (이전 노드의 다음)을 (다음의 다음)을 가리킴
58
// 그러면 중간 노드가 아무 노드를 가리키고 있지 않기 때문에 GC에 의해 메모리 상에서 삭제
59
prevNode.next = prevNode.next.next
60
}
61
}
62
63
// 📝 출력
64
display() {
65
let currNode = this.head
66
let displayString = '['
67
while (currNode !== null) {
68
displayString += `${currNode.value}, `
69
currNode = currNode.next
70
}
71
displayString = displayString.substr(0, displayString.length - 2)
72
displayString += ']'
73
console.log(displayString)
74
}
75
}
76
77
const linkedList = new SinglyLinkedList()
78
linkedList.append(1)
79
linkedList.append(2)
80
linkedList.append(3)
81
linkedList.append(5)
82
linkedList.display() // [1, 2, 3, 5]
83
console.log(linkedList.find(3))
84
// Node { value: 3, next: Node { value: 5, next: null } }
85
86
linkedList.remove(3)
87
linkedList.display() // [1, 2, 5]
88
linkedList.insert(linkedList.find(2), 10)
89
linkedList.display() // [1, 2, 10, 5]

코딩 테스트에서 연결리스트를 직접 구현하는 경우는 많지 않습니다. 배열을 사용하면 되기 때문이죠.