태그 보관물: 링크드 리스트

자료구조 – c언어를 링크드 리스트를 사용한 큐(Queue) 구현

 큐(queue)는 데이터가 먼저 들어간 것이 먼저 나오는 특성을 가진 자료구조입니다. C언어에서 링크드 리스트를 사용하여 큐를 구현하는 방법과 메모리 레이아웃을 그림으로 함께 설명하겠습니다.

링크드 리스트로 큐 구현하기

 링크드 리스트로 큐를 구현하면 동적 메모리 할당을 통해 유연하게 크기를 조절할 수 있습니다. 아래는 큐의 기본 동작인 삽입(enqueue)과 삭제(dequeue)에 대해 단계별로 설명합니다.

큐 노드 정의하기

 큐 노드를 정의하는 구조체를 설계합니다. 이 구조체는 데이터와 다음 노드를 가리키는 포인터를 포함합니다.

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int data;
    struct Node* next;
} Node;

Node* front = NULL;
Node* rear = NULL;

삽입 함수 구현하기 (Enqueue)

 새로운 데이터를 큐에 추가하는 함수입니다.

void enqueue(int value) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    if (!newNode) {
        printf("메모리 할당 오류\n");
        return;
    }
    newNode->data = value;
    newNode->next = NULL;
    if (rear == NULL) {
        front = rear = newNode;
    } else {
        rear->next = newNode;
        rear = newNode;
    }
    printf("%d 삽입\n", value);
}

삭제 함수 구현하기 (Dequeue)

 큐에서 가장 먼저 추가된 데이터를 제거하는 함수입니다.

int dequeue() {
    if (front == NULL) {
        printf("큐가 비어 있습니다\n");
        return -1;
    }
    int value = front->data;
    Node* temp = front;
    front = front->next;
    if (front == NULL) {
        rear = NULL;
    }
    free(temp);
    printf("%d 제거\n", value);
    return value;
}

메모리 레이아웃

 링크드 리스트 형태의 큐를 시각적으로 이해하기 위해 메모리 레이아웃을 그림으로 표현해보겠습니다. 아래 예제는 10, 20, 30을 순서대로 삽입한 후 10을 제거하는 과정입니다.

초기 상태 (빈 큐):

front -> NULL
rear  -> NULL

10 삽입 후:

front -> [10|NULL]
rear  -> [10|NULL]

20 삽입 후:

front -> [10|next] -> [20|NULL]
rear  -> [20|NULL]

30 삽입 후:

front -> [10|next] -> [20|next] -> [30|NULL]
rear  -> [30|NULL]

10 제거 후:

front -> [20|next] -> [30|NULL]
rear  -> [30|NULL]

예제 실행 코드

 마지막으로 완전한 프로그램을 작성하여 큐의 삽입 및 삭제 기능을 검증할 수 있습니다.

int main() {
    enqueue(10);
    enqueue(20);
    enqueue(30);
    dequeue();
    dequeue();
    dequeue();
    return 0;
}

큐의 특성 요약

 링크드 리스트로 구현된 큐는 다음과 같은 장점과 단점을 갖습니다.

장점 단점
메모리 사용의 효율성 포인터 사용으로 인한 오버헤드
동적 크기 조절 가능 삽입 및 삭제 연산의 복잡성

 이처럼 링크드 리스트를 사용하면 동적 메모리 할당이 가능하여 큐의 크기를 유연하게 조절할 수 있지만, 각 노드마다 포인터를 유지해야 해서 메모리 오버헤드가 발생할 수 있습니다.


C언어를 사용한 단일 링크드 리스트 (Single Linked List)

  링크드 리스트는 데이터 요소들을 노드라 부르는 객체로 유지하는 자료구조이다. 이 노드들은 각자 다음 노드를 가리키는 포인터를 갖고 있어, 연결 리스트를 형성한다.

링크드 리스트의 기본 개념

  링크드 리스트는 순차적 접근이 용이하며, 동적 크기를 가지기 때문에 유연하게 데이터를 삽입 및 삭제할 수 있다.

노드 구조체

  링크드 리스트의 노드는 데이터와 다음 노드를 가리키는 포인터로 구성된 구조체이다. 예제 코드를 통해 쉽게 이해할 수 있다.

struct Node {
    int data;
    struct Node* next;
};

메모리 레이아웃

  링크드 리스트의 메모리 레이아웃은 다음과 같은 형태로 시각화할 수 있다:

[Head] -> [Data|Next] -> [Data|Next] -> [Data|Next] -> NULL

링크드 리스트의 초기화

  링크드 리스트를 구현하기 위해 먼저 리스트를 초기화해야 한다. 초기화하는 방법은 매우 간단하다.

struct Node* head = NULL;

노드 추가

  새로운 노드를 링크드 리스트에 추가하는 과정 역시 중요하다. 우리는 리스트의 끝에 노드를 추가하는 예제를 살펴볼 것이다.

가장 끝에 노드 추가하기

  리스트 끝에 노드를 추가하는 함수는 다음과 같다:

void append(struct Node** head_ref, int new_data) {
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    struct Node* last = *head_ref;
    new_node->data = new_data;
    new_node->next = NULL;
    if (*head_ref == NULL) {
        *head_ref = new_node;
        return;
    }
    while (last->next != NULL)
        last = last->next;
    last->next = new_node;
}

노드 삭제

  링크드 리스트에서 노드를 삭제하는 방법도 중요하다. 특정 값을 가진 노드를 삭제하는 예제를 보자.

특정 값의 노드 삭제하기

  삭제 함수는 다음과 같다:

void deleteNode(struct Node** head_ref, int key) {
    struct Node* temp = *head_ref, *prev;
    if (temp != NULL && temp->data == key) {
        *head_ref = temp->next;
        free(temp);
        return;
    }
    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }
    if (temp == NULL) return;
    prev->next = temp->next;
    free(temp);
}

링크드 리스트의 검색

  마지막으로, 링크드 리스트에서 특정 값을 가진 노드를 검색하는 방법을 알아보자.

노드 검색하기

  검색 함수는 다음과 같다:

struct Node* search(struct Node* head, int key) {
    struct Node* current = head;
    while (current != NULL) {
        if (current->data == key)
            return current;
        current = current->next;
    }
    return NULL;
}

예제 코드 종합

  위의 예제를 종합하여 통합적인 예제 코드를 작성해보자:

#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node* next;
};

void append(struct Node** head_ref, int new_data) {
    struct Node* new_node = (struct Node*)malloc(sizeof(struct Node));
    struct Node* last = *head_ref;
    new_node->data = new_data;
    new_node->next = NULL;
    if (*head_ref == NULL) {
        *head_ref = new_node;
        return;
    }
    while (last->next != NULL)
        last = last->next;
    last->next = new_node;
}

void deleteNode(struct Node** head_ref, int key) {
    struct Node* temp = *head_ref, *prev;
    if (temp != NULL && temp->data == key) {
        *head_ref = temp->next;
        free(temp);
        return;
    }
    while (temp != NULL && temp->data != key) {
        prev = temp;
        temp = temp->next;
    }
    if (temp == NULL) return;
    prev->next = temp->next;
    free(temp);
}

struct Node* search(struct Node* head, int key) {
    struct Node* current = head;
    while (current != NULL) {
        if (current->data == key)
            return current;
        current = current->next;
    }
    return NULL;
}

int main() {
    struct Node* head = NULL;
    append(&head, 1);
    append(&head, 2);
    append(&head, 3);

    printf("Linked List created: ");
    struct Node* temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");

    deleteNode(&head, 2);
    printf("Linked List after deletion of 2: ");
    temp = head;
    while (temp != NULL) {
        printf("%d -> ", temp->data);
        temp = temp->next;
    }
    printf("NULL\n");

    struct Node* found = search(head, 3);
    if (found != NULL) printf("Element 3 found in the list.\n");
    else printf("Element 3 not found in the list.\n");

    return 0;
}