연결 리스트
포스트
취소

연결 리스트

해당 포스트는 TypeScript를 이용한 Linked List 구현에 대한 포스트입니다.

❓ 연결 리스트란? ( Linked List )

Linked List에는 노드라고 불리는 단위가 연결된 형태로 배열과 유사하게 보이는 자료구조를 의미합니다.

노드는 데이터와 다음 노드를 가리키는 주소를 갖습니다.
이 노드들이 연속적으로 다음 노드를 가리키면서 연결된 형태를 띄는 것이 Linked List입니다.

SinglyLinkedList 단방향 연결 리스트

위 그림의 녹색 상자가 노드이고, 노드 내부에는 데이터와 주소(포인터) 값을 갖습니다.
보기 쉽게 하기 위해서 규칙적으로 배치했지만, 실제로는 여기 저리 떨어져있어도 문제 없습니다. ( 각자가 다음 노드의 주소를 갖기 때문 )

0️⃣ Linked List 특징

밀집 배열과 비교해봐야 명확하게 특징을 이해할 수 있습니다.

밀집 배열은 일반적으로 생각하는 배열입니다.
메모리에 연속적인 공간에 같은 크기로 데이터가 들어가는 배열이죠.

1. 요소 접근

밀집 배열은 특정 요소에 접근하는 속도가 매우 빠릅니다.
메모리 공간이 연속적이고 같으니까 하나하나 찾을 필요 없이 바로 접근이 가능합니다.

반면 Linked List는 요소에 접근하기 위해서는 현재 요소의 주소(포인터)를 이용해서 접근해야합니다.
따라서 100번째 요소를 찾는다면 99번의 요소를 거쳐야 원하는 데이터를 찾을 수 있습니다.

2. 요소 삽입 및 삭제등의 동작 처리

Linked List는 삽입과 삭제같은 것을 빠르게 처리할 수 있습니다.
원하는 위치의 포인터만 바꿔치기해주면 끝입니다.
찾는 것을 제외하면 단 두번의 수정으로 처리가 가능하죠.

반면 밀집 배열은 마지막 요소를 삭제하는 경우를 제외하면 삭제하고 나서 배열 삭제된 이후 요소들을 이동해야합니다.
연속적인 공간에 같은 크기로 나열되기 때문입니다.
그리고 최초에 지정한 크기보다 배열의 크기가 늘어난다면 전체를 이동시키는 작업도 필요하겠죠.

대상요소 접근요소 수정 및 삭제 등의 동작
Array비교적 빠름비교적 느림
Linked List비교적 느림비교적 빠름

1️⃣ Linked List를 주제로 정한 이유

개인적으로 Linked List를 주제로 정한 이유를 적어보겠습니다.

JavaScript에서 배열을 사용해보면 특이한 점이 있습니다.
다른 언어를 안 해봤다면 느끼지 못했겠지만, 배열에는 어떤 요소라도 넣을 수 있습니다.

1
const arr = ["1", 1, true, null, undefined, Symbol(1), {}, []];

JavaScript에는 자료형이라는 개념이 없지만 서로 다른 크기를 갖는 여러가지 데이터를 마음대로 배열에 넣을 수 있습니다.
( 위 예시는 할 수 있다는 것을 보여주기 위해 사용한 것이지 실제로 서로 다른 형태의 데이터를 넣으면 배열을 관리하기 매우 힘들어집니다. )
( 그럼에도 불구하고 넣지 않고 개발을 할 수 없다면 아마 설계 자체가 잘못됐을 확률이 높다고 생각합니다. ( 개인적인 생각 ) )

저희가 아는 일반적인 배열(밀집 배열)은 연속적인 공간에 같은 크기의 데이터를 보관하는 형태인데 그렇다면 위와 같은 코드가 가능한 것으로 볼때는 JavaScript배열은 밀집 배열이 아닙니다.

실제로는 Linked List에 가깝다고 느껴집니다. ( 개인적인 생각 )
JavaScript의 배열은 해시 테이블로 구현된 객체라고 하는데 아직 이게 무슨 의미인지는 모르겠지만, 제가 아는 만큼으로는 Linked List에 더 가깝기 때문에 이번 포스트 주제로 정하게 되었습니다.

( TMI: 사실 코딩을 처음 배우던 시절에 C, C++Linked List를 구현해봤었기 때문에 포인터라는 개념이 없는 JavaScript로 구현하지 못한다고 생각하고 있었는데 최근 코드 스테이츠라는 부트캠프를 수강하면서 알게 된 분이 알려주셔서 구현하기로 마음먹었습니다. )

🫗 단방향 연결 리스트 ( Singly Linked List )

노드에 주소(포인터)를 하나만 가지고 있어서 한 방향으로만 순회가 가능한 Linked List를 의미합니다.

0️⃣ Node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
 * 단뱡향 노드
 *
 * @param _data 노드에 들어갈 데이터
 * @param _link 다음 노드 주소(포인터)
 */
class MySinglyNode<T> {
  private _data: T;
  private _link: MySinglyNode<T> | null;

  constructor(data: T) {
    this._data = data;
    this._link = null;
  }

  // getter/setter
  get data() {
    return this._data;
  }
  set data(data: T) {
    this._data = data;
  }
  get link() {
    return this._link;
  }
  set link(link: MySinglyNode<T> | null) {
    this._link = link;
  }
}

1️⃣ Singly Linked List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
/**
 * 단뱡향 연결 리스트
 *
 * @param nodes 연결될 노드들
 * @param head 첫 노드의 주소(포인터)
 * @param current 현재 노드의 주소(포인터)
 * @param tail 마지막 노드의 주소(포인터)
 */
class MySinglyLinkedList<T> {
  private nodes: MySinglyNode<T>[];

  public head: MySinglyNode<T> | null;
  public current: MySinglyNode<T> | null;
  public tail: MySinglyNode<T> | null;

  constructor(...args: T[]) {
    // 노드 생성
    this.nodes = args.map((data) => new MySinglyNode(data));

    // 노드 연결
    this.nodes.forEach((node, index, self) => {
      // 마지막 노드라면
      if (index + 1 === self.length) {
        node.link = null;
      }
      // 마지막 노드가 아니면 다음 노드 주소
      else {
        node.link = self[index + 1];
      }
    });

    // 각 포인터 결정
    this.head = this.nodes[0] || null;
    this.current = this.head;
    this.tail = this.nodes[this.nodes.length - 1] || null;
  }

  /**
   * 맨 뒤에 추가
   * @param data 맨 뒤에 추가할 데이터
   * @return linked list 길이
   */
  public append(data: T) {
    // 노드 생성
    const createdNode = new MySinglyNode(data);

    // 첫 노드라면
    if (!this.head || !this.tail) {
      // 헤드 포인터 변경
      this.head = createdNode;
      // 테일 포인터 변경
      this.tail = createdNode;
      // 현재 가리키는 포인터 변경
      this.current = this.head;
    }
    // 이전에 노드가 존재했다면
    else {
      // 현재 마지막 노드에 새로 생성한 노드 포인터 등록
      this.tail.link = createdNode;

      // 테일 포인터 변경
      this.tail = createdNode;
    }

    // 연결 리스트에 노드 추가
    this.nodes.push(createdNode);

    // 길이 반환
    return this.nodes.length;
  }

  /**
   * 맨 앞에 추가
   * @param data 맨 앞에 추가할 데이터
   * @return linked list 길이
   */
  public insert(data: T) {
    // 새로운 노드 생성
    const createdNode = new MySinglyNode(data);

    // 새로운 노드의 포인터에 현재 헤드 포인터 지정
    createdNode.link = this.head;

    // 헤드 포인터 변경
    this.head = createdNode;
    this.current = this.head;

    return this.nodes.length;
  }

  /**
   * 특정 값이 맨 앞의 노드의 값인지
   * @param data 특정 값
   * @returns T/F
   */
  public isFront(data: T) {
    return data === this.head?.data;
  }

  /**
   * 특정 노드 검색
   * @param targetData 검색할 노드의 데이터
   * @returns 검색된 노드 | null
   */
  public find(targetData: T) {
    let targetNode = this.head;

    while (targetNode) {
      if (targetNode.data === targetData) return targetNode;

      targetNode = targetNode.link;
    }

    return null;
  }

  /**
   * 특정 노드의 앞 노드 탐색
   * ( 단방향이기 때문에 앞 노드 탐색 메서드 추가 )
   * @param targetData 검색할 노드의 데이터
   * @returns 검색된 노드의 앞 노드 | null
   */
  public findBefore(targetData: T) {
    let previousNode = this.head;

    while (previousNode?.link) {
      if (previousNode.link?.data === targetData) return previousNode;

      previousNode = previousNode.link;
    }

    return null;
  }

  /**
   * 특정 노드 앞에 추가
   * ( 특정 노드가 맨 앞의 노드라면 맨 앞에 추가 ( insert() ) )
   * @param data 추가할 데이터
   * @param target 기준이 되는 노드
   * @returns 추가 여부
   */
  public insertBefore(data: T, target: T) {
    // 특정 노드가 맨 앞의 노드라면 맨 앞에 추가
    if (this.isFront(target)) {
      this.insert(data);

      return true;
    }

    // 특정 노드의 앞노드 탐색
    const previousNode = this.findBefore(target);

    // 특정 노드가 없다면
    if (!previousNode) return false;

    // 새로운 노드 생성
    const createdNode = new MySinglyNode(data);

    // 임시 저장 변수 ( previous -> created -> currnt ) 순서이기 때문에 중간에 저장할 변수가 필요
    const tempLink = previousNode.link;

    // 이전 노드 포인터에 생성한 노드 포인터 등록 ( previous -> created )
    previousNode.link = createdNode;

    // 생성한 노드 포인터에 이전 노드 포인터가 가리키던 노드 등록 ( created -> currnt )
    createdNode.link = tempLink;

    return true;
  }

  /**
   * 특정 노드 뒤에 추가
   * @param data 추가할 데이터
   * @param target 기준이 되는 노드
   * @returns 추가 여부
   */
  public insertAfter(data: T, target: T) {
    // 특정 노드 탐색
    const targetNode = this.find(target);

    // 특정 노드가 없다면
    if (!targetNode) return false;

    // 새로운 노드 생성
    const createdNode = new MySinglyNode(data);

    // 임시 저장 변수 ( previous -> created -> currnt ) 순서이기 때문에 중간에 저장할 변수가 필요
    const tempLink = targetNode.link;

    // 이전 노드 포인터에 생성한 노드 포인터 등록 ( previous -> created )
    targetNode.link = createdNode;

    // 생성한 노드 포인터에 이전 노드 포인터가 가리키던 노드 등록 ( created -> currnt )
    createdNode.link = tempLink;

    return true;
  }

  /**
   * 특정 노드 삭제
   * 제거될 노드는 가비지 컬렉터에 의해 자동 삭제 ( 접근할 모든 경우를 없앰 )
   * @param target 삭제할 노드
   * @returns 삭제 여부
   */
  public remove(target: T) {
    // 노드가 아예 없다면
    if (!this.head || !this.tail) return false;

    // 특정 노드가 맨 앞의 노드라면 맨 앞 노드 제거
    if (this.isFront(target)) {
      this.head = this.head.link;
      this.current = this.head;

      return true;
    }

    // 특정 노드의 앞 노드 탐색
    const previousNode = this.findBefore(target);
    // 특정 노드 탐색
    const currentNode = this.find(target);

    // 이전 노드 / 타겟 노드가 없는 경우
    if (!currentNode) return false;
    if (!previousNode) return false;

    // 이전 노드 포인터 변경
    previousNode.link = currentNode.link;

    return true;
  }
}

2️⃣ 사용 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// SinglyLinkedList 생성
const mySinglyLinkedList = new MySinglyLinkedList<string>("apple", "blue");

// 값 두개 추가
mySinglyLinkedList.append("color");
mySinglyLinkedList.append("delete");

// 맨 앞에 추가
mySinglyLinkedList.insert("맨 앞에 추가");
// 특정 데이터 앞에 추가
mySinglyLinkedList.insertBefore("'맨 앞에 추가' 앞에 추가", "맨 앞에 추가");

// 특정 데이터 앞에 추가
mySinglyLinkedList.insertBefore("blue 앞에 추가", "blue");
// 특정 데이터 뒤에 추가
mySinglyLinkedList.insertAfter("blue 뒤에 추가", "blue");

// 특정 데이터 제거
mySinglyLinkedList.remove("blue");

// 특정 데이터 뒤에 추가
mySinglyLinkedList.insertAfter("egg", "delete");

// 모든 요소 순회
while (mySinglyLinkedList.current) {
  console.log(mySinglyLinkedList.current.data);

  mySinglyLinkedList.current = mySinglyLinkedList.current.link;
}

/**
 * "'맨 앞에 추가' 앞에 추가"
 * "맨 앞에 추가"
 * "apple"
 * "blue 앞에 추가"
 * "blue 뒤에 추가"
 * "color"
 * "delete"
 * "egg"
 */

SinglyLinkedList 단방향 연결 리스트

🛝 양방향 연결 리스트 ( Doubly Linked List )

0️⃣ Node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
 * 양뱡향 노드
 *
 * @param _data 노드에 들어갈 데이터
 * @param _prev 이전 노드 주소(포인터)
 * @param _next 다음 노드 주소(포인터)
 */
class MyDoublyNode<T> {
  private _data: T;
  private _prev: MyDoublyNode<T> | null;
  private _next: MyDoublyNode<T> | null;

  constructor(data: T) {
    this._data = data;
    this._prev = null;
    this._next = null;
  }

  // getter/setter
  get data() {
    return this._data;
  }
  set data(data: T) {
    this._data = data;
  }
  get prev() {
    return this._prev;
  }
  set prev(prev: MyDoublyNode<T> | null) {
    this._prev = prev;
  }
  get next() {
    return this._next;
  }
  set next(next: MyDoublyNode<T> | null) {
    this._next = next;
  }
}

1️⃣ Doubly Linked List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
/**
 * 단뱡향 링크드 리스트
 *
 * @param nodes 연결될 노드들
 * @param head 첫 노드의 주소(포인터)
 * @param current 현재 노드의 주소(포인터)
 * @param tail 마지막 노드의 주소(포인터)
 */
class MyDoublyLinkedList<T> {
  private nodes: MyDoublyNode<T>[];

  public head: MyDoublyNode<T> | null;
  public current: MyDoublyNode<T> | null;
  public tail: MyDoublyNode<T> | null;

  constructor(...args: T[]) {
    // 노드 생성
    this.nodes = args.map((data) => new MyDoublyNode(data));

    // 노드 연결
    this.nodes.forEach((node, index, self) => {
      // 첫 번째 노드라면 다음 노드 주소만 등록
      if (index === 0) {
        node.prev = null;
        node.next = self[index + 1];
      }
      // 마지막 노드라면 이전 노드 주소만 등록
      else if (index + 1 === self.length) {
        node.prev = self[index - 1];
        node.next = null;
      }
      // 처음과 마지막이 아니라면 이전/다음 노드 주소 등록
      else {
        node.prev = self[index - 1];
        node.next = self[index + 1];
      }
    });

    // 각 포인터 결정
    this.head = this.nodes[0] || null;
    this.current = this.head;
    this.tail = this.nodes[this.nodes.length - 1] || null;
  }

  /**
   * 같은 노드인지 확인하는 함수
   * @param node1 비교할 노드 1
   * @param node2 비교할 노드 2 ( 사용자 정의 타입 가드 )
   * @returns 같은지 다른지 T/F
   */
  public static compareNode = <T>(
    node1: MyDoublyNode<T> | null,
    node2: MyDoublyNode<T> | null
  ): node2 is MyDoublyNode<T> => {
    // null이면 같지 않음
    if (node1 === null) return false;
    if (node2 === null) return false;

    // 본인 데이터 비교
    if (node1.data !== node2.data) return false;

    // 이전 데이터 비교
    if (node1.prev?.data !== node2.prev?.data) return false;

    // 다음 데이터 비교
    if (node1.next?.data !== node2.next?.data) return false;

    return true;
  };

  /**
   * 특정 노드 검색
   * @param targetData 검색할 노드의 데이터
   * @returns 검색된 노드 | null
   */
  find(targetData: T) {
    let targetNode = this.head;

    while (targetNode) {
      if (targetNode.data === targetData) return targetNode;

      targetNode = targetNode.next;
    }

    return null;
  }

  /**
   * 맨 뒤에 추가
   * @param data 맨 뒤에 추가할 데이터
   * @return linked list 길이
   */
  append(data: T) {
    // 노드 생성
    const createdDoublyNode = new MyDoublyNode(data);

    // 노드가 없다면
    if (!this.head || !this.tail) {
      this.head = createdDoublyNode;
      this.current = this.head;
      this.tail = createdDoublyNode;
    }
    // 첫 노드가 아니라면
    else {
      // 마지막 노드 탐색
      const lastNode = this.tail;

      // 기존 마지막 노드의 다음 포인터에 생성한 노드 주입
      lastNode.next = createdDoublyNode;

      // 생성한 노드의 이전 포인터에 기존 마지막 노드 포인터 주입
      createdDoublyNode.prev = lastNode;
    }

    // 마지막 노드 포인터 변경
    this.tail = createdDoublyNode;

    return this.nodes.length;
  }

  /**
   * 맨 앞에 추가
   * @param data 맨 앞에 추가할 데이터
   * @return linked list 길이
   */
  insert(data: T) {
    // 노드 생성
    const createdDoublyNode = new MyDoublyNode(data);

    // 노드가 없다면
    if (!this.head || !this.tail) {
      this.head = createdDoublyNode;
      this.current = this.head;
      this.tail = createdDoublyNode;
    }
    // 첫 노드가 아니라면
    else {
      // 첫 번째 노드 탐색
      const firstNode = this.head;

      // 기존 첫 번째 노드의 이전 포인터에 생성한 노드 주입
      firstNode.prev = createdDoublyNode;

      // 생성한 노드의 다음 포인터에 기존 첫 번째 노드 포인터 주입
      createdDoublyNode.next = firstNode;
    }

    // 첫 번째 노드 포인터 변경
    this.head = createdDoublyNode;
    this.current = this.head;

    return this.nodes.length;
  }

  /**
   * 특정 노드 앞에 추가
   * ( 특정 노드가 맨 앞의 노드라면 맨 앞에 추가 ( insert() ) )
   * @param data 추가할 데이터
   * @param target 기준이 되는 노드
   * @returns 추가 여부
   */
  insertBefore(data: T, target: T) {
    // 타겟 노드 탐색
    const targetNode = this.find(target);

    // 존재하는 않은 노드
    if (!targetNode) return false;

    // 노드 생성
    const createdDoublyNode = new MyDoublyNode(data);

    // 첫 번째 노드라면
    if (MyDoublyLinkedList.compareNode(targetNode, this.head)) {
      // 기존 첫 번째 노드 탐색
      const firstNode = this.head;

      // 기존 첫 번째 노드의 이전 포인터에 생성한 노드 주입
      firstNode.prev = createdDoublyNode;

      // 생성한 노드의 다음 포인터에 첫 번째 노드 주입
      createdDoublyNode.next = firstNode;

      // 헤드/현재 포인터 변경
      this.head = createdDoublyNode;
      this.current = this.head;
    }
    // 첫 번째 노드가 아닌 경우
    else {
      // 이론상은 무조건 존재하는 값 ( 코드 로직이 잘못되지 않는 한 첫 번째 노드가 아니라면 이전 노드의 값이 없을 수 없음 )
      if (!targetNode.prev) return false;

      // 타겟 노드의 이전 노드의 다음 노드 포인터에 생성한 노드 주입
      targetNode.prev.next = createdDoublyNode;

      // 생성한 노드의 이전 노드에 타겟 노드의 이전 노드 주입
      createdDoublyNode.prev = targetNode.prev;

      // 생성한 노드의 다음 노드에 타겟 노드 주입
      createdDoublyNode.next = targetNode;

      // 타겟 노드의 이전 노드에 생성한 노드 주입
      targetNode.prev = createdDoublyNode;
    }

    return true;
  }

  /**
   * 특정 노드 뒤에 추가
   * @param data 추가할 데이터
   * @param target 기준이 되는 노드
   * @returns 추가 여부
   */
  insertAfter(data: T, target: T) {
    // 타겟 노드 탐색
    const targetNode = this.find(target);

    // 존재하는 않은 노드
    if (!targetNode) return false;

    // 노드 생성
    const createdDoublyNode = new MyDoublyNode(data);

    // 마지막 노드라면
    if (MyDoublyLinkedList.compareNode(targetNode, this.tail)) {
      // 기존 마지막 노드 탐색
      const lastNode = this.tail;

      // 기존 마지막 노드의 다음 포인터에 생성한 노드 주입
      lastNode.next = createdDoublyNode;

      // 생성한 노드의 이전 포인터에 마지막 노드 주입
      createdDoublyNode.prev = lastNode;

      // 꼬리 포인터 변경
      this.tail = createdDoublyNode;
    }
    // 마지막 노드가 아닌 경우
    else {
      // 이론상은 무조건 존재하는 값 ( 코드 로직이 잘못되지 않는 한 마지막 노드가 아니라면 다음 노드의 값이 없을 수 없음 )
      if (!targetNode.next) return false;

      // 타겟 노드의 다음 노드의 이전 노드 포인터에 생성한 노드 주입
      targetNode.next.prev = createdDoublyNode;

      // 생성한 노드의 다음 노드에 타겟 노드의 다음 노드 주입
      createdDoublyNode.next = targetNode.next;

      // 생성한 노드의 이전 노드에 타겟 노드 주입
      createdDoublyNode.prev = targetNode;

      // 타겟 노드의 다음 노드에 생성한 노드 주입
      targetNode.next = createdDoublyNode;
    }

    return true;
  }

  /**
   * 특정 노드 삭제
   * 제거될 노드는 가비지 컬렉터에 의해 자동 삭제 ( 접근할 모든 경우를 없앰 )
   * @param target 삭제할 노드
   * @returns 삭제 여부
   */
  remove(target: T) {
    // 타겟 노드 탐색
    const targetNode = this.find(target);

    // 존재하는 않은 노드
    if (!targetNode) return false;

    // 노드가 하나인 경우
    if (MyDoublyLinkedList.compareNode(this.head, this.tail)) {
      this.head = null;
      this.current = this.head;
      this.tail = null;
    }
    // 첫 번째 노드라면
    else if (MyDoublyLinkedList.compareNode(targetNode, this.head)) {
      // 이전에 노드가 하나인 경우를 제외했으니 첫 번째 노드면서 타겟 노드의 다음이 없는 경우는 없음 ( TS 체커를 위해 작성 )
      if (!targetNode.next) return false;

      // 헤드/현재 포인터 변경
      this.head = targetNode.next;
      this.current = this.head;

      // 기존 첫 번째 노드 연결 끊기
      targetNode.next.prev = null;
    }
    // 마지막 노드라면
    else if (MyDoublyLinkedList.compareNode(targetNode, this.tail)) {
      // 이전에 노드가 하나인 경우를 제외했으니 마지막 노드면서 타겟 노드의 이전이 없는 경우는 없음 ( TS 체커를 위해 작성 )
      if (!targetNode.prev) return false;

      // 꼬리 포인터 변경
      this.tail = targetNode.prev;

      // 기존 마지막 노드 연결 끊기
      targetNode.prev.next = null;
    }
    // 처음과 끝이 아니라면
    else {
      if (!targetNode.next) return false;
      if (!targetNode.prev) return false;

      // 이전 노드의 다음 포인터를 다음 노드로 변경
      targetNode.prev.next = targetNode.next;

      // 다음 노드의 이전 포인터를 이전 노드로 변경
      targetNode.next.prev = targetNode.prev;
    }
  }
}

2️⃣ 사용 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const myDoublyLinkedList = new MyDoublyLinkedList(
  "apple",
  "blue",
  "color",
  "delete",
  "egg"
);

// 맨 뒤 추가
myDoublyLinkedList.append("fruits");

// 맨 앞 추가
myDoublyLinkedList.insert("zero");

// 특정 노드 앞에 추가
myDoublyLinkedList.insertBefore("color 앞에 추가", "color");

// 특정 노드 뒤에 추가
myDoublyLinkedList.insertAfter("color 뒤에 추가", "color");

// 특정 노드 제거
myDoublyLinkedList.remove("zero");
myDoublyLinkedList.remove("color");
myDoublyLinkedList.remove("fruits");

myDoublyLinkedList.insert("first");
myDoublyLinkedList.append("last");

while (myDoublyLinkedList.current) {
  console.log(myDoublyLinkedList.current.data);

  myDoublyLinkedList.current = myDoublyLinkedList.current.next;
}

/**
 * first
 * apple
 * blue
 * color 앞에 추가
 * color 뒤에 추가
 * delete
 * egg
 * last
 */

DoublyLinkedList 양방향 연결 리스트

😢 현재 구현한 Linked List의 문제점

  1. 요소를 단순 비교하기 때문에 객체나 배열은 처리할 수 없음
  2. 중복된 데이터 처리에 대한 문제
  3. isFront(), isBack() 타입 확정 문제 ( 사용자 정의 타입 가드 )

문제점은 고치지 않은 이유는 직접 사용할 목적으로 만들기 보다는 어떤 식으로 동작하는지를 알고 있다는 것과 그것을 구현하는 것에 의의를 뒀기 때문입니다.

📮 레퍼런스

  1. const P - 연결리스트 with JavaScript
  2. poiemaweb - JavaScript의 배열은 배열이 아니다
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

브라우저 이벤트

자바스크립트 완벽 가이드 9장 정리 ( Class )