연결 리스트는 단일 연결 리스트, 이중 연결 리스트, 원형 연결 리스트로 나뉜다.
핵심 로직
① 요소 찾기
② 요소 추가
③ 요소 삭제
❗ 요소 추가나 삭제를 위해 리스트를 탐색하게 하면 복잡도가 늘어나므로 주의
다음은 단일 연결 리스트를 자바스크립트를 통해 구현한 예시이다.
// 요소(노드) 생성자가 담긴 클래스
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
// 단일 연결 리스트를 구현한 클래스
class SinglyLinkedList {
// 생성자
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
// 요소 탐색 로직
find(value) {
let currNode = this.head;
while(currNode.value !== value) {
currNode = currNode.next;
}
return currNode;
}
//첫 부분 요소 추가 로직
unshift(newValue) {
const newNode = new Node(newValue);
if(this.head === null) {
this.head = newNode;
this.tail = newNode;
}
newNode.next = this.head;
this.head = newNode;
this.length++;
return this;
}
// 끝 부분 요소 추가 로직
append(newValue) {
const newNode = new Node(newValue);
if(this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
this.tail = newNode;
}
this.length++;
}
// 중간 요소 추가 로직
insert(node, newValue) {
const newNode = new Node(newValue);
newNode.next = node.next;
node.next = newNode;
this.length++;
}
// 요소 삭제 로직
remove(value) {
let prevNode = this.head;
while(prevNode.next.value !== value) {
prevNode = prevNode.next;
}
if(prevNode.next !== null) {
prevNode.next = prevNode.next.next;
}
this.length--;
}
//첫 부분 요소 제거 로직
shift() {
if(this.head===null) return undefined;
const currNode = this.head;
this.head = currNode.next;
this.length--;
if(this.length===0) {
this.tail = null;
}
return currNode;
}
// 리스트 출력 로직
display() {
let currNode = this.head;
let displayString = "[";
while(currNode !== null) {
displayString += `${currNode.value}, `;
currNode = currNode.next;
}
displayString = displayString.substr(0, displayString.length-2);
displayString += "]";
console.log(displayString);
}
// 리스트 길이 출력 로직
size() {
console.log(this.length);
}
}
해당 코드에 대한 테스트 코드는 다음과 같다.
const linkedList = new SinglyLinkedList();
linkedList.append(1);
linkedList.append(3);
linkedList.append(8);
linkedList.unshift(192);
linkedList.append(23);
linkedList.display();
console.log(linkedList.find(3));
linkedList.remove(8);
linkedList.shift();
linkedList.display();
linkedList.size();
linkedList.insert(linkedList.find(23), 55);
linkedList.display();
linkedList.size();
핵심 로직
① 요소 찾기
② 요소 추가
③ 요소 삭제
다음은 이중 연결 리스트를 자바스크립트를 통해 구현한 예시이다.
class Node {
constructor(value) {
this.value = value;
this.next = null;
this.prev = null;
}
}
// 이중 연결 리스트를 구현한 클래스
class DoublyLinkedList {
// 생성자
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
// 요소 탐색 로직
find(index) {
if (index < 0 || index >= this.length) return null;
let count;
let currNode;
if (index <= this.length / 2) {
count = 0;
currNode = this.head;
while (count !== index) {
currNode = currNode.next;
count++;
}
} else {
count = this.length - 1;
currNode = this.tail;
while (count !== index) {
currNode = currNode.prev;
count--;
}
}
return currNode;
}
//첫 부분 요소 추가 로직
unshift(newValue) {
const newNode = new Node(newValue);
if(this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
this.head.prev = newNode;
newNode.next = this.head;
this.head = newNode;
}
this.length++;
return this;
}
// 끝 부분 요소 추가 로직
append(newValue) {
const newNode = new Node(newValue);
if(this.head === null) {
this.head = newNode;
this.tail = newNode;
} else {
this.tail.next = newNode;
newNode.prev = this.tail;
this.tail = newNode;
}
this.length++;
}
// 중간 요소 추가 로직
insert(index, newValue) {
if (index < 0 || index > this.length) return false;
if (index === this.length) return !!this.append(newValue);
if (index === 0) return !!this.unshift(newValue);
const newNode = new Node(newValue);
const beforeNode = this.find(index - 1);
const afterNode = beforeNode.next;
(beforeNode.next = newNode), (newNode.prev = beforeNode);
(newNode.next = afterNode), (afterNode.prev = newNode);
this.length++;
return true;
}
// 요소 삭제 로직
remove(index) {
if (index < 0 || index > this.length) return undefined;
if (index === 0) return this.shift();
const removedNode = this.find(index);
removedNode.prev.next = removedNode.next;
removedNode.next.prev = removedNode.prev;
removedNode.next = null;
removedNode.prev = null;
this.length--;
return removedNode;
}
//첫 부분 요소 제거 로직
shift() {
if(this.head===null) return undefined;
const currNode = this.head;
if (this.length === 1) {
this.head = null;
this.tail = null;
} else {
this.head = currNode.next;
this.head.prev = null;
currNode.next = null;
}
this.length--;
return currNode;
}
// 리스트 출력 로직
display() {
let currNode = this.head;
let displayString = "[";
while(currNode !== null) {
displayString += `${currNode.value}, `;
currNode = currNode.next;
}
displayString = displayString.substr(0, displayString.length-2);
displayString += "]";
console.log(displayString);
}
// 리스트 길이 출력 로직
size() {
console.log(this.length);
}
}
해당 코드에 대한 테스트 코드는 다음과 같다.
const linkedList2 = new DoublyLinkedList();
linkedList2.append(1);
linkedList2.append(3);
linkedList2.append(8);
linkedList2.unshift(192);
linkedList2.append(23);
linkedList2.display();
console.log(linkedList2.find(3));
linkedList2.remove(2);
linkedList2.shift();
linkedList2.display();
linkedList2.size();
linkedList2.insert(2, 55);
linkedList2.display();
linkedList2.size();
단일 혹은 이중 연결 리스트에서 Tail이 Head로 연결되는 연결 리스트
메모리를 아껴쓸 수 있다.
환형 큐 등을 만들 때도 사용된다.
맨 마지막 요소의 포인터가 맨 처음 요소를 가리키게 만드는 식으로 간단하게 수정하면 된다.
자세한 코드는 다음에 기회가 된다면 다루도록 한다.
그림 제공: https://programmers.co.kr/