

정글 5주차 과제로는 기본적으로 제공하는 자료구조 구조체 및 함수를 이용해서, 다양한 응용문제를 풀게 됩니다.
블로그에서 모든 응용문제를 다루기보다는, 기본 제공 코드의 구조체 및 함수의 동작 원리만 분석할 계획입니다. 사실 이걸 제대로 알면 응용문제를 푸는 게 더 수월할 거에요.
연결 리스트에 대한 상세한 설명은 이 글을 참고해주세요
#include <stdio.h>
#include <stdlib.h>
// 각 노드를 나타내는 구조체 ListNode
typedef struct _listnode{
int item; // 노드에 저장된 값
struct _listnode *next; // 다음 노드를 가리키는 포인터
} ListNode;
// 연결 리스트 전체를 나타내는 구조체 LinkedList
typedef struct _linkedlist{
int size; // 연결 리스트의 원소 수
ListNode *head; // 머리 노드를 가리키는 포인터
} LinkedList;

연결 리스트는 각 노드가 다음 노드를 가리키는 구조
ListNode 구조체에, 다음 노드를 가리키는 ListNode *형 포인터 next를 둠next의 값은 NULL (널 포인터)머리 노드의 위치를 모르면 연결 리스트의 탐색을 할 수 없음
LinkedList 구조체에, 머리 노드를 가리키는 ListNode *형 포인터 head를 둠head의 값은 NULL (널 포인터)⚠️ 엄밀히 말하면 head나 next의 포인터에는 가리키는 노드 구조체의 주소가 저장됩니다. 다만 본 글에서는 간결성을 위해 head에 노드를 저장, next에 노드를 저장과 같은 방식으로 표현하겠습니다. 착오가 없길 바랍니다.
// 연결 리스트의 특정 인덱스 노드를 반환
ListNode *findNode(LinkedList *ll, int index){
// 연결 리스트가 없거나, 인덱스가 범위를 벗어나는 경우
if (ll == NULL || index < 0 || index >= ll -> size){
return NULL;
}
// 리스트 순회 중 현재 노드
ListNode *temp = ll -> head; // 머리부터 시작
// 리스트에 노드가 없는 경우 (그럼 head가 NULL이겠죠)
if (temp == NULL){
return NULL;
}
// 본격적으로 연결 리스트 탐색
// index번째 요소를 찾으려면, 총 index번 이동해야 함
while (index > 0){
temp = temp -> next; // 다음 노드로 이동
if (temp == NULL) return NULL;
index--; // index 값 1 감소
}
// 현재 위치의 노드 반환
return temp;
}

temp 선언index번째 요소를 찾기 위해선, 0번째 노드인 head부터 총 index번 이동해야 함while문으로 temp = temp -> next로 다음 노드로 이동하며 - index--로 값을 1씩 감소시킴index == 0이 되었을 때 temp를 반환하여, 현재 위치의 노드 반환// 연결 리스트 ll의 인덱스 index에 값 value 삽입
int insertNode(LinkedList *ll, int index, int value){
// pre는 이전 노드, cur은 현재 노드를 가리킴
ListNode *pre, *cur;
// 연결 리스트가 없거나, 인덱스가 범위를 벗어나는 경우
if (ll == NULL || index < 0 || index > ll->size)
return -1;
// 이후 코드에 계속
}
cur을 선언next 포인터가 삽입된 노드를 가리키도록 변경해야 함pre 역시 선언int insertNode(LinkedList *ll, int index, int value){
// 위 코드에서 계속
// 리스트가 비어 있거나 머리에 삽입할 때 (index == 0)
if (ll -> head == NULL || index == 0) {
// (1) 현재 머리 노드를 cur에 저장
cur = ll -> head;
// (2) 새로운 노드 생성 후, 머리 노드를 새로운 노드로 변경
ll -> head = malloc(sizeof(ListNode));
ll -> head -> item = value;
// (3) 새로운 노드는 기존 머리노드 (cur)를 가리키게 함
ll -> head -> next = cur;
// (4) 연결리스트의 size를 1 증가
ll -> size++;
return 0;
}
// 아래 코드에서 계속
}

cur에 임시 저장ll -> head에 생성한 노드를 저장malloc(sizeof(ListNode)) 썼으니, 나중에 삭제 시 free해줘야 함!!ll -> head -> item, ll -> head -> next로 새로운 노드의 멤버 설정 가능next에 기존 머리 노드 cur을 저장ll -> head -> next = curll -> size를 1 증가int insertNode(LinkedList *ll, int index, int value){
// 위 코드에서 계속
// 0이 아닌 인덱스에 삽입하는 경우
// (1) 삽입할 인덱스 직전 노드를 `pre`에 저장
// 앞서 만든 findNode로 index-1번째 노드를 찾으면 됨
if ((pre = findNode(ll, index - 1)) != NULL){
// (1계속) 그리고 삽입할 인덱스의 노드는 `cur`에 저장
// 당연히 `pre`의 다음 노드
cur = pre -> next;
// (2) 새로운 노드 생성 후, 인덱스 이전의 노드가 새로운 노드를 가리키게 함
pre -> next = malloc(sizeof(ListNode));
pre -> next -> item = value;
// (3) 새로운 노드는 원래 인덱스의 노드를 가리키게 함
pre -> next -> next = cur;
// (4) 연결리스트의 size를 1 증가
ll -> size++;
return 0;
}
// findNode 과정에서 NULL을 만나면 -1 반환
return -1;
}

pre, 삽입할 인덱스의 기존 노드를 cur에 저장 findIndex로 index-1번째 노드를 찾아 pre를 구함cur은 pre의 다음 노드pre -> next에 생성한 노드를 저장malloc(sizeof(ListNode)) 썼으니, 나중에 삭제 시 free해줘야 함!!pre -> next -> item, pre -> next -> next로 새로운 노드의 멤버 설정 가능next에 기존 위치의 노드 cur을 저장pre -> next -> next = curll -> size를 1 증가🫤 연결 리스트의 맨 끝에 삽입을 해도 정상 작동하나요? 이 경우 삽입할 위치에 아무것도 없으니 cur이 NULL이 될 것 같은데요.
next를 cur로 설정하면, 결국 NULL이 저장되므로 새 노드가 꼬리 노드가 됩니다.cur을 * 연산자로 참조할 일이 없기 때문에, 널 포인터를 참조하는 오류는 발생하지 않습니다.// 연결 리스트 ll의 index번째 노드를 제거
int removeNode(LinkedList *ll, int index){
// pre는 이전 노드, cur은 현재 노드를 가리킴
ListNode *pre, *cur;
// 연결 리스트가 없거나, 인덱스가 범위를 벗어나는 경우
if (ll == NULL || index < 0 || index >= ll -> size){
return -1;
}
// 이후 코드에 계속
}
cur을 선언next 포인터가 삭제할 노드 다음의 노드를 가리키도록 변경해야 함pre 역시 선언int removeNode(LinkedList *ll, int index){
// 위 코드에서 계속
// 머리 노드를 삭제하는 경우 (index == 0)
if (index == 0){
// (1) 삭제할 노드의 다음 노드를 cur에 저장
cur = ll -> head -> next;
// (2) 삭제할 노드의 메모리 사용 해제
free(ll -> head);
// (3) 머리 노드를, 삭제할 노드의 다음 노드로 변경
ll -> head = cur;
// (4) 연결 리스트의 size를 1 감소
ll -> size--;
return 0;
}
// 아래 코드에서 계속
}

cur에 저장ll -> head)를 삭제하므로, cur은 (ll -> head -> next)malloc으로 메모리를 할당했으니, free해 주어야 함ll -> head를 cur로 변경ll -> size를 1 감소⚠️ 머리 노드가 유일한 노드인 경우, 다음 노드가 없기 때문에 cur은 NULL이 됩니다. 하지만 cur을 간접참조(*, ->)할 일은 없으니 에러가 발생하지 않습니다.
ll -> head = NULL이 되어 빈 연결 리스트가 됩니다.// 연결 리스트 ll의 index번째 노드를 제거
int removeNode(LinkedList *ll, int index){
// 위 코드에서 계속
// 머리 노드가 아닌 노드를 제거하는 경우
// (1) 제거할 인덱스 직전 노드를 `pre`에 저장
// 앞서 만든 findNode로 index-1번째 노드를 찾으면 됨
if ((pre = findNode(ll, index - 1)) != NULL){
// 제거할 인덱스의 노드가 NULL인 경우 삭제 불가
// -1 반환
if (pre -> next == NULL) return -1;
// (1계속) 제거할 인덱스의 노드는 `cur`에 저장
// 당연히 `pre`의 다음 노드
cur = pre -> next;
// (2) 삭제할 인덱스 직전 노드가, 삭제할 인덱스 다음 노드를 가리키게 함
pre -> next = cur -> next;
// (3) 삭제할 `cur`의 메모리 사용 해제
free(cur);
// (4) 연결 리스트의 size를 1 감소
ll -> size--;
return 0;
}
// 삭제에 실패
return -1;
}

pre에, 삭제할 노드를 cur에 저장findIndex로 index-1번째 노드를 찾아 pre를 구함cur은 pre의 다음 노드pre -> next에 cur -> next를 저장해, 삭제할 인덱스 직전 노드가 삭제할 인덱스 다음 노드를 가리키게 함malloc으로 메모리를 할당했으니, free해 주어야 함ll -> size를 1 감소⚠️ 꼬리 노드를 제거하는 경우, 다음 노드가 없기 때문에 cur -> next는 NULL이 됩니다. 하지만 cur -> next를 간접참조(*, ->)할 일은 없으니 에러가 발생하지 않습니다.
pre -> next = NULL이 되어, 삭제할 노드 이전 노드가 꼬리 노드가 됩니다.void printList(LinkedList *ll){
// 연결리스트가 없는 경우
if (ll == NULL) return;
// 탐색하는 현재 노드
// 머리 노드부터 시작
ListNode *cur = ll -> head;
// 연결 리스트가 비어 있을 때
if (cur == NULL) printf("빈 리스트입니다.");
// NULL을 만날 때까지 각 노드를 이동하며 값 출력
while (cur != NULL){
printf("%d ", cur -> item);
cur = cur -> next;
}
printf("\n");
}
NULL 주소를 만날 때까지 각 값을 출력cur을 두고, cur -> item으로 출력cur = cur -> next로 다음 노드로 이동// 모든 노드를 삭제
void removeAllItems(LinkedList *ll)
{
ListNode *cur = ll->head; // 현재 노드
ListNode *tmp; // 다음 노드를 저장할 임시 변수
// 모든 노드의 메모리 할당 해제
while (cur != NULL){
tmp = cur->next;
free(cur);
cur = tmp;
}
// 머리 노드는 NULL, 연결리스트 크기는 0으로
ll -> head = NULL;
ll -> size = 0;
}
malloc으로 동적 할당한 메모리 공간은, 함수가 종료되어도 자동으로 회수되지 않음free로 메모리 할당을 해제하는 함수 작성int main(void){
// size는 0, head는 NULL로 초기화
LinkedList ll = {0, NULL};
// 순서대로 노드 삽입
insertNode(&ll, ll.size, 41);
insertNode(&ll, ll.size, 9);
insertNode(&ll, ll.size, 33);
printList(&ll); // 41 9 33
// 노드 10을 1번 인덱스에 삽입
insertNode(&ll, 1, 10);
printList(&ll); // 41 10 9 33
// 2번 인덱스의 노드를 삭제
removeNode(&ll, 2);
printList(&ll); // 41 10 33
// 모든 노드 제거 및 free
removeAllItems(&ll);
return 0;
}
NULL로 초기화ll.size번째 인덱스에 삽입할 수 있음