크래프톤 정글
링크드리스트(LinkedList) 구현 (C언어) : Part.4 - delete_node, insert_at
고웅
2025. 4. 16. 11:49
4번째 포스팅이다. 이번 포스팅에서는 노드를 삭제하는 delete_node와 특정 인덱스 위치에 노드를 삽입하는 insert_at 기능을 구현해 보려고 한다.
1. 구현 목표
- insert_at : 링크드 리스트의 중간 원하는 인덱스에 데이터를 추가한다.
- delete_node : 원하는 값이 있는 노드를 링크드리스트에서 삭제한다.
이번 구현은 이전과 비교해 조금 복잡하게 느껴질 수 있다.
2. 기능 구현
🔨 insert_at
링크드 리스트의 특정 index 위치에 data를 가지는 node를 추가해야 한다.
구현 아이디어
- 새로운 노드를 먼저 생성해 둔다.
- 만약 index 값이 0 이라면 이 전에 구현했던 insert_front와 같이 추가를 하면 된다.
- 현재 노드를 지정할 cur 변수를 만들고 head로 지정한다.
- count라는 int 변수를 생성한다. 이 변수는 원하는 index 위치까지 반복하도록 도와준다.
- while 문을 이용해 반복하면서 count 값이 index - 1 값보다 작은 즉 index - 1 위치에 도달할 때까지 반복한다. 추가로 cur 이 NULL 이 되면 반복문을 종료하는 코드를 추가한다.
- cur이 NULL이 되었다는 것인 링크드 리스트 내부를 전부 돌았는데도 원하는 인덱스 위치를 얻지 못했다는 것이니 반환을 한다. 이때 처음에 생성했던 노드를 ‼️ free로 해제해야 한다. ‼️
- 이 모든 조건을 넘어갔다면 즉 원하는 index 위치를 찾았을 경우 새로 만든 노드의 다음 값을 cur-> next 값으로 설정한다. 즉 cur 노드와 cur->next 노드 사이에 새로운 노드를 연결한다는 것이다.
void insert_at(Node** head, int index, int data) {
Node* newNode = create_node(data);
if (newNode == NULL) {
return;
}
if (index == 0){
newNode->next = *head;
*head = newNode;
return;
}
int count = 0;
Node* cur = *head;
while (cur != NULL && count < index - 1) {
cur = cur->next;
count++;
}
// index가 리스트 길이보다 클 경우 처리
if (cur == NULL) {
printf("[insert_at] Invalid index %d: out of bounds.\n", index);
free(newNode); // 사용 안 하므로 메모리 해제
return;
}
newNode->next = cur->next;
cur->next = newNode;
}
🔨 delete_node
노드를 삭제하는 함수를 구현할 것이다.
구현 아이디어는 다음과 같다.
- 노드 삭제에는 cur 외에도 prev 라는 Node 타입의 포인터 변수를 사용한다.
- cur 값을 이용해 while 반복을 한다.
- 반복문 안에 조건문 if를 사용해 현재 노드의 data 값이 삭제하려는 data값과 같은지 검사한다.
- prev 값은 이전 노드를 지정하는 변수이다.
- 조건 문 내에 원하는 값을 찾았는데 prev 값이 NULL이라면 링크드 리스트의 가장 처음에서 발견된 것이니 링크드 리스트의 head를 cur->next로 설정한다.
- prev 가 NULL이 아니라는 것은 발견된 노드가 링크드 리스트 시작 이후에 발견된 것이니 이전 노드의 다음 값을 현재 노드의 다음 값으로 설정해 현재 노드를 가리키는 노드를 제거한다.
- 삭제 과정은 free_list 함수와 같이 temp를 이용해 진행한다.;
- free를 통해 노드의 메모리를 해제한다.
- prev의 값을 현재 노드로
- cur 노드의 값을 cur->next으로 설정한다.
void delete_node(Node** head, int data) {
if (*head == NULL) return;
Node* cur = *head;
Node* prev = NULL;
while (cur != NULL) {
if (cur->data == data) {
if (prev == NULL) {
*head = cur->next;
}else {
prev->next = cur->next;
}
Node* temp = cur;
cur->next = NULL;
free(temp);
return;
}
prev = cur;
cur = cur->next;
}
}
3. 기능 검증
이제 구현한 기능들을 검증할 시간이다. 이 전처럼 해당되는 테스트 코드의 주석을 해제하고 테스트를 하면 된다.
static char * test_delete_node() {
Node* head = NULL;
insert_end(&head, 10);
insert_end(&head, 20);
insert_end(&head, 30);
delete_node(&head, 20);
// 리스트: 10 -> 30
mu_assert("delete_node failed at head", head->data == 10);
mu_assert("delete_node failed at 2nd", head->next->data == 30);
mu_assert("delete_node failed: list too long", head->next->next == NULL);
free_list(head);
return 0;
}
static char * test_delete_from_empty_list() {
Node* head = NULL;
delete_node(&head, 10); // 아무 일도 없어야 함
mu_assert("delete_node on empty list should keep head NULL", head == NULL);
return 0;
}
static char * test_insert_at() {
Node* head = NULL;
insert_end(&head, 1);
insert_end(&head, 3);
insert_at(&head, 1, 2); // 1 -> 2 -> 3
mu_assert("insert_at failed at 1", head->data == 1);
mu_assert("insert_at failed at 2", head->next->data == 2);
mu_assert("insert_at failed at 3", head->next->next->data == 3);
free_list(head);
return 0;
}
static char * test_insert_at_out_of_bounds() {
Node* head = NULL;
insert_at(&head, 3, 100); // 인덱스 3은 잘못된 위치, 아무 일도 없어야 함
mu_assert("insert_at out of bounds should not modify list", head == NULL);
return 0;
}
static char * all_tests() {
mu_run_test(test_insert_end);
mu_run_test(test_insert_front);
mu_run_test(test_delete_node);
mu_run_test(test_find_node);
mu_run_test(test_insert_at);
mu_run_test(test_delete_from_empty_list);
mu_run_test(test_find_in_empty_list);
mu_run_test(test_insert_at_out_of_bounds);
if (tests_failed > 0) {
return "Some tests failed.";
}
return 0;
}
int tests_run = 0;
int tests_failed = 0;
int main(void) {
char *result = all_tests();
printf("========================================\n");
printf("Tests run: %d\n", tests_run);
printf("Tests passed: %d\n", tests_run - tests_failed);
printf("Tests failed: %d\n", tests_failed);
if (result != 0) {
printf("❌ TESTS FAILED\n");
return 1;
}
printf("✅ ALL TESTS PASSED\n");
return 0;
}
지금까지 구현된 테스트 코드들을 포함한 테스트 결과는 다음과 같다.
▶ Running test_insert_end...
10 20 30 NULL
✅ Passed test_insert_end (0.02 ms)
▶ Running test_insert_front...
30 20 10 NULL
✅ Passed test_insert_front (0.00 ms)
▶ Running test_delete_node...
✅ Passed test_delete_node (0.00 ms)
▶ Running test_find_node...
✅ Passed test_find_node (0.00 ms)
▶ Running test_insert_at...
✅ Passed test_insert_at (0.00 ms)
▶ Running test_delete_from_empty_list...
✅ Passed test_delete_from_empty_list (0.00 ms)
▶ Running test_find_in_empty_list...
✅ Passed test_find_in_empty_list (0.00 ms)
▶ Running test_insert_at_out_of_bounds...
[insert_at] Invalid index 3: out of bounds.
✅ Passed test_insert_at_out_of_bounds (0.00 ms)
========================================
Tests run: 8
Tests passed: 8
Tests failed: 0
✅ ALL TESTS PASSED
위와 같은 결과가 나오면 정상적으로 구현이 된 것이다.