링크드 리스트(Linked List)는 여러 개의 노드(Node)로 구성되어 있으며, 각 노드는 두 부분으로 이루어져 있습니다: 하나는 데이터를 저장하는 부분이고, 다른 하나는 다음 노드를 가리키는 참조(Reference) 부분입니다.
링크드 리스트를 이해하기 쉬운 예로, 기차를 생각해보세요. 기차는 여러 개의 칸으로 이루어져 있습니다. 링크드 리스트의 각 노드는 기차의 한 칸과 같습니다. 각 칸에는 승객들이 타고 있으며(데이터), 칸과 칸 사이에 연결장치(참조)가 있어서 다음 칸을 연결하고 있습니다.
링크드 리스트에는 두 가지 주요한 유형이 있습니다:
장점:
단점:
적합한 상황:
부적합한 상황:
주의사항:
class Node:
def __init__(self, data):
self.data = data
self.next = None
class LinkedList:
def __init__(self):
self.head = None
def append(self, data):
if self.head == None:
self.head = Node(data)
else:
node = self.head
while node.next:
node = node.next
node.next = Node(data)
def delete(self, data):
print('삭제대상:', data)
# 헤드가 비어있을 경우의 방어코드
if self.head == None:
print('링크드 리스트가 비어있습니다.')
return
else:
if self.head.data == data: # 헤드의 데이터가 삭제 대상인 경우
temp = self.head
self.head = self.head.next
del temp
else: # 삭제 대상이 중간에 위치하거나 마지막일 경우 아래의 코드로 처리 가능
node = self.head
while node.next:
print('다음노드:', node.next, '삭제대상:', data)
if node.next.data == data:
temp = node.next
node.next = node.next.next
del temp
print(data, ' 삭제성공')
return
else: # 삭제 대상이 아니라면 다음 노드로..
node = node.next
def print(self):
node = self.head
while node:
print(node.data)
node = node.next
def search_node(self, data):
node = self.head
while node:
if node.data == data:
return node
else:
node = node.next
linkedList = LinkedList()
for data in range(1, 10):
linkedList.append(data)
linkedList.print()
linkedList.delete(5)
linkedList.print()
더블 링크드 리스트
class Node:
def __init__(self, data, prev=None, next=None):
self.data = data
self.prev = prev
self.next = next
class DoubleLinkedList:
def __init__(self):
self.head = None
self.tail = None
def append(self, data):
if self.head == None:
self.head = Node(data)
self.tail = self.head
else:
node = self.head
while node.next:
node = node.next
newNode = Node(data)
node.next = newNode
newNode.prev = node
self.tail = newNode
def search_from_head(self, data):
node = self.head
while node:
if node.data == data:
return node
else:
node = node.next
return false
def search_from_tail(self, data):
node = self.tail
while node:
if node.data == data:
return node
else:
node = node.prev
return false
def append_before(self, data, beforeNodeData):
node = self.tail
while node.data != beforeNodeData:
node = node.prev
if node == None:
return False
newNode = Node(data)
beforeNode = node.prev
beforeNode.next = newNode
newNode.prev = beforeNode
newNode.next = node
return True
def append_after(self, data, afterNodeData):
node = self.head
while node.data != afterNodeData:
node = node.next
if node == None:
return False
afterNode = node.next
newNode = Node(data)
afterNode.prev = newNode
newNode.prev = node
newNode.next = afterNode
node.next = newNode
return True
def delete(self, data):
print('삭제대상:', data)
# 헤드가 비어있을 경우의 방어코드
if self.head == None:
return false
else:
if self.head.data == data: # 헤드의 데이터가 삭제 대상인 경우
temp = self.head
self.head = self.head.next
del temp
else: # 삭제 대상이 중간에 위치하거나 마지막일 경우 아래의 코드로 처리 가능
node = self.head
while node.next:
if node.next.data == data:
targetNode = node.next
node.next = node.next.next
del targetNode
return
else: # 삭제 대상이 아니라면 다음 노드로..
node = node.next
def print(self):
node = self.head
while node:
print(node.data)
node = node.next
def search_node(self, data):
node = self.head
while node:
if node.data == data:
return node
else:
node = node.next
doubleLinkedList = DoubleLinkedList()
for data in range(1, 10):
doubleLinkedList.append(data)
doubleLinkedList.print()
doubleLinkedList.delete(5)
doubleLinkedList.print()
doubleLinkedList.append_before(6.5, 7)
doubleLinkedList.print()
doubleLinkedList.append_after(3.5, 3)
doubleLinkedList.print()
단일 링크드 리스트
// 노드 클래스 정의
class Node {
constructor(data) {
this.data = data;
this.next = null;
}
}
// 링크드 리스트 클래스 정의
class LinkedList {
constructor() {
this.head = null;
this.tail = null;
this.size = 0;
}
// 리스트가 비어있는지 확인하는 메소드
isEmpty() {
return this.size === 0;
}
// 리스트에 노드를 추가하는 메소드
add(data) {
const node = new Node(data);
if (this.isEmpty()) {
// 리스트가 비어있을 경우
this.head = newNode; // head와 tail이 새 노드를 가리킴
this.tail = newNode;
} else {
// 리스트에 노드가 있는 경우
this.tail.next = newNode; // 현재 tail 노드가 새 노드를 가리키도록 함
this.tail = newNode; // tail을 새 노드로 업데이트
}
this.size++; // 노드 개수 증가
}
// 리스트에서 데이터를 삭제하는 메소드
remove(data) {
if (this.isEmpty()) {
throw new Error("List is empty");
}
if (this.head.data === data) {
this.head = this.head.next;
if (this.size === 1) {
this.tail = null;
}
this.size--;
return;
}
let currentNode = this.head;
let previousNode = null;
while (currentNode !== null) {
if (currentNode.data === data) {
// 마지막 요소일 경우
if (currentNode.next === null) {
this.tail = previousNode;
}
previousNode.next = currentNode.next;
currentNode = null;
this.size--;
return;
}
// 해당하지 않는다면
previousNode = currentNode;
currentNode = currentNode.next;
}
throw new Error("Data not found in the list");
}
// 리스트에서 데이터를 찾는 메소드
find(data) {
let currentNode = this.head;
while (currentNode !== null) {
if (currentNode.data === data) {
return currentNode;
}
currentNode = currentNode.next;
}
throw new Error("Data not found in the list");
}
// 리스트의 모든 노드를 출력하는 메소드
print() {
let currentNode = this.head;
let result = "";
while (currentNode !== null) {
result += currentNode.data + (currentNode.next ? " -> " : "");
currentNode = currentNode.next;
}
return result;
}
}
// 링크드 리스트 인스턴스 생성
function assert(condition, message) {
if (condition) {
console.log("✅ " + message);
} else {
console.log("❌ " + message);
}
}
const linkedList = new LinkedList();
console.log("Add=======================");
linkedList.add(10);
linkedList.add(20);
linkedList.add(30);
linkedList.add(40);
const result1 = linkedList.print();
assert(result1 === "10 -> 20 -> 30 -> 40", "Adding elements to the list");
// 중복 데이터 추가 테스트
linkedList.add(30);
const afterAddDuplicate = linkedList.print();
assert(
afterAddDuplicate === "10 -> 20 -> 30 -> 40 -> 30",
"Adding duplicate data"
);
// 리스트의 첫 번째 노드 찾기 테스트
try {
const firstNode = linkedList.find(30);
assert(firstNode.data === 30, "Finding the first node in the list");
} catch (error) {
console.log("❌ " + error.message);
}
// 리스트의 마지막 노드 찾기 테스트
try {
linkedList.find(50);
console.log("❌ Finding non-existing node");
} catch (error) {
assert(
error.message === "Data not found in the list",
"Finding non-existing node"
);
}
// 리스트에 단일 노드가 있는 경우 데이터 삭제 테스트
const singleNodeList = new LinkedList();
singleNodeList.add(100);
try {
singleNodeList.remove(100);
const afterRemoveSingleNode = singleNodeList.print();
assert(afterRemoveSingleNode === "", "Removing the single node in the list");
} catch (error) {
console.log("❌ " + error.message);
}
// 리스트 크기 확인 테스트
const sizeTestList = new LinkedList();
sizeTestList.add(1);
sizeTestList.add(2);
sizeTestList.add(3);
assert(sizeTestList.size === 3, "Checking the size of the list");
try {
linkedList.remove(20);
const afterRemove20 = linkedList.print();
assert(afterRemove20 === "10 -> 30 -> 40 -> 30", "Removing element 20");
} catch (error) {
console.log("❌ " + error.message);
}
try {
linkedList.remove(10);
const afterRemove10 = linkedList.print();
assert(afterRemove10 === "30 -> 40 -> 30", "Removing element 10");
} catch (error) {
console.log("❌ " + error.message);
}
try {
linkedList.remove(40);
const afterRemove40 = linkedList.print();
assert(afterRemove40 === "30 -> 30", "Removing tail element");
} catch (error) {
console.log("❌ " + error.message);
}
const isNotEmpty = linkedList.isEmpty();
assert(!isNotEmpty, "Checking if list is not empty");
try {
linkedList.remove(30);
const afterRemove30 = linkedList.print();
assert(afterRemove30 === "30", "Removing head element");
} catch (error) {
console.log("❌ " + error.message);
}
const result4 = linkedList.print();
assert(result4 === "30", "Printing list Checking if list is not empty");
try {
linkedList.remove(30);
const afterRemove30 = linkedList.print();
assert(afterRemove30 === "", "Removing last element");
} catch (error) {
console.log("❌ " + error.message);
}
const result5 = linkedList.print();
assert(result5 === "", "Printing list after removing last element");
const isEmpty2 = linkedList.isEmpty();
assert(isEmpty2, "Checking if list is empty");
양방향(더블) 링크드 리스트
// Node class definition
class Node {
constructor(data) {
this.data = data;
this.next = null;
this.prev = null;
}
}
// DoublyLinkedList class definition
class DoublyLinkedList {
constructor() {
this.head = null;
this.tail = null;
this.size = 0;
}
// Add data to the list
add(data) {
const newNode = new Node(data);
if (this.isEmpty()) {
this.head = newNode;
this.tail = newNode;
} else {
newNode.prev = this.tail;
this.tail.next = newNode;
this.tail = newNode;
}
this.size++;
}
// Remove data from the list
remove(data) {
if (this.isEmpty()) {
throw new Error("Empty");
}
if (this.head.data === data) {
this.head = this.head.next;
if (this.head !== null) {
this.head.prev = null;
} else {
// When there's no element left in the list
this.tail = null;
}
this.size--;
return;
}
let currentNode = this.head.next;
while (currentNode !== null) {
if (currentNode.data === data) {
if (this.tail.data === data) {
this.tail = currentNode.prev;
this.tail.next = null;
} else {
currentNode.prev.next = currentNode.next;
currentNode.next.prev = currentNode.prev;
}
this.size--;
return;
}
currentNode = currentNode.next;
}
throw new Error("Data not found in the list");
}
// Find a node with the given data in the list
find(data) {
let currentNode = this.head;
while (currentNode !== null) {
if (currentNode.data === data) {
return currentNode;
}
currentNode = currentNode.next;
}
throw new Error("Data not found in the list");
}
// Find a node with the given data in the list, starting from the tail
reverseFind(data) {
let currentNode = this.tail;
while (currentNode !== null) {
if (currentNode.data === data) {
return currentNode;
}
currentNode = currentNode.prev;
}
throw new Error("Data not found in the list");
}
// Print the list in the forward direction
print() {
let currentNode = this.head;
let result = "";
while (currentNode !== null) {
result += currentNode.data + (currentNode.next ? " <-> " : "");
currentNode = currentNode.next;
}
return result;
}
// Print the list in the reverse direction
reversePrint() {
let currentNode = this.tail;
let result = "";
while (currentNode !== null) {
result += currentNode.data + (currentNode.prev ? " <-> " : "");
currentNode = currentNode.prev;
}
return result;
}
// Check if the list is empty
isEmpty() {
return this.size === 0;
}
}
// 링크드 리스트 인스턴스 생성
function assert(condition, message) {
if (condition) {
console.log("✅ " + message);
} else {
console.log("❌ " + message);
}
}
const linkedList = new DoublyLinkedList();
console.log("Add=======================");
linkedList.add(10);
linkedList.add(20);
linkedList.add(30);
linkedList.add(40);
const result1 = linkedList.print();
assert(result1 === "10 <-> 20 <-> 30 <-> 40", "Adding elements to the list");
// 중복 데이터 추가 테스트
linkedList.add(30);
const afterAddDuplicate = linkedList.print();
assert(
afterAddDuplicate === "10 <-> 20 <-> 30 <-> 40 <-> 30",
"Adding duplicate data"
);
// 리스트의 첫 번째 노드 찾기 테스트
try {
const firstNode = linkedList.find(30);
assert(firstNode.data === 30, "Finding the first node in the list");
} catch (error) {
console.log("❌ " + error.message);
}
try {
const firstNode = linkedList.reverseFind(30);
assert(firstNode.data === 30, "Reverse finding the first node in the list");
} catch (error) {
console.log("❌ " + error.message);
}
// 리스트의 마지막 노드 찾기 테스트
try {
linkedList.find(50);
console.log("❌ Finding non-existing node");
} catch (error) {
assert(
error.message === "Data not found in the list",
"Finding non-existing node"
);
}
// 리스트에 단일 노드가 있는 경우 데이터 삭제 테스트
const singleNodeList = new DoublyLinkedList();
singleNodeList.add(100);
console.log(singleNodeList);
try {
singleNodeList.remove(100);
console.log(singleNodeList);
const afterRemoveSingleNode = singleNodeList.print();
assert(afterRemoveSingleNode === "", "Removing the single node in the list");
} catch (error) {
console.log("❌ " + error.message);
}
// 리스트 크기 확인 테스트
const sizeTestList = new DoublyLinkedList();
sizeTestList.add(1);
sizeTestList.add(2);
sizeTestList.add(3);
assert(sizeTestList.size === 3, "Checking the size of the list");
try {
linkedList.remove(20);
console.log(linkedList);
const afterRemove20 = linkedList.print();
console.log(afterRemove20);
assert(afterRemove20 === "10 <-> 30 <-> 40 <-> 30", "Removing element 20");
} catch (error) {
console.log("❌ " + error.message);
}
const reversed = linkedList.reversePrint();
console.log(reversed);
try {
linkedList.remove(10);
const afterRemove10 = linkedList.print();
assert(afterRemove10 === "30 <-> 40 <-> 30", "Removing element 10");
} catch (error) {
console.log("❌ " + error.message);
}
try {
linkedList.remove(40);
const afterRemove40 = linkedList.print();
assert(afterRemove40 === "30 <-> 30", "Removing tail element");
} catch (error) {
console.log("❌ " + error.message);
}
const isNotEmpty = linkedList.isEmpty();
assert(!isNotEmpty, "Checking if list is not empty");
try {
linkedList.remove(30);
const afterRemove30 = linkedList.print();
assert(afterRemove30 === "30", "Removing head element");
} catch (error) {
console.log("❌ " + error.message);
}
const result4 = linkedList.print();
assert(result4 === "30", "Printing list Checking if list is not empty");
try {
linkedList.remove(30);
const afterRemove30 = linkedList.print();
assert(afterRemove30 === "", "Removing last element");
} catch (error) {
console.log("❌ " + error.message);
}
const result5 = linkedList.print();
assert(result5 === "", "Printing list after removing last element");
const isEmpty2 = linkedList.isEmpty();
assert(isEmpty2, "Checking if list is empty");
재귀 함수를 이용한 단일 링크드 리스트
// 단일 링크드 리스트 노드 클래스 정의
class ListNode {
data;
next;
constructor(data) {
this.data = data; // 노드에 저장할 데이터
this.next = null; // 다음 노드를 가리키는 포인터
}
}
// 단일 링크드 리스트 클래스 정의
class LinkedList {
head;
size;
constructor() {
this.head = null; // 리스트의 첫 번째 노드를 가리킴
this.size = 0; // 리스트의 노드 개수
}
// 재귀 함수를 사용하여 노드를 추가하는 메소드
add(data, currentNode = this.head) {
if (!this.head) {
// 리스트가 비어있을 경우
this.head = new ListNode(data);
} else if (!currentNode.next) {
// 다음 노드가 없는 경우
currentNode.next = new ListNode(data);
} else {
this.add(data, currentNode.next); // 다음 노드로 이동
}
this.size++; // 노드 개수 증가
}
// 재귀 함수를 사용하여 노드를 삭제하는 메소드
remove(data, currentNode = this.head, prevNode = null) {
if (!currentNode) {
// 현재 노드가 없는 경우
return;
}
if (currentNode.data === data) {
// 삭제할 데이터를 찾았을 경우
if (prevNode) {
// 이전 노드가 있는 경우
prevNode.next = currentNode.next;
} else {
// 삭제할 노드가 head인 경우
this.head = currentNode.next;
}
this.size--; // 노드 개수 감소
} else {
// 다음 노드로 이동하며 재귀 호출
this.remove(data, currentNode.next, currentNode);
}
}
// 재귀 함수를 사용하여 리스트에서 데이터를 찾는 메소드
find(data, currentNode = this.head) {
if (!currentNode) {
// 현재 노드가 없는 경우
return null;
}
if (currentNode.data === data) {
// 찾고자 하는 데이터를 가진 노드를 찾았을 경우
return currentNode; // 해당 노드를 반환
}
// 다음 노드로 이동하며 재귀 호출
return this.find(data, currentNode.next);
}
// 재귀 함수를 사용하여 리스트의 모든 노드를 출력하는 메소드
print(currentNode = this.head, result = "") {
if (!currentNode) {
// 현재 노드가 없는 경우
console.log(result); // 결과 문자열 출력
return;
}
result += currentNode.data + (currentNode.next ? " -> " : ""); // 현재 노드의 데이터를 결과 문자열에 추가
this.print(currentNode.next, result); // 다음 노드로 이동하며 재귀 호출
}
}
// 테스트 코드
const linkedList = new LinkedList();
// 데이터 추가 테스트
linkedList.add(10);
linkedList.add(20);
linkedList.add(30);
linkedList.add(40);
// 리스트 출력: 10 -> 20 -> 30 -> 40
linkedList.print();
// 데이터 검색 테스트
console.log(linkedList.find(20)); // ListNode { data: 20, next: ListNode { data: 30, ... } }
console.log(linkedList.find(50)); // null
// 데이터 삭제 테스트
linkedList.remove(20);
linkedList.remove(10);
// 리스트 출력: 30 -> 40
linkedList.print();
// 데이터 삭제 테스트 (tail)
linkedList.remove(40);
// 리스트 출력: 30
linkedList.print();
// 데이터 삭제 테스트 (head)
linkedList.remove(30);
// 리스트 출력: (빈 리스트)
linkedList.print();