

정글 5주차 과제로는 기본적으로 제공하는 자료구조 구조체 및 함수를 이용해서, 다양한 응용문제를 풀게 됩니다.
블로그에서 모든 응용문제를 다루기보다는, 기본 제공 코드의 구조체 및 함수의 동작 원리만 분석할 계획입니다. 사실 이걸 제대로 알면 응용문제를 푸는 게 더 수월할 거에요.
LinkedList 구조체에 꼬리 노드를 가리키는 tail 포인터를 새로 추가
// 각 노드를 나타내는 구조체 Node
typedef struct _listnode{
int item; // 노드에 저장된 값
struct _listnode *next; // 다음 노드를 가리키는 포인터
} ListNode;
// 연결 리스트 전체를 나타내는 구조체 LinkedList
typedef struct _linkedlist{
int size; // 연결 리스트의 원소 수
ListNode *head; // 머리 노드
ListNode *tail;
} LinkedList;
// 큐를 나타내는 구조체 Queue
typedef struct _queue {
LinkedList ll;
} Queue;
Queue *q와 같이, Queue 구조체를 가리키는 포인터 q를 매개변수로 받음q는 포인터기 때문에, q -> ll을 이용해 연결 리스트에 접근(q -> ll).head, (q -> ll).tail, (q -> ll).size을 이용해 head, tail, size 멤버에 접근int isEmptyQueue(Queue *q){
// 큐가 없는 경우
if (q == NULL) return 1;
// 원소 수가 0개거나 머리 / 꼬리 노드가 존재하지 않음
if ((q -> ll).size < 1 || (q -> ll).head == NULL || (q -> ll).tail == NULL){
return 1; // 참, 비어 있음
} else{
return 0; // 거짓, 비어 있지 않음
}
}
(q -> ll).size < 1)(q -> ll).head == NULL, (q -> ll).tail == NULL)1 반환 (참)0 반환 (거짓)insertNode는 지정한 인덱스에 도착할 때까지 모든 노드를 거친 후 삽입이 이루어짐insertNode 함수를 사용하는 것은 비효율적// 큐에 값 item을 인큐
void enqueue(Queue *q, int item){
// 큐가 없는 경우
if (q == NULL) return;
// 새로운 노드를 생성
ListNode *newNode = malloc(sizeof(ListNode));
newNode -> item = item;
newNode -> next = NULL;
// 꼬리 노드가 있는 경우
if ((q -> ll).tail != NULL){
// 현재 꼬리 노드의 다음 노드로 설정
(q -> ll).tail -> next = newNode;
} else {
// 꼬리노드가 없으면, 빈 리스트
// 머리노드로 설정
(q -> ll).head = newNode;
}
// 새로운 노드를 꼬리노드로 설정
(q -> ll).tail = newNode;
(q -> ll).size++;
}

malloc으로 새로운 노드 newNode를 생성newNode -> next = NULL로 저장(q -> ll).tail)가 있는 경우(q -> ll).tail -> next = newNode로, 기존 꼬리 노드의 다음 노드로 newNode 설정(q -> ll).head = newNode)(q -> ll).tail = newNode로, 새로운 노드를 꼬리 노드로 설정(q -> ll).size++로 연결 리스트의 크기를 1 증가removeNode 함수를 사용해도 됨tail 포인터를 NULL로 변경해 주어야 함removeNode는 tail이 없는 연결 리스트에서 동작하도록 만들었으니, tail까지 바꿔주진 않음// 큐의 맨 앞 값을 제거하고 반환
int dequeue(Queue *q){
// 큐가 없는 경우
if (q == NULL) return -99999; (에러)
// 큐가 비어있지 않는 경우
if (isEmptyQueue(q) == 0){
// 머리노드의 값 저장
int dequeue_value = (q -> ll).head -> item;
// 0번째 노드 제거
removeNode(&(q -> ll), 0);
if ((q -> ll).size == 0 || (q -> ll).head == NULL){
(q -> ll).tail = NULL;
}
return dequeue_value;
} else {
return -99999; // 큐가 빈 경우 -99999 반환 (에러)
}
}

isEmptyQueue 사용)(q -> ll).head -> item)을 dequeue_value에 저장removeNode 함수를 이용해 0번째 노드를 제거dequeue_value 반환(q -> ll).tail = NULL로 꼬리 노드가 제거되었음을 명시 -99999 반환// 큐의 맨 앞 값 확인
int queue_peek(Queue *q){
// 큐가 없는 경우
if (q == NULL) return -99999; (에러)
// 큐가 비어있지 않는 경우 (머리 노드 존재)
if (isEmptyQueue(q) == 0){
// 머리노드의 값 반환
return (q -> ll).head -> item;
} else {
return -99999; // 큐가 빈 경우 -99999 반환 (에러)
}
}
queue_peek으로 함수 이름을 지은 이유peek이라는 함수가 있었는데isEmptyQueue 사용)(q -> ll).head -> item)을 반환-99999 반환int main(void){
Queue q;
// 큐 내 연결 리스트의 size는 0, head, tail은 NULL로 초기화
q.ll.size = 0;
q.ll.head = NULL;
q.ll.tail = NULL;
// 큐에 인큐
enqueue(&q, 10);
enqueue(&q, 20);
enqueue(&q, 30);
// 큐에서 디큐
printf("디큐한 원소: %d\n", dequeue(&q));
// [출력] 디큐한 원소: 10
// 다시 인큐
enqueue(&q, 40);
enqueue(&q, 50);
// 맨 앞의 값 확인
printf("맨 앞의 값: %d\n", queue_peek(&q));
// [출력] 맨 앞의 값: 20
// 현재 큐 확인
printList(&(q.ll));
// [출력] 20 30 40 50 (앞 -> 뒤 순서)
// 큐의 크기 확인
printf("큐의 크기: %d\n", q.ll.size);
// [출력] 큐의 크기: 4
// 모든 원소 제거 및 데이터 할당 해제
removeAllItems(&(q.ll));
q.ll.tail = NULL;
return 0;
}
NULL로 초기화printList 함수로 연결 리스트의 주소(&(q.ll))를 보내면 현재 큐를 출력할 수 있음q.ll.size)로 스택의 크기를 확인할 수 있음removeAllItems 함수 사용ll -> tail은 NULL로 초기화하지 않기 때문에, 이는 수동으로 진행