[자료 구조 in JavaScript] 이중 연결 리스트(Doubly Linked List)
Doubly Linked List
단일 연결 리스트는 다음 노드를 가르키는 한 개의 포인터만 가지고 있기 때문에 전체 리스트를 순차적 한 방향 순회만 가능하다는 문제가 있다. 이 문제를 극복하기 위한 것이 이전 노드와 다음 노드를 가르키는 두 개의 포인터를 포함한 연결 리스트인 이중 연결 리스트이다.
단일 연결 리스트와 마찬가지로 이중 연결 리스트의 시작점을 헤드라고 한다. 마지막 노드는 테일이라고 하며 테일을 이용해 새 노드를 쉽게 삽입하고 리스트의 가장 뒤 부터 앞으로 검색할 수 있다.
Advantage of Doubly Linked Lists
이전 노드와 다음 노드를 가르키는 두 개의 포인터를 포함하고 있으므로 양방향 순회가 가능하다.
Disadvantage of Doubly Linked Lists
단일 연결 리스트와 비교해 하나의 포인터를 더 사용해야하기 때문에 메모리 소비량이 늘어나고 구현도 복잡하다.
Implemetation of Doubly Linked List
Node
이중 연결 리스트의 Node
클래스는 이전/다음 노드를 가르키는 next
, previous
프로퍼티와 data
프로퍼티로 구성된다.
class DoublyLinkedListNode {
constructor(data) {
this.data = data;
this.next = null;
this.previous = null;
}
}
Doubly Linked List
const head = Symbol("head");
const tail = Symbol("tail");
class DoublyLinkedList {
constructor() {
this[head] = null;
this[tail] = null;
}
// add(data)
// get(index)
// remove(index)
// *reverse()
}
add(data)
새로운 요소를 추가하는 메소드이다. 비어있는 이중 연결 리스트에 새로운 요소를 추가하는 경우 this[head]
과 this[tail]
이 동일한 노드로 설정된다.
class DoublyLinkedList {
add(data) {
// 새로운 노드 생성
const newNode = new DoublyLinkedListNode(data);
// 빈 리스트에 추가하는 경우
if (this[head] === null) {
this[head] = newNode;
} else {
// 현재 tail의 next가 이 새로운 노드를 가르키게 하고
// 새로운 노드의 previous가 현재의 tail을 가르키게 함
this[tail].next = newNode;
newNode.previous = this[tail];
}
// tail을 새로운 노드로 설정
this[tail] = newNode;
}
}
get(index)
특정 인덱스의 요소를 반환하는 메소드이다.
class DoublyLinkedList {
...
get(index) {
if (index > -1) {
let current = this[head];
let i = 0;
while ((current !== null) && (i < index)) {
current = current.next;
i++;
}
return current !== null ? current.data : undefined;
} else {
return undefined;
}
}
}
remove(index)
특정 인덱스의 요소를 제거하는 메소드이다.
class DoublyLinkedList {
...
remove(index) {
if ((this[head] === null) || (index < 0)) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
// special case: 첫번째 노드를 제거하는 경우
if (index === 0) {
// 현재 헤드에 대한 data를 저장
const data = this[head].data;
this[head] = this[head].next;
// special case: 노드 하나만 존재하는 경우, tail 설정
if (this[head] === null) {
this[tail] = null;
} else {
this[head].previous = null;
}
return data;
}
// 리스트를 순회하기 위한 포인터
let current = this[head];
let i = 0;
while ((current !== null) && (i < index)) {
current = current.next;
i++;
}
// 노드를 찾은 후 삭제하는 로직
if (current !== null) {
current.previous.next = current.next;
if (this[tail] === current) {
this[tail] = current.previous;
} else {
current.next.previous = current.previous;
}
return current.data;
}
// 찾지 못한 경우 에러 출력
throw new RangeError(`Index ${index} does not exist in the list.`);
}
}
Reverse iterator
리스트를 역방향으로 순회하기 위한 메소드이다.
class DoublyLinkedList {
*reverse(){
// tail을 기준으로 순회
let current = this[tail];
while (current !== null) {
yield current.data;
current = current.previous;
}
}
}
Complete Code
class DoublyLinkedListNode {
constructor(data) {
this.data = data;
this.next = null;
this.previous = null;
}
}
const head = Symbol('head');
const tail = Symbol('tail');
class DoublyLinkedList {
constructor() {
this[head] = null;
this[tail] = null;
}
add(data) {
const newNode = new DoublyLinkedListNode(data);
if (this[head] === null) {
this[head] = newNode;
} else {
this[tail].next = newNode;
newNode.previous = this[tail];
}
this[tail] = newNode;
}
get(index) {
if (index > -1) {
let current = this[head];
let i = 0;
while (current !== null && i < index) {
current = current.next;
i++;
}
return current !== null ? current.data : undefined;
} else {
return undefined;
}
}
remove(index) {
if (this[head] === null || index < 0) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
if (index === 0) {
const data = this[head].data;
this[head] = this[head].next;
if (this[head] === null) {
this[tail] = null;
} else {
this[head].previous = null;
}
return data;
}
let current = this[head];
let i = 0;
while (current !== null && i < index) {
current = current.next;
i++;
}
if (current !== null) {
current.previous.next = current.next;
if (this[tail] === current) {
this[tail] = current.previous;
} else {
current.next.previous = current.previous;
}
return current.data;
}
throw new RangeError(`Index ${index} does not exist in the list.`);
}
*reverse() {
let current = this[tail];
while (current !== null) {
yield current.data;
current = current.previous;
}
}
}
Example
이중 연결 리스트에 요소를 삽입/삭제/역방향순회/조회하는 예제이다.
const list = new DoublyLinkedList();
list.add("red");
list.add("orange");
list.add("yellow");
console.log(list.get(1)); // "orange"
// tail에서 head 방향으로 순회
for (const color of list.reverse()) {
console.log(color);
}
console.log(list.remove(1)); // "orange"
console.log(list.get(1)); // "yellow"
// output:
orange
yellow
orange
red
orange
yellow
Reference
Author And Source
이 문제에 관하여([자료 구조 in JavaScript] 이중 연결 리스트(Doubly Linked List)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@iwaskorean/자료-구조-in-JavaScript-1.-이중-연결-리스트Doubly-Linked-List저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)