크래프톤 정글 8기 - TIL 31일차 (링크드 리스트 - C)
TIL - 2025.04.10 (목요일)
📝 오늘 배운 것: C로 구현하는 링크드 리스트
4월 10일, 크래프톤 정글에 입소한 지 벌써 1달이 되었다.
1~4주 차까지는 알고리즘 위주로 학습했고, 이제부터는 C 언어로 자료구조를 구현하거나 문제를 풀어보는 활동을 4주간 진행할 예정이다.
링크드 리스트란?
링크드 리스트(Linked List)는 데이터를 순차적으로 저장하는 자료구조로, 각 요소가 포인터를 통해 다음 요소와 연결되어 있다.
배열과는 달리 메모리상에서 연속된 공간에 있지 않으며, 노드(Node)라는 단위로 구성된다.
노드는 보통 두 가지 요소를 가진다:
- 데이터(Data): 실제 저장 값
- 포인터(Next): 다음 노드의 주소 (또는 참조)
[1 | ●] → [2 | ●] → [3 | null]
🔹 링크드 리스트의 종류
종류 | 설명 |
단일 연결 리스트 | 한 방향으로만 연결됨 (next만 존재) |
이중 연결 리스트 | 앞뒤로 연결됨 (prev, next 모두 존재) |
원형 연결 리스트 | 마지막 노드가 첫 노드를 가리킴 |
🔹 꼭 알아야 할 개념들
개념 | 설명 |
---|---|
노드(Node) | 데이터와 다음 노드에 대한 참조를 가진 기본 단위 |
헤드(Head) | 리스트의 첫 번째 노드 |
포인터(Reference) | 다음 노드를 가리키는 변수 |
삽입/삭제 | 중간에 노드를 추가하거나 제거할 수 있음 (배열보다 효율적) |
순회(Traversal) | 처음부터 끝까지 차례로 노드를 따라가며 처리 |
Null 체크 | 마지막 노드의 다음은 null 또는 None임 |
구현 예시: insertSortedLL
구현 목표
정글에서 제공해 준 과제 리스트? 에 있는 Q1_A_LL은 insertSortedLL을 구현해야 하는 과제다. 이 insertSortedLL은 링크드 리스트에 입력되는 값이 정렬이 되어 입력이 되어야 한다.
문제 설명
insertSortedLL()라는 C 함수를 작성하시오.
- 이 함수는 사용자에게 정수를 입력받아, 해당 값을 오름차순으로 정렬된 연결 리스트에 삽입한다.
- 함수의 동작 요건:
- 사용자가 입력한 정수를 정렬된 연결 리스트에 오름차순으로 삽입한다.
- 중복된 값은 삽입하지 않는다.
- 삽입이 성공하면, 삽입된 위치의 인덱스(index)를 반환해야 한다.
- 삽입에 실패한 경우(예: 중복 또는 메모리 문제)에는 -1을 반환해야 한다.
- 연결 리스트는 항상 정렬된 상태이거나 비어 있는 상태라고 가정할 수 있다.
주요 로직 설명
ListNode *cur = ll->head; // 현재 탐색 위치
ListNode *prev = NULL; // 삽입을 위한 이전 노드
int index = 0;
while (cur != NULL && cur->item < item) {
prev = cur;
cur = cur->next;
index++;
}
- 리스트를 순회하며 item이 들어갈 위치를 탐색
- 중복된 값이 있는 경우 아래 조건에서 바로 종료
위에서부터 설명하면 prev 즉 이전 노드가 cur 노드 값으로 설정된다. 이후 cur 노드는 cur 노드의 next 즉 다음 연결된 노드로 이동을 하게 된다. 이후 문제 요구사항에 있던 인덱스 값 반환을 위해 index 값을 1 증가한다.
// 중복 탐지 시 -1 반환
if (cur != NULL && cur->item == item) {
printf("❌ Duplicate detected: %d already exists at index %d\n", item, index);
return -1;
}
이 때 cur 값이 item 값보다 커지면 while 문의 중지 조건에 걸려 반복을 그만두게 된 상태에서 현재 노드의 item 값이 삽입하려는 item 값과 같은지 검사한다. 이때 같은 값 즉 기존 값과 중복된 값이 들어온 경우 요구 조건에 맞게 -1을 반환하며 종료한다.
위 GIF 가 중복된 값이 들어왔을 때의 동작 예시이다.
새로운 노드 삽입 과정
값이 중복되지 않았을 경우 아래와 같은 코드가 실행된다.
ListNode *newNode = (ListNode *) malloc(sizeof(ListNode));
if (newNode == NULL) return -1;
newNode->item = item;
newNode->next = cur;
- 새 노드를 생성하고 값을 할당
- 새 노드가 가리킬 다음 노드는 현재 위치(cur)
if (prev == NULL)
ll->head = newNode; // 맨 앞에 삽입
else
prev->next = newNode; // 중간 또는 맨 뒤에 삽입
- prev가 NULL이면 맨 앞(head)에 삽입
- 그렇지 않으면 prev 뒤에 삽입
전체 코드
ListNode *cur = ll->head; // 탐색용
ListNode *prev = NULL; // 삽입 시 사용
int index = 0; // 문제 요구 사항에 should return the index 가 있었다.
printf("\n[INSERT] Trying to insert %d into linked list...\n", item);
// 삽입 위치 탐색
while (cur != NULL && cur->item < item) {
prev = cur;
cur = cur->next;
index++;
}
// 중복 탐지 시 -1 반환
if (cur != NULL && cur->item == item) {
printf("❌ Duplicate detected: %d already exists at index %d\n", item, index);
return -1;
}
ListNode *newNode = (ListNode *) malloc(sizeof(ListNode)); // 메모리 동적 할당
// 메모리 할당에 실패 한 경우 -1 반환
if (newNode == NULL) {
printf("❌ Memory allocation failed for %d\n", item);
return -1;
}
newNode->item = item;
newNode->next = cur;
if (prev == NULL) {
printf("🔗 Inserting %d at the head\n", item);
ll->head = newNode;
} else {
printf("🔗 Inserting %d after %d\n", item, prev->item);
prev->next = newNode;
}
ll->size++;
printf("✅ Inserted %d at index %d | New size: %d\n", item, index, ll->size);
// 현재 리스트 상태 출력 (없어도 무방함)
printf("📌 Current list: ");
ListNode *temp = ll->head;
while (temp != NULL) {
printf("%d ", temp->item);
temp = temp->next;
}
printf("\n");
return index;
😅 트러블 슈팅
prev와 cur등 포인터 변수를 이용해서 반복문을 돌리는 것에서 접근 방식을 떠올리는데 애를 먹었다. 또한 prev->next에 newNode를 연결하는 등 생각보다 왜 그렇게 설정을 하는지 이해가 되지 않아서 오랜 시간 이해를 위해 손으로 그려가면서 이해를 하려 노력했다.
🧐 느낀 점
c언어... 너무 어렵다. 3년 만에 포인터와 구조체를 만나니 머리가 아프다.
🔚 마무리
링크드 리스트 삽입 함수를 직접 구현하면서, 자료구조의 개념뿐 아니라 C의 포인터와 메모리 할당에 대해서도 한층 깊이 이해할 수 있었다.
앞으로의 자료구조 과제도 기대되며, 이 과정을 차곡차곡 기록해 나갈 예정이다.