안녕하세요? 주말에 공부 안 했다가 발등에 불똥이 떨어져 버린 사람입니다. 잔말 없이 빨리 시작해 보겠습니당나귀. 바빠서 비유 같은 걸 생각하지를 못했네요. 흑흑. 내일은 꼭 시간이 많길 바랍니다.
요번부터는 목차가 생깁니다. 왜냐구요. 정리할 게 많기 때문입니다. 쓰다가 헷갈리지 않게 써 봅니다.
연결 리스트(linked list)란?
배열 리스트와 연결 리스트의 장단점
연결 리스트의 특징
연결 리스트의 종류
실생활과 닿아 있는 연결 리스트?
연결 리스트의 method
수도 코드
코드 구현
이름... 그대로입니다. 링크 목록
이라는 뜻인데요.(...) 각 데이터들을 한 줄로 연결시킨 모양을 띄우고 있는 자료 구조
입니다. 여기서 왜 링크라는 게 나오냐면, 그 데이터들을 링크로 연결
을 시키기 때문입니다. 네.
연결 리스트의 구조를 그림으로 한번 볼까요?
(한방향 리스트)
오늘은 좀 잘 그린 것 같네요. ㅎㅎ 보통 노드를 그릴 때 일자형으로 그리더라구요. 그런데 저는 이게 조금 더 이해가 가서 이렇게 그려 보았습니다. 그림을 차근차근 정리해 봅시다.
연결 리스트에서 데이터와 링크를 가지고 있는 박스를(크기가 동적인 자료구조로, 자료구조를 구성하는 요소
)노드
라고 합니다. 노드는 데이터만 가지고 있을 수도, 링크만 가지고 있을 수도 없는, 걍 (원소 자신인)데이터와 링크 한 세트
라고 보시면 됩니다. 보시는 것과 같이 노드의 링크
는 그 다음 데이터를 가리키고 있는데요
, 링크를 통해
서 데이터를 추가
하고, 삭제
하며 탐색
도 가능합니다. 같이 있는 게 아닙니다! 데이터를 저장할 장소
와 다음 원소를 가리키기 위한 장소
가 구분되어 있습니다.
배열과 유사하죠? 그래서 배열과 비교를 많이 하는데요,
배열은 arr = [1, 2, 3, 4]
형태로 가지고 있는 거 아시죠? 요 배열 리스트와 연결 리스트는 아주 유사하지만, 링크라는 것으로 아주 크게 차이가 나는데요.
배열 리스트
가장 간단한 메모리 데이터 구조이다.
(장) 동일한 데이터 타입을 연속적으로 저장할 수 있다.
(장) 간단하고, 사용이 쉬우며 데이터를 참조하기 쉽다.
(단) 고정된 크기(JS 제외)를 가지고 있어서 배열의 처음이나 중간에서 원소를 넣고 빼려면 비싼 연산을 빈번하게 해야 한다.
연결 리스트
일련의 원소를 배열처럼 차례대로 저장하지만 원소들이 메모리상에 연속적으로 위치하지 않는다. 노드로 구현된 리스트이다. 링크 하나에 4 byte 메모리를 차지하며, 더블 링크드일 경우엔 노드 하나에 8 byte의 링크 메모리를 차지한다.
(장) 배열에 비해 데이터의 추가 및 삽입이 용이하다.
(장) 배열보다 메모리를 효율적으로 쓸 수 있다.
(단) 특정 위치의 데이터를 검색하기 위해서는 처음부터 끝까지 순회해야 하기 때문에 탐색에 비효율적임.
탐색과 정렬을 자주 한다면 배열 리스트
추가와 삭제가 많다면 연결 리스트
위에서 다 설명한 것 같지만... 다시 한 번만 적어 봅니다.
그리고 이건 제가 구현을 하면서 느낀 건데, 연결 리스트의 그림과 구현 코드가 살짝(그런데 체감으로는 아주 많이임) 달랐습니다. 일단 헤드에 연결을 전부 해 놓구요, 테일에는 제일 마지막 데이터만 걸어 주었습니다.
요런 닉김이었어요. 이거 보니까 진짜 동물이 떠오르긴 하네요.
그림 안 그리려고 했는데..(ㅠㅠ)
아무튼 이렇게, 달려 있긴 하지만 꼬리가 몸통은 아니잖아요? 이런 느낌이긴 한데, 역시 개발자는 코드죠. 코드로 확인하면 제가 말한 느낌이 팍 옵니다.
head: {0: 'apple', next: {1: 'banana', next: {2: 'dream', next: null}}}}
tail: {2: 'dream', next: null}
어떤 모습인지 아시겠나요? 뜨흐흑.
조금 더 자세한 그림이 담긴 유익 블로그도 추가합니다.
https://medium.com/@lyhlg0201/immersive-sprint-js-linkedlist-4edcb5928a9e
요 그림은 데이터를 삭제할 때
의 모습인데요, data2를 삭제해 주는 것이 아닌
, data 1의 링크
를 2가 아닌 3으로 다시 연결
해 주면 data 2는 head에서 자동으로 탈락
되어 사라지게 됩니다. 추가
도 마찬가지겠지요? data1의 링크를 3이 아닌 넣으려고 하는 데이터에 연결해 주고, 넣으려고 하는 데이터의 포인터를 3으로 연결
해 주면 됩니다.
* 모든 리스트의 코드를 구현하지는 않습니다!
지금까지 말했던 게 바로 일방향 연결 리스트입니다. 일방향 연결 리스트는 노드에 포인터(다음 노드를 가리키는 링크)가 한 개인 연결 리스트인데요, 이 리스트의 장점은 노드당 포인터 데이터를 4 바이트만 차지해서 이중 연결 리스트보다 데이터를 아낄 수 있다는 점입니다. 다만 일방향이기 때문에 현재 노드는 이전 노드로 돌아가는 법을 모릅니다. 오직 직진만이 있을 뿐.
노드에 포인터가 두 개 있는 이중 연결 리스트인데요, 일방향 연결 리스트와 똑같지만 앞서 말한 것처럼 노드에 포인터가 두 개 있다는 점이 다른데요, 이 두 개의 포인터는 전과 후를 가리키고 있기 때문에 일방향과는 달리 이전으로 갈 수도, 이후로도 갈 수도 있습니다. 포인터가 두 개가 생겼으니 데이터도 두 배로 먹겠죠. 8 바이트를 차지합니다.
연결 리스트에 있는 헤드와 테일이 붙여져 있어, 원의 모양과 같다고 하여 환형 리스트인데요. 테일의 포인터가 null을 가리키고 있는 것이 아닌 head를 가리키고 있는 것입니다. 노드 6 번에 있다가 4 번으로 가야 된다면, 일방향 리스트는 다시 시작해야 되지만 환형 리스트는 7, 8, 9, 10을 지나 다시 0, 1, 2, 3, 4로 갈 수 있겠죠? 순회할 때 좋은 연결 리스트입니다.
실생활과 닿아 있기 전, 연결 리스트와 유사한 비유가 하나 떠올랐어요. 지하철역입니다.
일방향으로 가고, 역에 도착할 때마다 다음 역을 말해 주는 게 연결 리스트와 닮아 있습니다. 음, 또, 2호선 같은 경우엔 환형으로 되어 있는 연결 리스트네요. 그냥 갑자기 떠올라서 말씀을 드렸습니다. 하핫. 런닝맨도 있겠네요. 뭐 하나 찾으면 어디 가라고 지시 써 있고, 뭐 하나 찾으면 어디 가라고 지시 써 있고.
우리가 사용하고 있는 것에 연결 리스트가 숨어 있습니다.
멜론 등 음악을 듣는 플랫폼을 사용할 때, 다음 곡이나 이전 곡으로 갈 수 있는 것들
에도 연결 리스트를 사용하고, 포토샵처럼 ctrl+z로 했던 것을 지우거나 ctrl+y로 다시 나타내는 것
을 할 수도 있겠구요, 이미지 뷰어처럼 다음 이미지, 이전 이미지를 볼 수 있게
할 수도 있겠습니다.
* 어디서든 추가와 삭제가 가능하지만 끝에만 추가하는 것으로 구현해 봅니다.
addToTail(value): 연결 리스트의 마지막에 데이터를 추가합니다.
getNodeAt(index): 인덱스를 넣었을 때, 그 인덱스가 어떠한 값을 가지고 있는지 반환합니다.
contains(value): 해당 값이 연결 리스트에 있는지 true와 false로 반환합니다.
indexOf(value): 해당 값이 어떤 인덱스에 있는지, 인덱스 값과 -1로 반환합니다.
remove(value): 해당하는 연결 리스트의 값을 지웁니다.
size(): 연결 리스트의 사이즈를 반환합니다.
실제 코드로 구현하기에 앞서 수도 코드로 작성해 보겠습니다.
Node의 생성자 함수를 따로 구현했습니다.
Linked-list의 구현 함수에는 constructor로 head와 tail, size가 있습니다.
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
class LinkedList {
constructor() {
this.head = head;
this.tail = tail;
this._size = 0;
}
addToTail(value) {
let newNode = Node();
if(!this.head) {
this.head = newNode;
this.tail = newNode;
} else{
this.tail.next = newNode;
this.tail = newNode;
}
this._size++;
return this;
}
getNodeAt(index) {
let curNode = this.head;
if(index > this._size) return undefined;
for(let i = 0; i < index; i++) {
curNode = curNode.next;
}
return curNode;
}
contains(value) {
let curNode = this.head;
while(curNode) {
if(curNode.value === value) {
return true;
}else {
curNode = curNode.next;
}
}
return false;
}
indexOf(value) {
let curNode = this.head;
let indexCount = 0;
while(curNode) {
if(curNode.value === value) {
return indexCount;
}else{
indexCount++;
curNode = curNode.next;
}
}
return -1;
}
remove(value) {
if(!this.head) return undefined;
if(this.head.value === value) {
this.head = this.head.next;
return this;
}
let prevNode = this.head;
let curNode = this.head.next;
while(curNode) {
if(curNode.value === value) {
break;
}else {
prevNode = curNode;
curNode = curNode.next;
}
}
prevNode.next = curNode.next;
this._size--;
return this;
}
size() {
return this._size;
}
JS 코드 다시 쓰는 거 정말 힘들고 공부 많이 된다 흑흑