태그 보관물: C 프로그래밍

자료구조 – c언어 배열을 사용한 스택(stack) 구현

 스택(Stack)은 후입선출(Last In, First Out) 구조를 가지는 대표적인 자료구조 중 하나입니다. 이 글에서는 C언어로 배열을 사용하여 스택을 구현하는 방법에 대해 자세히 알아보겠습니다.

1. 스택의 기본 이해

 스택은 데이터를 한쪽 끝에서만 넣고 빼는 구조입니다. 이를 후입선출(LIFO: Last In, First Out) 구조라고 합니다.

1-1. 스택의 주요 연산

  • push: 스택에 데이터를 삽입하는 연산입니다.
  • pop: 스택에서 데이터를 제거하는 연산입니다.
  • peek: 스택의 최상단 요소를 확인하는 연산입니다.
  • isEmpty: 스택이 비어있는지 확인하는 연산입니다.
  • isFull: 스택이 가득 찼는지 확인하는 연산입니다.

2. 배열을 사용한 스택 구현

 우리는 배열을 사용하여 스택을 구현할 수 있습니다. 다음 예제 코드에서는 int형 스택을 구현합니다.

#include <stdio.h>
#define MAX 5

int stack[MAX];
int top = -1;

void push(int value) {
    if (top == MAX - 1) {
        printf("스택 오버플로우\n");
        return;
    }
    stack[++top] = value;
}

int pop() {
    if (top == -1) {
        printf("스택 언더플로우\n");
        return -1;
    }
    return stack[top--];
}

int peek() {
    if (top == -1) {
        printf("스택이 비어 있습니다\n");
        return -1;
    }
    return stack[top];
}

int isEmpty() {
    return top == -1;
}

int isFull() {
    return top == MAX - 1;
}

int main() {
    push(10);
    push(20);
    push(30);
    printf("Top 요소: %d\n", peek());
    printf("Pop 요소: %d\n", pop());
    printf("Top 요소: %d\n", peek());
    return 0;
}

3. 메모리 레이아웃 이해

 스택의 메모리 레이아웃은 다음과 같이 생길 수 있습니다.

 메모리 주소   데이터
--------------
 0x7fffd4 | [    ] (빈 자리)
 0x7fffd3 | [    ]
 0x7fffd2 | [ 30 ] < top
 0x7fffd1 | [ 20 ]
 0x7fffd0 | [ 10 ]
--------------

3-1. push 연산 후 메모리 상태

메모리 주소   데이터
--------------
 0x7fffd4 | [    ]
 0x7fffd3 | [ 40 ] < top
 0x7fffd2 | [ 30 ]
 0x7fffd1 | [ 20 ]
 0x7fffd0 | [ 10 ]
--------------

3-2. pop 연산 후 메모리 상태

메모리 주소   데이터
--------------
0x7fffd4 | [    ] 
0x7fffd3 | [    ]
0x7fffd2 | [ 30 ] < top
0x7fffd1 | [ 20 ]
0x7fffd0 | [ 10 ]
--------------

4. 스택의 활용 사례

 스택은 일상에서도 다양한 방식으로 사용됩니다.

4-1. 함수 호출 스택

 프로그래밍 언어에서는 함수 호출을 관리하기 위해 스택을 사용합니다. 함수를 호출할 때마다 스택에 복귀 주소와 매개변수 등이 저장됩니다.

4-2. 브라우저 뒤로 가기 기능

 웹 브라우저에서는 방문한 웹 페이지를 스택에 저장해두고, 뒤로 가기 버튼을 누르면 가장 마지막에 저장된 페이지로 돌아갑니다.

5. 스택과 배열의 차이점

 배열은 정적이지만 스택은 동적으로 요소를 추가하거나 제거할 수 있다는 점에서 차이가 있습니다.

5-1. 배열의 특성

  • 미리 할당된 크기만큼 저장할 수 있습니다.
  • 임의 접근이 가능합니다.

5-2. 스택의 특성

  • 동적으로 요소를 추가하거나 제거할 수 있습니다.
  • 특정 순서(LIFO)를 따릅니다.
구분 배열 스택
접근 방식 임의 접근 LIFO
크기 미리 정해짐 동적 조정 가능

자료구조 – c언어를 사용한 스택(Stack)

  스택은 컴퓨터 과학과 프로그래밍에서 많이 사용되는 자료구조입니다. 여기서는 C언어를 이용해 스택의 개념과 구현 방법을 살펴보겠습니다. 또한, 스택의 메모리 레이아웃을 그림으로 표현해 이해를 돕겠습니다.

스택의 기본 개념

스택의 정의

  스택은 데이터가 LIFO 구조로 저장되는 컬렉션입니다. 즉, 마지막에 삽입된 데이터가 가장 먼저 제거됩니다. 스택은 pushpop이라는 두 가지 기본 연산을 제공합니다.

스택의 활용 예

  스택은 함수 호출 관리, 문자열 역순 처리, 브라우저 히스토리 등 다양한 곳에서 활용됩니다. 이를 통해 스택의 유용성을 이해할 수 있습니다.

C언어로 스택 구현하기

구조체 이용한 스택 정의

  스택을 C언어로 구현하기 위해 구조체와 배열을 사용할 수 있습니다. 아래는 스택을 정의하는 코드입니다:

#include <stdio.h>
#include <stdlib.h>
#define MAX 100

typedef struct Stack {
    int items[MAX];
    int top;
} Stack;

void initialize(Stack *s) {
    s->top = -1;
}

int isEmpty(Stack *s) {
    return s->top == -1;
}

int isFull(Stack *s) {
    return s->top == (MAX - 1);
}

void push(Stack *s, int item) {
    if (isFull(s)) {
        printf("Stack is full\n");
    } else {
        s->items[++(s->top)] = item;
    }
}

int pop(Stack *s) {
    if (isEmpty(s)) {
        printf("Stack is empty\n");
        return -1;
    } else {
        return s->items[(s->top)--];
    }
}

스택 연산 예제 코드

  다음은 스택에 데이터를 push하고 pop하는 예제입니다:

int main() {
    Stack s;
    initialize(&s);

    push(&s, 10);
    push(&s, 20);
    push(&s, 30);

    printf("Popped item: %d\n", pop(&s));
    printf("Popped item: %d\n", pop(&s));

    return 0;
}

스택의 메모리 레이아웃

메모리 상태 시각화

  스택의 메모리 상태를 쉽게 이해하기 위해 그림을 사용합니다. push 연산과 pop 연산 후의 메모리 상태는 다음과 같습니다:

초기 상태

[ ][ ][ ][ ][ ]
top = -1

push(10)

[10][ ][ ][ ][ ]
top = 0

push(20)

[10][20][ ][ ][ ]
top = 1

pop()

[10][ ][ ][ ][ ]
top = 0

메모리 레이아웃 코드 분석

  위 그림들은 pushpop 연산 후 스택의 메모리 상태를 시각적으로 설명합니다. 이 과정을 코드와 함께 살펴보면 스택의 동작 원리를 쉽게 이해할 수 있습니다.

스택의 장단점

장점

  스택은 한쪽 끝에서만 데이터를 추가하고 제거하기 때문에 구현이 단순합니다. 또한, 데이터의 순서를 기억하고 역순으로 데이터를 처리할 때 유용합니다.

단점

  스택의 크기를 미리 지정해야 하기 때문에 메모리 낭비가 발생할 수 있습니다. 또한, 크기를 초과하는 데이터를 추가할 수 없습니다.

스택 활용 사례

재귀 호출 관리

  스택은 함수의 재귀 호출을 관리하는 데 유용합니다. 각 함수 호출 시 스택에 정보가 저장되고, 함수가 반환될 때 스택에서 제거됩니다.

수식 계산

  스택은 후위 표기법(Reverse Polish Notation) 계산기에서 사용됩니다. 연산자가 나올 때마다 스택에서 피연산자를 꺼내 계산합니다.

  스택은 또한 유효한 괄호 검사, 웹 브라우저의 뒤로 가기 기능 등에서도 중요한 역할을 합니다.

내부 링크

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;
}