floatFirstTOC: right
🖥️ 들어가며순차 리스트 (Sequential List)연결 리스트 (Linked List)노드의 구조연결 리스트 구현C구현해야 하는 함수들isEmptycreateNodeInsertNode 1. 맨 앞에 새로운 노드 삽입2. 맨 뒤에 새로운 노드 삽입3. 중간에 새로운 노드 삽입insertNode 코드 전문deleteNode 1. 처음 위치의 노드 제거2. 그 외 위치의 노드 제거printList코드 전문개선된 코드 전문Python코드 전문
🖥️ 들어가며
리스트는 프로그래밍에서 매우 중요한 자료 구조 중 하나입니다. 이는 순서가 있는 데이터의 집합을 관리하기 위한 방법으로, 데이터를 효율적으로 저장하고 검색, 수정, 삭제하는 작업을 수행할 수 있도록 돕습니다. 리스트는 크게 두 가지 유형으로 나눌 수 있습니다.
순차 리스트 (Sequential List)
- 순차 리스트는 가장 기본적인 형태의 리스트로, 배열을 기반으로 구현됩니다. 배열의 인덱스를 사용하여 데이터에 접근하기 때문에 특정 위치의 데이터를 빠르게 찾거나 읽어올 수 있습니다. 이러한 특성 덕분에 생성 및 데이터 검색(또는 출력) 작업은 순차 리스트에서 매우 효율적으로 이루어집니다.
- 그러나 순차 리스트는 데이터의 삽입이나 삭제가 비효율적입니다. 특히 리스트의 중간에서 삭제나 삽입 연산을 수행할 경우, 이후의 모든 데이터를 이동시켜야 하기 때문에 상당한 비용이 소모됩니다.
연결 리스트 (Linked List)
- 연결 리스트는 순차 리스트의 이러한 단점을 극복하기 위해 개발된 구조입니다.
- 연결 리스트에서는 데이터를 메모리의 여러 위치에 동적으로 할당하고, 각 데이터(노드)는 다음 데이터의 위치 정보를 포함합니다. 이 위치 정보는 보통 포인터를 사용하여 다음 노드를 가리킵니다.
- 연결 리스트와 순차 리스트는 대조되는 장단점을 가지기 때문에, 연결 리스트는 노드 삽입 및 삭제가 용이하고 데이터 구조의 크기를 유연하게 조정할 수 있다는 장점을 가집니다.
노드의 구조
노드는 연결 리스트의 기본 단위로, 두 부분으로 구성됩니다.
- 데이터를 저장하는
item 필드
- 다음 노드의 주소를 저장하는
next 필드
이 노드를 여러 개 이어 붙이면, 아래 그림과 같이 됩니다.
이런 구조 덕에 연결 리스트는 메모리가 허용하는 만큼 계속 확장할 수 있습니다.
HEAD 포인터
첫 번째 노드의 주소를 가리키는 포인터라고 생각하면 됩니다.
데이터가 들어있다고 가정하면 아래와 같은 그림이 됩니다.
물론 예시를 들었을 뿐 메모리가 항상 이렇게 연속적이지는 않습니다.
연결 리스트 구현
C
가장 통상적인 방법은 구조체를 활용하는 방안입니다.
노드 구조체를 선언해 안쪽에
next 필드
와 item 필드
를 선언하고 이를 계속 재사용하는 방식입니다.typedef struct node { int item; struct node *next; } Node;
구현해야 하는 함수들
isEmpty
노드가 비어있는지 확인합니다.
HEAD
포인터가 NULL이면 비어있다고 간주합니다.int isEmpty(Node *head) { if (head == NULL) return 1; else return 0; }
createNode
노드를 생성합니다. 동적으로 생성하기 위해
malloc
을 사용합니다.Node *createNode(int item) { Node *newNode = (Node *)malloc(sizeof(Node)); // 메모리 할당 실패 if (newNode == NULL) return -1; newNode->item = item; newNode->next = NULL; return newNode; }
InsertNode
노드를 삽입합니다. 노드를 삽입하는 데는 3가지 경우가 생길 수 있습니다.
- 맨 앞에 새로운 노드 삽입
- 맨 뒤에 새로운 노드 삽입
- 중간에 새로운 노드 삽입
우선 위 작업을 수행하기 위해서는, 기초 작업이 필요합니다.
Node *newnode = createNode(item); Node *pre_node = *head; // 이전 노드 Node *pos_node = *head; // 현재 노드 for (int count = 0; count < pos - 1; count++) pre_node = pre_node->next; for (int count = 0; count < pos; count++) pos_node = pos_node->next;
중요한 부분은
for
문 구간입니다. 쉽게 그림으로 설명하겠습니다.아래 그림에서는
pos
가 3일 경우를 예시로 들었습니다.- 이 아이디어는
2. 맨 뒤에 새로운 노드 삽입
과3. 중간에 새로운 노드 삽입
를 위한 아이디어입니다. - 맨 뒤에 넣을 때는 맨 뒤 노드가
pos
가 됩니다. - 중간에 넣을 때는 넣고 싶은 위치의 노드가
pos
, 그 앞 노드가pre
가 되므로 서로의Next 필드
를 연결할 수 있습니다.
1. 맨 앞에 새로운 노드 삽입
비교적 간단합니다.
- 새로운 노드의
NEXT
필드를 현재HEAD
포인터가 가리키는 주소로 변경합니다.
- 현재
HEAD
포인터를 새로 추가한 노드로 변경합니다.
코드상으로는 다음과 같습니다.
if (pos == 0) { // 맨 앞에 삽입 newnode->next = *head; *head = newnode; }
2. 맨 뒤에 새로운 노드 삽입
- 맨 뒤의 노드의
Next 필드
를 새로운 노드를 가리키도록 합니다.
코드는 다음과 같습니다.
else if (pos == NULL) { // 맨 뒤에 삽입 pos_node->next = newnode; }
3. 중간에 새로운 노드 삽입
- 새로 생성한 노드의
Next 필드
가pos_node
를 가리키도록 합니다.
pre_node
의Next 필드
가 새로운 노드를 가리키도록 합니다.
코드는 다음과 같습니다.
else { // 중간에 삽입 newnode->next = pos_node; pre_node->next = newnode; }
insertNode
코드 전문
void insertNode(Node **head, int item, int pos) { Node *newnode = createNode(item); Node *pre_node = *head; // 이전 노드 Node *pos_node = *head; // 현재 노드 for (int count = 0; count < pos - 1; count++) pre_node = pre_node->next; for (int count = 0; count < pos; count++) pos_node = pos_node->next; if (pos == 0) { // 맨 앞에 삽입 newnode->next = *head; *head = newnode; } else if (pos == NULL) { // 맨 뒤에 삽입 pos_node->next = newnode; } else { // 중간에 삽입 newnode->next = pos_node; pre_node->next = newnode; } }
deleteNode
노드를 추가도 했으니, 삭제도 할 수 있어야 진정한 연결리스트라 할 수 있을 것입니다.
노드의 삽입 과정은 3개의 분기가 있었지만 다행히 삭제는 2개의 분기만 고려하면 됩니다.
우선 초기 설정입니다.
insertNode
와 거의 유사합니다.Node *deleted_node; Node *pre_node = *head; Node *pos_node = *head; for (int count = 0; count < pos - 1; count++) pre_node = pre_node->next; for (int count = 0; count < pos; count++) pos_node = pos_node->next;
1. 처음 위치의 노드 제거
간단합니다. 그냥
HEAD
포인터가 삭제할 노드의 다음 노드를 가리키도록 합니다.
- 삭제할 노드는
free
합니다.
코드는 다음과 같습니다.
if (pos == 0) { // 맨 앞 삭제 deleted_node = *head; *head = (*head)->next; free(deleted_node); }
2. 그 외 위치의 노드 제거
pre_node
의next
를pos_node
의next
로 변경합니다.
- 삭제할 노드를
free
합니다.
코드는 다음과 같습니다.
else { // 중간 & 맨 뒤 삭제 deleted_node = pos_node; pre_node->next = pos_node->next; free(deleted_node); }
printList
모든 노드를 순회하며 값을 출력합니다.
void printList(Node *head) { Node *current = head; while (current != NULL) { printf("%d -> ", current->item); current = current->next; } printf("NULL\n"); }
코드 전문
#include <stdio.h> #include <stdlib.h> typedef struct node { int item; struct node *next; } Node; int isEmpty(Node *head) { if (head == NULL) return 1; else return 0; } Node *createNode(int item) { Node *newNode = (Node *)malloc(sizeof(Node)); // 메모리 할당 실패 if (newNode == NULL) return NULL; newNode->item = item; newNode->next = NULL; return newNode; } void insertNode(Node **head, int item, int pos) { Node *newnode = createNode(item); Node *pre_node = *head; // 이전 노드 Node *pos_node = *head; // 현재 노드 for (int count = 0; count < pos - 1; count++) pre_node = pre_node->next; for (int count = 0; count < pos; count++) pos_node = pos_node->next; if (pos == 0) { // 맨 앞에 삽입 newnode->next = *head; *head = newnode; } else if (pos == NULL) { // 맨 뒤에 삽입 pos_node->next = newnode; } else { // 중간에 삽입 newnode->next = pos_node; pre_node->next = newnode; } } void deleteNode(Node **head, int pos) { Node *deleted_node; Node *pre_node = *head; Node *pos_node = *head; for (int count = 0; count < pos - 1; count++) pre_node = pre_node->next; for (int count = 0; count < pos; count++) pos_node = pos_node->next; if (pos == 0) { // 맨 앞 삭제 deleted_node = *head; *head = (*head)->next; free(deleted_node); } else { // 중간 & 맨 뒤 삭제 deleted_node = pos_node; pre_node->next = pos_node->next; free(deleted_node); } } void printList(Node *head) { Node *current = head; while (current != NULL) { printf("%d -> ", current->item); current = current->next; } printf("NULL\n"); } int main() { Node *head = NULL; // 노드 삽입 insertNode(&head, 10, 0); insertNode(&head, 20, 1); insertNode(&head, 30, 2); printList(head); // 출력: 10 -> 20 -> 30 -> NULL // 노드 삭제 deleteNode(&head, 1); // 20 삭제 printList(head); // 출력: 10 -> 30 -> NULL // 더 많은 노드 삽입 insertNode(&head, 40, 2); // 30 뒤에 40 삽입 insertNode(&head, 50, 1); // 10 뒤에 50 삽입 printList(head); // 출력: 10 -> 50 -> 30 -> 40 -> NULL // 프로그램 종료 전 모든 노드 메모리 해제 while (!isEmpty(head)) { deleteNode(&head, 0); } return 0; }
개선된 코드 전문
#include <stdio.h> #include <stdlib.h> typedef struct node { int item; struct node *next; } Node; int isEmpty(Node *head) { return head == NULL; } Node *createNode(int item) { Node *newNode = (Node *)malloc(sizeof(Node)); if (newNode == NULL) { printf("메모리 할당 실패\n"); return NULL; } newNode->item = item; newNode->next = NULL; return newNode; } void insertNode(Node **head, int item, int pos) { Node *newNode = createNode(item); if (newNode == NULL) { return; } if (pos == 0) { // 맨 앞에 삽입 newNode->next = *head; *head = newNode; } else { Node *current = *head; for (int i = 0; i < pos - 1 && current != NULL; i++) { current = current->next; } if (current == NULL) { printf("위치 오류: 리스트 길이보다 큰 위치입니다\n"); free(newNode); } else { newNode->next = current->next; current->next = newNode; } } } void deleteNode(Node **head, int pos) { if (*head == NULL) { printf("리스트가 비어 있습니다.\n"); return; } Node *deletedNode; if (pos == 0) { // 맨 앞 삭제 deletedNode = *head; *head = (*head)->next; } else { Node *current = *head; for (int i = 0; i < pos - 1 && current->next != NULL; i++) { current = current->next; } if (current->next == NULL) { printf("위치 오류: 리스트 길이보다 큰 위치입니다\n"); return; } deletedNode = current->next; current->next = current->next->next; } free(deletedNode); } void printList(Node *head) { Node *current = head; while (current != NULL) { printf("%d -> ", current->item); current = current->next; } printf("NULL\n"); } int main() { Node *head = NULL; // 노드 삽입 insertNode(&head, 10, 0); insertNode(&head, 20, 1); insertNode(&head, 30, 2); printList(head); // 출력: 10 -> 20 -> 30 -> NULL // 노드 삭제 deleteNode(&head, 1); // 20 삭제 printList(head); // 출력: 10 -> 30 -> NULL // 더 많은 노드 삽입 insertNode(&head, 40, 2); // 30 뒤에 40 삽입 insertNode(&head, 50, 1); // 10 뒤에 50 삽입 printList(head); // 출력: 10 -> 50 -> 30 -> 40 -> NULL // 프로그램 종료 전 모든 노드 메모리 해제 while (!isEmpty(head)) { deleteNode(&head, 0); } return 0; }
Python
파이썬도 대충 원리는 비슷합니다. 대신 C언어와 다르게
free
작업을 하지 않아도 되기에 조금 더 간결하게 작성할 수 있습니다.코드 전문
class Node: """노드 클래스. 연결 리스트의 기본 요소.""" def __init__(self, item): self.item = item # 노드가 저장할 데이터 self.next = None # 다음 노드를 가리키는 참조, 초기에는 None class LinkedList: """연결 리스트 클래스. 노드들을 연결하여 리스트를 구현.""" def __init__(self): self.head = None # 리스트의 첫 번째 노드를 가리킴 def insert(self, item, pos): """주어진 위치에 새 노드를 삽입.""" new_node = Node(item) # 새 노드 생성 if pos == 0: # 맨 앞에 삽입하는 경우 new_node.next = self.head self.head = new_node else: # 중앙이나 맨 뒤에 삽입하는 경우 prev = None current = self.head current_pos = 0 while current_pos < pos and current is not None: prev = current current = current.next current_pos += 1 if current_pos < pos: raise IndexError("위치 오류: 리스트 길이보다 큰 위치입니다") if current is None: # 맨 뒤에 삽입하는 경우 prev.next = new_node else: # 중간에 삽입하는 경우 new_node.next = current prev.next = new_node def delete(self, pos): """주어진 위치의 노드를 삭제.""" if self.head is None: raise ValueError("List is empty") if pos == 0: # 맨 앞의 노드를 삭제하는 경우 self.head = self.head.next else: prev = None current = self.head current_pos = 0 while current_pos < pos and current.next is not None: prev = current current = current.next current_pos += 1 if current.next is None and current_pos < pos: raise IndexError("위치 오류: 리스트 길이보다 큰 위치입니다") prev.next = current.next def __iter__(self): """반복자 메서드. 연결 리스트를 순회할 수 있게 해줌.""" current = self.head while current: yield current.item current = current.next def __str__(self): """연결 리스트를 문자열로 표현하여 출력. 예: 10 -> 20 -> 30 -> NULL""" return " -> ".join(str(item) for item in self) + " -> NULL" # 사용 예시 if __name__ == "__main__": ll = LinkedList() ll.insert(10, 0) ll.insert(20, 1) ll.insert(30, 2) print(ll) # 출력: 10 -> 20 -> 30 -> NULL ll.delete(1) print(ll) # 출력: 10 -> 30 -> NULL ll.insert(40, 2) ll.insert(50, 1) print(ll) # 출력: 10 -> 50 -> 30 -> 40 -> NULL
댓글