작성자: mwyun([멍])
프로그래밍 분야에서 자료구조의 중요함은 누구나 아는 사실입니다.
실제 많이 활용하는 꼭 필요한 자료 구조 몇가지의 소개와 함께 활용분야에 대해 설명드리겠습니다.
링크드 리스트
스택과 큐를 설명하기 전에 링크드 리스트를 먼저 설명하겠습니다.
이 링크드 리스트를 기반으로 나중에 나올 스택과 큐에 활용하게 되므로 잘 이해하고 있어야 합니다.
링크드 리스트는 말그대로 각 원소가 연결된 리스트 형태로 관리되는 자료구조 입니다. 각 원소는 다음 연결된 원소의 위치를 알고 있어야 하는데, 포인터를 통해서 다음 원소의 위치를 알아내게 됩니다.
링크드 리스트는 어떻게 연결되었는지에 따라서, 선형 링크드 리스트, 더블(이중)링크드 리스트, 환형 링크드 리스트가 있습니다.
선형 링크드 리스트
더블 링크드 리스트
환형 링크드 리스트
좀더 자세한 내용은 링크드리스트(:12)문서 를 참고하시기 바랍니다.
소 스
list.h
#ifndef _LIST_H
#define _LIST_H
#include <stdlib.h>
// List Element 구조체
typedef struct ListElmt_ {
void *data; // 데이터: 타입에 제한이 없는 void *사용
struct ListElmt_ *next; // 다음 노드를 가리키는 포인터(단일 연결 리스트는 링크 포인터가 한개이다)
} ListElmt;
// List 구조체
typedef struct List_ {
int size;
int (*match)(const void *key1, const void *key2); // 함수포인터
void (*destroy)(void *data); // 함수포인터
ListElmt *head; // 리스트의 처음 노드를 가리키는 포인터
ListElmt *tail; // 리스트의 마지막 노드를 가리키는 포인터
} List;
void list_init(List *list, void (*destroy)(void *data));
void list_destroy(List *list);
int list_ins_next(List *list, ListElmt *element, const void *data);
int list_rem_next(List *list, ListElmt *element, void **data);
#define list_size(list) ((list)->size)
#define list_head(list) ((list)->head)
#define list_tail(list) ((list)->tail)
#define list_is_head(list, element) ((element) == (list)->head ? 1 : 0)
#define list_is_tail(element) ((element)->next == NULL ? 1 : 0)
#define list_data(element) ((element)->data)
#define list_next(element) ((element)->next)
#endif
list.c
#include <stdlib.h>
#include <string.h>
#include "list.h"
// List 초기화
void list_init(List *list, void (*destroy)(void *data))
{
list->size = 0;
list->destroy = destroy;
list->head = 0;
list->tail = 0;
return;
}
// List 파기(메모리 해제)
void list_destroy(List *list)
{
void *data;
// List의 size가 0보다 크면 element가 남아있으므로 삭제작업 진행
while (list_size(list) > 0)
{
// 2번째 인자가 NULL이면 head element 삭제
if (list_rem_next(list, NULL, (void **)&data) == 0 && list->destroy != NULL)
{
// free(data);
list->destroy(data);
}
}
memset(list, 0, sizeof(List));
return;
}
// List의 element 다음에 새로운 element 추가
int list_ins_next(List *list, ListElmt *element, const void *data)
{
ListElmt *new_element;
if ((new_element = (ListElmt *)malloc(sizeof(ListElmt))) == NULL)
return -1;
new_element->data = (void *)data;
// element가 NULL이면
if (element == NULL)
{
// List에 element가 한 개도 없으면
if (list_size(list) == 0)
list->tail = new_element;
new_element->next = list->head; // head element 앞에 추가
list->head = new_element;
}
else // element 다음에 새로운 element를 추가
{
if (element->next == NULL)
list->tail = new_element;
// new_element의 다음 element로 element->next를 저장
new_element->next = element->next;
// element 다음에 new_element 추가
element->next = new_element;
}
list->size++; // List의 size 증가
return 0;
}
// List의 element 다음의 element를 삭제
int list_rem_next(List *list, ListElmt *element, void **data)
{
ListElmt *old_element;
// List의 size가 0이면(즉, element가 한개도 없으면...)
if (list_size(list) == 0)
return -1;
// element가 NULL이면 List의 head element 삭제
if (element == NULL)
{
*data = list->head->data;
old_element = list->head;
list->head = list->head->next; // head element에 head의 다음 element를 저장
if (list_size(list) == 1)
list->tail = NULL;
}
// 그렇지 않으면
else
{
// 삭제할 element가 없으면
if (element->next == NULL)
return -1;
*data = element->next->data;
// 삭제할 element인 element->next를 저장
old_element = element->next;
// element->next은 삭제할 element가 가리키는 다음 element를 저장
element->next = element->next->next;
// 삭제할 element의 다음 element가 없는 경우
if (element->next == NULL)
list->tail = element;
}
// element 삭제
free(old_element);
list->size--; // List의 size 감소
return 0;
}
예 제
linkedlist.c
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
// List의 모든 element 출력
static void print_list(const List *list)
{
ListElmt *element;
int *data, i;
fprintf(stdout, "List size is %d\n", list_size(list));
i = 0;
// List의 head 저장
element = list_head(list);
while (1)
{
// element의 data 저장
data = list_data(element);
fprintf(stdout, "list[%03d] = %03d\n", i, *data);
i++;
if (list_is_tail(element)) // element가 tail이면 break
break;
else
element = list_next(element); // 그렇지 않으면 다음 element 저장
}
return;
}
// 메인함수
int main(int argc, char *argv[])
{
List list;
ListElmt *element;
int *data, i;
// List 초기화
list_init(&list, free);
element = list_head(&list);
// List에 element 추가(1~10)
for (i = 10; i > 0; i--)
{
if ((data = (int *)malloc(sizeof(int))) == NULL)
return 1;
*data = i;
// List에 element 추가
// 2번째 인자가 NULL이므로 새로운 element는 head 앞에 추가
if (list_ins_next(&list, NULL, data) != 0)
return 1;
}
// List는 data=1이 head, data=10이 tail이 된다.
print_list(&list);
// head element 저장
element = list_head(&list);
for (i = 0; i < 7; i++)
element = list_next(element);
// data 저장
data = list_data(element);
fprintf(stdout, "Removing an element after the one containing %03d\n", *data);
// 8번째 다음 element 삭제
if (list_rem_next(&list, element, (void **)&data) != 0)
return 1;
print_list(&list);
fprintf(stdout, "Inserting 011 at the tail of the list\n");
*data = 11;
// List의 tail element 다음에 element에 추가
if (list_ins_next(&list, list_tail(&list), data) != 0)
return 1;
print_list(&list);
// head element 다음 element 삭제
fprintf(stdout, "Removing an element after the first element\n");
element = list_head(&list);
if (list_rem_next(&list, element, (void **)&data) != 0)
return 1;
print_list(&list);
// head element 앞에 element 추가
fprintf(stdout, "Inserting 012 at the head of the list\n");
*data = 12;
if (list_ins_next(&list, NULL, data) != 0)
return 1;
print_list(&list);
// 4번째 element 삭제
fprintf(stdout, "Inserting and removing the fourth element\n");
element = list_head(&list);
element = list_next(element);
element = list_next(element);
// element의 다음 element 삭제
if (list_rem_next(&list, element, (void **)&data) != 0)
return 1;
print_list(&list);
// head element 다음에 element 추가
fprintf(stdout, "Inserting 013 after the first element\n");
*data = 13;
if (list_ins_next(&list, list_head(&list), data) != 0)
return 1;
print_list(&list);
// List의 head, tail element가 올바른지 비교
i = list_is_head(&list, list_head(&list));
fprintf(stdout, "Testing list is head value = %d (1=OK)\n", i);
i = list_is_head(&list, list_tail(&list));
fprintf(stdout, "Testing list is head value = %d (0=OK)\n", i);
i = list_is_tail(list_tail(&list));
fprintf(stdout, "Testing list is tail value = %d (1=OK)\n", i);
i = list_is_tail(list_head(&list));
fprintf(stdout, "Testing list is tail value = %d (0=OK)\n", i);
fprintf(stdout, "Destroying the list\n");
list_destroy(&list);
return 0;
}
실 행
[0058][mwyun@mwyun:~/Project/linkedlist_ex]$ gcc -o linkedlist linkedlist.c list.c
[0058][mwyun@mwyun:~/Project/linkedlist_ex]$ ./linkedlist
List size is 10
list[000] = 001 <=== head
list[001] = 002
list[002] = 003
list[003] = 004
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 009
list[009] = 010 <=== tail
Removing an element after the one containing 008
List size is 9
list[000] = 001
list[001] = 002
list[002] = 003
list[003] = 004
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 010
Inserting 011 at the tail of the list
List size is 10
list[000] = 001
list[001] = 002
list[002] = 003
list[003] = 004
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 010
list[009] = 011
Removing an element after the first element
List size is 9
list[000] = 001
list[001] = 003
list[002] = 004
list[003] = 005
list[004] = 006
list[005] = 007
list[006] = 008
list[007] = 010
list[008] = 011
Inserting 012 at the head of the list
List size is 10
list[000] = 012
list[001] = 001
list[002] = 003
list[003] = 004
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 010
list[009] = 011
Inserting and removing the fourth element
List size is 9
list[000] = 012
list[001] = 001
list[002] = 003
list[003] = 005
list[004] = 006
list[005] = 007
list[006] = 008
list[007] = 010
list[008] = 011
Inserting 013 after the first element
List size is 10
list[000] = 012
list[001] = 013
list[002] = 001
list[003] = 003
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 010
list[009] = 011
Testing list is head value = 1 (1=OK)
Testing list is head value = 0 (0=OK)
Testing list is tail value = 1 (1=OK)
Testing list is tail value = 0 (0=OK)
Destroying the list
[0059][mwyun@mwyun:~/Project/linkedlist_ex]$
링크드 리스트를 이용한 스택
다음은 링크드 리스트를 기반으로 스택을 만들어 활용하는 예입니다.
소 스
stack.h
#ifndef STACK_H
#define STACK_H
#include <stdlib.h>
#include "list.h"
typedef List Stack;
#define stack_init list_init
#define stack_destroy list_destroy
int stack_push(Stack *stack, const void *data);
int stack_pop(Stack *stack, void **data);
// 스택에 top element에 데이터가 있는지 검사
#define stack_peek(stack) ((stack)->head == NULL ? NULL : (stack)->head->data)
#define stack_size list_size
#endif
stack.c
#include <stdlib.h>
#include "list.h"
#include "stack.h"
// 스택에 저장(밀어넣기)
int stack_push(Stack *stack, const void *data)
{
return list_ins_next(stack, NULL, data); // head element 앞에 추가
}
// 스택에서 꺼내기
int stack_pop(Stack *stack, void **data)
{
return list_rem_next(stack, NULL, data); // head element 삭제
}
예 제
stack_ex.c
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
#include "stack.h"
static void print_stack(const Stack *stack)
{
ListElmt *element;
int *data, size, i;
fprintf(stdout, "Stack size is %d\n", size = stack_size(stack));
i = 0;
element = list_head(stack);
while (i < size)
{
data = list_data(element);
fprintf(stdout, "stack[%03d] = %03d\n", i, *data);
element = list_next(element);
i++;
}
return;
}
int main(int argc, char *argv[])
{
// 스택 변수 정의
Stack stack;
int *data, i;
// 스택 초기화
stack_init(&stack, free);
// 10개의 element 저장
fprintf(stdout, "Pushing 10 elements\n");
for (i = 0; i < 10; i++)
{
if ((data = (int *)malloc(sizeof(int))) == NULL)
return 1;
*data = i + 1;
// 스택에 저장
if (stack_push(&stack, data) != 0)
return 1;
}
// 스택 출력
print_stack(&stack);
// 5개 element 꺼내기
fprintf(stdout, "Popping 5 elements\n");
for (i = 0; i < 5; i++)
{
if (stack_pop(&stack, (void **)&data) == 0)
free(data);
else
return 1;
}
// 스택 출력
print_stack(&stack);
// 100, 200을 스택에 저장
fprintf(stdout, "Pushing 100 and 200\n");
// 100을 스택에 저장
if ((data = (int *)malloc(sizeof(int))) == NULL)
return 1;
*data = 100;
if (stack_push(&stack, data) != 0)
return 1;
// 200을 스택에 저장
if ((data = (int *)malloc(sizeof(int))) == NULL)
return 1;
*data = 200;
if (stack_push(&stack, data) != 0)
return 1;
// 스택 출력
print_stack(&stack);
// 스택의 top element에 데이터가 있는지 검사
if ((data = stack_peek(&stack)) != NULL) // 있으면 data 출력
fprintf(stdout, "Peeking at the top element... value = %03d\n", *data);
else // 없으면
fprintf(stdout, "Peeking at the top element... value = NULL\n");
// 스택 출력
print_stack(&stack);
// 모든 element 꺼내기
fprintf(stdout, "Popping all elements\n");
// stack size가 0일 때까지 반복 실행
while (stack_size(&stack) > 0)
{
// 스택의 element 꺼내기
if (stack_pop(&stack, (void **)&data) == 0)
free(data);
}
// 스택 출력
print_stack(&stack);
// 스택 엿보기
if ((data = stack_peek(&stack)) != NULL) // 스택이 empty가 아니면 data 출력
fprintf(stdout, "Peeking at an empty stack... value = %03d\n", *data);
else // 스택이 empty(비었다)라고 출력
fprintf(stdout, "Peeking at an empty stack... value = NULL\n");
// 스택 출력
print_stack(&stack);
// 스택 제거
fprintf(stdout, "Destroying the stack\n");
stack_destroy(&stack);
return 0;
}
실 행
[2340][mwyun@mwyun:~/Project/stack]$ gcc -c stack_ex.c stack.c ../linkedlist_ex/list.c -I../linkedlist_ex
[2340][mwyun@mwyun:~/Project/stack]$ gcc -o stack_ex stack_ex.o stack.o list.o
[2341][mwyun@mwyun:~/Project/stack]$ ./stack_ex
Pushing 10 elements
Stack size is 10
stack[000] = 010 <=== head(top)
stack[001] = 009
stack[002] = 008
stack[003] = 007
stack[004] = 006
stack[005] = 005
stack[006] = 004
stack[007] = 003
stack[008] = 002
stack[009] = 001 <== tail(bottom)
Popping 5 elements
Stack size is 5
stack[000] = 005
stack[001] = 004
stack[002] = 003
stack[003] = 002
stack[004] = 001
Pushing 100 and 200
Stack size is 7
stack[000] = 200
stack[001] = 100
stack[002] = 005
stack[003] = 004
stack[004] = 003
stack[005] = 002
stack[006] = 001
Peeking at the top element... value = 200
Stack size is 7
stack[000] = 200
stack[001] = 100
stack[002] = 005
stack[003] = 004
stack[004] = 003
stack[005] = 002
stack[006] = 001
Popping all elements
Stack size is 0
Peeking at an empty stack... value = NULL
Stack size is 0
Destroying the stack
배열을 이용한 스택
링크드 리스트를 이용한 스택보다 배열을 이용한 스택의 구현은 유연성이나 효용성면에서는 링크드 리스트보다 떨어지지만 빠른 실행속도와 구현의 단순함 등의 장점이 있습니다.
스택은 일반 생활에서 쉽게 볼 수 있는 구조입니다. 서류더미 정도로 생각하면 되겠군요. 처리를 위한 서류더미(스택)가 있을 때 우리는 가장 위에 있는 서류 부터 꺼내어서 처리할 겁니다. 흔히 LIFO(후입선출)의 자료구조라고 합니다.
스택에 대한 좀더 학문?적인 내용은 stack 데이타 추상을 참고하기 바란다.
소 스
stack2.h
#ifndef STACK2_H
#define STACK2_H
#define STACKSIZE 10
typedef struct _stack {
int top;
int items[STACKSIZE];
} stack;
int push(stack *s, int value);
void print_stack(stack *s);
void stack_init(stack *s);
int auto_push(stack *s, int begin, int end);
int auto_pop(stack *s, int count);
#endif
stack2.c
#include "stack2.h"
// 스택 초기화
void stack_init(stack *s)
{
int i;
s->top = -1;
for (i = 0; i < STACKSIZE; i++)
s->items[i] = 0;
}
// 스택에 저장
int push(stack *s, int value)
{
if (s->top == STACKSIZE-1)
{
printf("stack overflow!!\n");
return -1;
}
s->top++; // top element를 하나 증가시킨다.
s->items[s->top] = value; // 새로운 top element에 value 저장
// printf("s->top = %d\n", s->top);
return value;
}
// 스택에서 꺼내기
int pop(stack *s)
{
int value;
//printf("s->top = %d\n", s->top);
if (s->top == -1)
{
printf("stack empty!!\n");
return -1;
}
value = s->items[s->top]; // top element의 value를 꺼낸다.
// s->items[s->top] = 0;
s->top--; // top element를 꺼냈으므로 top을 -1시킨다.
return value;
}
예 제
stack2_ex.c
#incldue "stack2.h"
// 지정한 범위 내의 숫자를 스택에 Push
int auto_push(stack *s, int begin, int end)
{
int i;
int value;
for (i = begin; i <= end; i++) // push 1~5
{
value = push(s, i);
if (value == -1)
return -1;
else
printf("pushed value = %3d\n", value);
}
// 스택 출력
print_stack(s);
return 1;
}
// 지정한 개수만큼 element를 스택에서 Pop
int auto_pop(stack *s, int count)
{
int i;
int value;
for (i = 1; i <= count; i++)
{
value = pop(s);
if (value == -1)
return -1;
else
printf("poped value = %3d\n", value);
}
// 스택 출력
print_stack(s);
return 1;
}
int main(int argc, char *argv[])
{
// 스택 변수 정의
stack s;
int i, value;
// 스택 초기화
stack_init(&s);
// 스택 테스트
auto_push(&s, 1, 5); // push 1~5
auto_pop(&s, 2); // pop 2 element
auto_push(&s, 6, 10); // push 6~10
auto_pop(&s, 6); // pop 6 element
auto_push(&s, 11, 14); // push 11~14
return 0;
}
실 행
[2351][mwyun@mwyun:~/Project/stack2]$ gcc -c stack2_ex.c stack2.c
[2351][mwyun@mwyun:~/Project/stack2]$ gcc -o stack2_ex stack2_ex.o stack2.o
[2351][mwyun@mwyun:~/Project/stack2]$ ./stack2_ex
pushed value = 1
pushed value = 2
pushed value = 3
pushed value = 4
pushed value = 5
stack size = 5
stack[ 4] = 5 <=== top
stack[ 3] = 4
stack[ 2] = 3
stack[ 1] = 2
stack[ 0] = 1 <== bottom
poped value = 5
poped value = 4
stack size = 3
stack[ 2] = 3
stack[ 1] = 2
stack[ 0] = 1
pushed value = 6
pushed value = 7
pushed value = 8
pushed value = 9
pushed value = 10
stack size = 8
stack[ 7] = 10
stack[ 6] = 9
stack[ 5] = 8
stack[ 4] = 7
stack[ 3] = 6
stack[ 2] = 3
stack[ 1] = 2
stack[ 0] = 1
poped value = 10
poped value = 9
poped value = 8
poped value = 7
poped value = 6
poped value = 3
stack size = 2
stack[ 1] = 2
stack[ 0] = 1
pushed value = 11
pushed value = 12
pushed value = 13
pushed value = 14
stack size = 6
stack[ 5] = 14 <=== top
stack[ 4] = 13
stack[ 3] = 12
stack[ 2] = 11
stack[ 1] = 2
stack[ 0] = 1 <=== bottom
[2351][mwyun@mwyun:~/Project/stack2]$
template을 이용한 스택
template class를 구현해보았습니다.
template은 컴파일 타임에 정적으로 타입이 결정되어 바인딩됩니다.
그러므로 template으로 구현된 클래스의 모든 메소드의 구현은 헤더파일(.h)에 정의되어야 합니다.
g++환경에서 컴파일을 하시면 되구요 VC++에서도 정상적으로 동작하는 것을 확인하였습니다.
소 스
tstack.h
#ifndef _TSTACK_H_
#define _TSTACK_H_
// 스택 클래스의 구조 정의및 멤버함수의 구현
#include <iostream.h>
const int DefaultSize = 80;
enum Boolean { FALSE = 0, TRUE = 1 };
template <class KeyType>
class Stack
{
private:
int top; // 스택의 top을 나타내는 인덱스
KeyType *stack; // 스택을 가리키는 포인터
int MaxSize; // 스택의 크기
public:
Stack(int MaxStackSize = DefaultSize); // 객체생성자
~Stack(); // 객체파괴자
KeyType* Add(const KeyType& x); // 스택의 top에 해당 값을 저장
KeyType* Delete(KeyType& x); // 스택의 top에서 해당 값을 빼냄
Boolean IsFull(); // 스택이 가득찼는가를 체크
Boolean IsEmpty(); // 스택이 비었는가를 체크
KeyType* GetStackTop(KeyType& x);
int GetTop() { return top; };
void Print(void);
};
template <class KeyType>
Stack<KeyType>::Stack(int MaxStackSize):MaxSize(MaxStackSize)
{
stack = new KeyType[MaxSize];
top = -1;
}
template <class KeyType>
Stack<KeyType>::~Stack()
{
delete stack;
}
template <class KeyType>
Boolean Stack<KeyType>::IsFull()
{
if (top == MaxSize-1) return TRUE;
else return FALSE;
}
template <class KeyType>
Boolean Stack<KeyType>::IsEmpty()
{
if (top == -1) return TRUE;
else return FALSE;
}
template <class KeyType>
KeyType *Stack<KeyType>::Add(const KeyType& x)
{
if (IsFull()) return;
stack[++top] = x;
return (KeyType *)&x;
}
template <class KeyType>
KeyType* Stack<KeyType>::Delete(KeyType& x)
{
if (IsEmpty()) return 0;
x = stack[top--];
return &x;
}
template <class KeyType>
KeyType* Stack<KeyType>::GetStackTop(KeyType& x);
{
if (IsEmpty()) return 0;
x = stack[top];
return &x;
}
#endif
예 제
stack3_ex.cpp
#include "tstack.h"
template <class KeyType> int auto_push(Stack<KeyType> &s, int begin = 0, int end = 0)
{
int i;
int value;
for (i = begin; i <= end; i++)
{
value = *s.Add(i);
if (value)
{
cout << "pushed value = " << value << endl;
}
else
{
cout << "stack full" << endl;
return 0;
}
}
s.Print();
return 1;
}
template <class KeyType> int auto_pop(Stack<KeyType> &s, int count = 0)
{
int i;
int value;
for (i = 1; i <= count; i++)
{
value = i;
value = *s.Delete(value);
if (value)
{
cout << "poped value = " << value << endl;
}
else
{
cout << "stack empty" << endl;
return 0;
}
}
s.Print();
return 1;
}
int main(int argc, char *argv[])
{
Stack<int> stack;
if (!auto_push(stack, 1, 5)) return -1;
if (!auto_pop(stack, 2)) return -1;
if (!auto_push(stack, 6, 10)) return -1;
if (!auto_pop(stack, 6)) return -1;
if (!auto_push(stack, 11, 14)) return -1;
return 0;
}
실 행
[2358][mwyun@mwyun:~/Project/stack3]$ g++ -c -g -Wno-deprecated stack3_ex.cpp
[2358][mwyun@mwyun:~/Project/stack3]$ g++ -o stack3_ex stack3_ex.o
[2358][mwyun@mwyun:~/Project/stack3]$ ./stack3_ex
pushed value = 1
pushed value = 2
pushed value = 3
pushed value = 4
pushed value = 5
stack size = 5
stack[4] = 5
stack[3] = 4
stack[2] = 3
stack[1] = 2
stack[0] = 1
poped value = 5
poped value = 4
stack size = 3
stack[2] = 3
stack[1] = 2
stack[0] = 1
pushed value = 6
pushed value = 7
pushed value = 8
pushed value = 9
pushed value = 10
stack size = 8
stack[7] = 10
stack[6] = 9
stack[5] = 8
stack[4] = 7
stack[3] = 6
stack[2] = 3
stack[1] = 2
stack[0] = 1
poped value = 10
poped value = 9
poped value = 8
poped value = 7
poped value = 6
poped value = 3
stack size = 2
stack[1] = 2
stack[0] = 1
pushed value = 11
pushed value = 12
pushed value = 13
pushed value = 14
stack size = 6
stack[5] = 14
stack[4] = 13
stack[3] = 12
stack[2] = 11
stack[1] = 2
stack[0] = 1
[2358][mwyun@mwyun:~/Project/stack3]$
링크드 리스트를 이용한 큐
다음은 링크드 리스트를 기반으로 큐를 만들어 활용하는 예입니다.
소 스
queue.h
#ifndef QUEUE_H
#define QUEUE_H
#include "list.h"
typedef List Queue;
#define queue_init list_init
#define queue_destroy list_destroy
int queue_enqueue(Queue *queue, const void *data);
int queue_dequeue(Queue *queue, void **data);
// 큐이 head element에 데이터가 있는지 검사
#define queue_peek(queue) ((queue)->head == NULL ? NULL : (queue)->head->data)
#define queue_size list_size
#endif
queue.c
#include <stdlib.h>
#include "list.h"
#include "queue.h"
// 큐의 tail 다음에 element 저장
int queue_enqueue(Queue *queue, const void *data)
{
return list_ins_next(queue, list_tail(queue), data);
}
// 큐의 head element 꺼내기
int queue_dequeue(Queue *queue, void **data)
{
return list_rem_next(queue, NULL, data);
}
예 제
queue_ex.c
#include <stdio.h>
#include <stdlib.h>
#include "list.h"
#include "queue.h"
static void print_queue(const Queue *queue)
{
ListElmt *element;
int *data, size, i;
fprintf(stdout, "Queue size is %d\n", size = queue_size(queue));
i = 0;
element = list_head(queue);
while (i < size)
{
data = list_data(element);
fprintf(stdout, "queue[%03d] = %03d\n", i, *data);
element = list_next(element);
i++;
}
return;
}
int main(int argc, char *argv[])
{
// 큐 변수 정의
Queue queue;
int *data, i;
// 큐 초기화
queue_init(&queue, free);
// 10개의 element 저장
fprintf(stdout, "Enqueuing 10 elements\n");
for (i = 0; i < 10; i++)
{
if ((data = (int *)malloc(sizeof(int))) == NULL)
return 1;
*data = i + 1;
// 큐에 저장
if (queue_enqueue(&queue, data) != 0)
return 1;
}
// 큐 출력
print_queue(&queue);
// 5개 element 꺼내기
fprintf(stdout, "Dequeuing 5 elements\n");
for (i = 0; i < 5; i++)
{
if (queue_dequeue(&queue, (void **)&data) == 0)
free(data);
else
return 1;
}
// 100, 200을 큐에 저장
fprintf(stdout, "Enqueuing 100 and 200\n");
// 100을 큐에 저장
if ((data = (int *)malloc(sizeof(int))) == NULL)
return 1;
*data = 100;
if (queue_enqueue(&queue, data) != 0)
return 1;
// 큐 출력
print_queue(&queue);
// 200을 큐에 저장
if ((data = (int *)malloc(sizeof(int))) == NULL)
return 1;
*data = 200;
if (queue_enqueue(&queue, data) != 0)
return 1;
// 큐 출력
print_queue(&queue);
// 큐의 head element에 데이터가 있는지 검사
if ((data = queue_peek(&queue)) != NULL)
fprintf(stdout, "Peeking at the head element... value = %03d\n", *data);
else
fprintf(stdout, "Peeking at the head element... value = NULL\n");
// 큐 출력
print_queue(&queue);
// 모든 element 꺼내기
fprintf(stdout, "Dequeuing all elements\n");
// queue size가 0일 때까지 반복 실행
while (queue_size(&queue) > 0)
{
// 큐의 element 꺼내기
if (queue_dequeue(&queue, (void **)&data) == 0)
free(data);
}
// 큐 엿보기
if ((data = queue_peek(&queue)) != NULL) // 큐가 empty가 아니면 data 출력
fprintf(stdout, "Peeking at an empty queue... value = %03d\n", *data);
else // 큐가 empty(비었다)라고 출력
fprintf(stdout, "Peeking at an empty queue... value = NULL\n");
// 큐 제거
fprintf(stdout, "Destroying the queue\n");
queue_destroy(&queue);
return 0;
}
실 행
[0046][mwyun@mwyun:~/Project/queue]$ gcc -c queue_ex.c queue.c ../linkedlist_ex/list.c -I../linkedlist_ex
[0046][mwyun@mwyun:~/Project/queue]$ gcc -o queue_ex queue_ex.o queue.o list.o
[0046][mwyun@mwyun:~/Project/queue]$ ./queue_ex
Enqueuing 10 elements
Queue size is 10
queue[000] = 001 <=== head(top)
queue[001] = 002
queue[002] = 003
queue[003] = 004
queue[004] = 005
queue[005] = 006
queue[006] = 007
queue[007] = 008
queue[008] = 009
queue[009] = 010 <=== tail(bottom)
Dequeuing 5 elements
Enqueuing 100 and 200
Queue size is 6
queue[000] = 006
queue[001] = 007
queue[002] = 008
queue[003] = 009
queue[004] = 010
queue[005] = 100
Queue size is 7
queue[000] = 006
queue[001] = 007
queue[002] = 008
queue[003] = 009
queue[004] = 010
queue[005] = 100
queue[006] = 200
Peeking at the head element... value = 006
Queue size is 7
queue[000] = 006
queue[001] = 007
queue[002] = 008
queue[003] = 009
queue[004] = 010
queue[005] = 100
queue[006] = 200
Dequeuing all elements
Peeking at an empty queue... value = NULL
Destroying the queue
[0046][mwyun@mwyun:~/Project/queue]$
배열을 이용한 큐
링크드 리스트를 이용한 큐보다 배열을 이용한 큐의 구현은 유연성이나 효용성면에서는 링크드 리스트보다 떨어지지만 빠른 실행속도와 구현의 단순함 등의 장점이 있습니다.
본 장에서의 소스는 환형큐의 제일 간단한 예를 든 것입니다.
소 스
queue2.h
#ifndef QUEUE2_H
#define QUEUE2_H
#define MAXQUEUE 10
typedef struct _queue {
int front;
int rear;
int items[MAXQUEUE];
} queue;
int queue_enqueue(queue *q, int value);
int queue_dequeue(queue *q);
void print_queue(queue *q);
void queue_init(queue *q);
int auto_enqueue(queue *q, int begin, int end);
int auto_dequeue(queue *q, int count);
#endif
queue2.c
#include "queue2.h"
// 큐 초기화
void queue_init(queue *q)
{
int i;
q->front = -1;
q->rear = -1;
for (i = 0; i < MAXQUEUE; i++)
q->items[i] = 0;
}
// 큐에 rear에 element 추가
int queue_enqueue(queue *q, int value)
{
// 큐가 가득찬 경우
if ((q->front == 0 && q->rear == MAXQUEUE - 1) ||
(q->rear + 1 == q->front))
{
printf("queue full!!\n");
return -1;
}
// rear값이 큐 버퍼의 최대 크기이면
// 큐 버퍼의 남은 공간을 활용하기 위해 rear를 -1로 초기화(환형큐)
if (q->rear == MAXQUEUE - 1)
q->rear = -1;
// 1증가(rear는 항상 1증가하면서 버퍼를 활용후 최대 크기(MAXQUEUE-1)가 되면 0부터 다시 시작한다.
q->rear++;
q->items[q->rear] = value;
printf("front = %d, rear = %d ---> ", q->front, q->rear);
return value;
}
// 큐의 front element 원소 삭제
int queue_dequeue(queue *q)
{
int value;
// 큐가 빈 경우
if (q->front == q->rear)
{
printf("queue empty!!\n");
return -1;
}
// front값이 큐 버퍼의 최대 크기이면
// 큐 버퍼의 남은 공간을 활용하기 위해 front를 -1로 초기화(환형큐)
if (q->front == MAXQUEUE - 1)
q->front = -1;
// 1증가(front는 항상 1증가하면서 버퍼를 활용후 최대 크기(MAXQUEUE-1)가 되면 0부터 다시 시작한다.
q->front++; // 1증가
value = q->items[q->front];
q->items[q->front] = 0;
printf("front = %d, rear = %d ---> ", q->front, q->rear);
return value;
}
front와 rear는 항상 인덱스값이 증가하다가(같은 방향으로 인덱스 이동) 큐의 최대 크기(MAXQUEUE-1)가 되면 다시 처음 인덱스(0번)부터
다시 시작한는 것을 보여주며 이것은 전형적인 환형큐의 예입니다.
환형는 이미 사용한 남아있는 빈 공간은 활요할 수 있다는 장점이 있습니다.
큐보다 더욱 효율적입니다.
예 제
queue2_ex.c
#include "queue2.h"
void print_queue(queue *q)
{
int i;
for (i = 0; i < MAXQUEUE; i++)
{
printf("queue[%3d] = %3d\n", i, q->items[i]);
}
printf("\n");
}
// 지정한 범위 내의 숫자를 큐에 추가
int auto_enqueue(queue *q, int begin, int end)
{
int i;
int value;
for (i = begin; i <= end; i++) // enqueue 1~5
{
value = queue_enqueue(q, i);
if (value == -1)
return -1;
else
printf("enqueued value = %3d\n", value);
}
// 큐 출력
print_queue(q);
return 1;
}
// 지정한 개수만큼 element를 큐에서 삭제
int auto_dequeue(queue *q, int count)
{
int i;
int value;
for (i = 1; i <= count; i++)
{
value = queue_dequeue(q);
if (value == -1)
return -1;
else
printf("dequeued value = %3d\n", value);
}
// 큐 출력
print_queue(q);
return 1;
}
int main(int argc, char *argv[])
{
// 큐 변수 정의
queue q;
// 큐 초기화
queue_init(&q);
// 큐 테스트
auto_enqueue(&q, 1, 5);
auto_dequeue(&q, 4);
auto_enqueue(&q, 6, 12);
auto_dequeue(&q, 5);
auto_enqueue(&q, 13, 14);
return 0;
}
배열의 사용하여 큐를 구현하는 경우 링크드 리스트와는 달리 빈 공간을 낭비할 수 있습니다.
그래서 일반적인 큐가 아닌 환형큐를 예를 든 것입니다.
물론 링크드 리스트로도 환형큐를 구현할 수 있습니다.
front와 rear의 증가 방향을 잘보면 빈 공간의 활용을 어떻게 하는지 알 수 있으며 환형큐의 작동원리를 이해할 수 있습니다.
더블 링크드 리스트
단일 링크드 리스트는 링크가 한개만 존재하나 더블 링크드 리스트는 말 그대로 링크가 두개가 존재합니다.
즉 노드는 다음 노드를 링크하는 포인터와 이전 노드를 링크하는 포인터를 가지게 됩니다.
단일 링크드 리스트는 다음 노드를 링크하는 포인터만 가지므로 단방향이지만, 더블 링크드 리스트는 양방향이라고 보시면 됩니다.
노드 탐색이 휠씬 유리합니다.
#include <stdlib.h>
#include <string.h>
#include "dlist.h"
// DList 초기화
void dlist_init(DList *list, void (*destroy)(void *data))
{
list->size = 0;
list->destroy = destroy;
list->head = 0;
list->tail = 0;
return;
}
// DList 파기(메모리 해제)
void dlist_destroy(DList *list)
{
void *data;
while (dlist_size(list) > 0)
{
if (dlist_remove(list, dlist_tail(list), (void **)&data) == 0 && list->destroy
!= NULL)
{
list->destroy(data);
}
}
memset(list, 0, sizeof(DList));
return;
}
// DList의 element 다음에 새로운 element 추가
int dlist_ins_next(DList *list, DListElmt *element, const void *data)
{
DListElmt *new_element;
if (element == NULL && dlist_size(list) != 0)
return -1;
// 새로운 element 생성
if ((new_element = (DListElmt *)malloc(sizeof(DListElmt))) == NULL)
return -1;
new_element->data = (void *)data;
// DList에 element가 한 개도 없으면
if (dlist_size(list) == 0) // 첫 element 추가
{
list->head = new_element; // head에 저장
list->head->prev = NULL;
list->head->next = NULL;
list->tail = new_element; // tail에 저장
}
// element 다음에 새로운 element 추가
else
{
// 양방향 연결이기 때문에 next, prev 값을 정확히 저장해야 링크가 끊기지 않는다.
new_element->next = element->next;
new_element->prev = element;
// element가 tail이면 새로운 element를 tail에 저장
if (element->next == NULL)
list->tail = new_element;
else
element->next->prev = new_element; // element->next의 element의 prev에 새로운 element 저장
// element->next은 새로운 element를 저장
element->next = new_element;
}
list->size++; // DList의 size 증가
return 0;
}
// DList의 element 이전에 새로운 element 추가
int dlist_ins_prev(DList *list, DListElmt *element, const void *data)
{
DListElmt *new_element;
if (element == NULL && dlist_size(list) != 0)
return -1;
// 새로운 element 생성
if ((new_element = (DListElmt *)malloc(sizeof(DListElmt))) == NULL)
return -1;
new_element->data = (void *)data;
// DList에 element가 한 개도 없으면
if (dlist_size(list) == 0) // 첫 element 추가
{
list->head = new_element;// head에 저장
list->head->prev = NULL;
list->head->next = NULL;
list->tail = new_element; // tail에 저장
}
// element 이전에 새로운 element 추가
else
{
// 양방향 연결이기 때문에 next, prev 값을 정확히 저장해야 링크가 끊기지 않는다.
new_element->next = element;
new_element->prev = element->prev;
// element가 head이면 새로운 element를 head에 저장
if (element->prev== NULL)
list->head = new_element;
else // element->prev의 element의 next에 새로운 element 저장
element->prev->next = new_element;
// element->prev는 새로운 element를 저장
element->prev = new_element;
}
list->size++; // DList의 size 증가
return 0;
}
// DList의 element 삭제
int dlist_remove(DList *list, DListElmt *element, void **data)
{
DListElmt *old_element;
if (element == NULL|| dlist_size(list) == 0)
return -1;
*data = element->data;
// element가 head이면
if (element == list->head)
{
list->head = element->next; // element 다음 element를 head에 저장
if (list->head == NULL) // element가 한 개도 없는 경우
list->tail = NULL; // tail도 NULL
else // element->next가 있다면 그 element의 prev를 NULL로 저장
element->next->prev = NULL;
}
// 그렇지 않으면
else
{
// 이전 element의 next는 element의 next를 저장
element->prev->next = element->next;
// element->next가 NULL이면
if (element->next == NULL)
list->tail = element->prev; // element->prev를 tail에 저장
else
element->next->prev = element->prev;
}
free(element); // element 메모리 해제
list->size--; // DList의 size 감소
return 0;
}
예 제
dlist_ex.c
#include <stdio.h>
#include <stdlib.h>
#include "dlist.h"
// DList의 모든 element 출력
static void print_list(const DList *list)
{
DListElmt *element;
int *data, i;
fprintf(stdout, "DList size is %d\n", dlist_size(list));
i = 0;
// DList의 head 저장
element = dlist_head(list);
while (1)
{
// element의 data 저장
data = dlist_data(element);
fprintf(stdout, "list[%03d] = %03d\n", i, *data);
i++;
if (dlist_is_tail(element)) // element가 tail이면 break
break;
else
element = dlist_next(element); // 그렇지 않으면 다음 element 저장
}
return;
}
// 메인 함수
int main(int argc, char *argv[])
{
DList list;
DListElmt *element;
int *data, i;
// DList 초기화
dlist_init(&list, free);
element = dlist_head(&list);
// DList에 element 추가(10~1)
for (i = 10; i > 0; i--)
{
if ((data = (int *)malloc(sizeof(int))) == NULL)
return 1;
*data = i;
// DList에 element 추가
// 2번째 인자가 head이므로 새로운 element는 head 앞에 추가
if (dlist_ins_prev(&list, dlist_head(&list), data) != 0)
return 1;
}
// DList 출력
print_list(&list);
element = dlist_head(&list);
// 9번째 element 삭제
for (i = 0; i < 8; i++)
element = dlist_next(element);
data = dlist_data(element);
fprintf(stdout, "Removing an element after the one containing %03d\n", *data);
if (dlist_remove(&list, element, (void **)&data) != 0)
return 1;
// DList 출력
print_list(&list);
// tail 다음에 새로운 element 추가
fprintf(stdout, "Inserting 011 at the tail of the list\n");
*data = 11;
if (dlist_ins_next(&list, dlist_tail(&list), data) != 0)
return 1;
// DList 출력
print_list(&list);
// tail element 삭제
fprintf(stdout, "Removing an element at the tail of the list\n");
element = dlist_tail(&list);
if (dlist_remove(&list, element, (void **)&data) != 0)
return 1;
// DList 출력
print_list(&list);
// tail 이전에 새로운 element 추가
fprintf(stdout, "Inserting 012 just before the tail of list\n");
*data = 12;
if (dlist_ins_prev(&list, dlist_tail(&list), data) != 0)
return 1;
// DList 출력
print_list(&list);
// 네번째 element 삭제
fprintf(stdout, "Interatin and removing the fourth element\n");
// prev, next를 이용해서 element 이동
element = dlist_head(&list);
element = dlist_next(element);
element = dlist_prev(element);
element = dlist_next(element);
element = dlist_prev(element);
element = dlist_next(element);
element = dlist_next(element);
element = dlist_next(element);
if (dlist_remove(&list, element, (void **)&data) != 0)
return 1;
// DList 출력
print_list(&list);
// 첫번째 element(head) 앞에 새로운 element 추가
fprintf(stdout, "Inserting 013 before the first element\n");
*data = 13;
if (dlist_ins_prev(&list, dlist_head(&list), data) != 0)
return 1;
// DList 출력
print_list(&list);
// head element 삭제
fprintf(stdout, "Removing an element at the head of the list\n");
if (dlist_remove(&list, dlist_head(&list), (void **)&data) != 0)
return 1;
// DList 출력
print_list(&list);
// head 다음에 새로운 element 추가
fprintf(stdout, "Inserting 014 just after the head of the list\n");
*data = 14;
if (dlist_ins_next(&list, dlist_head(&list), data) != 0)
return 1;
// DList 출력
print_list(&list);
// 2번째 element 다음에 새로운 element 추가
fprintf(stdout, "Inserting 015 two element after the head of the list\n");
if ((data = (int *)malloc(sizeof(int))) == NULL)
return 1;
*data = 15;
element = dlist_head(&list);
element = dlist_next(element);
if (dlist_ins_next(&list, element, data) != 0)
return 1;
// DList 출력
print_list(&list);
// DList의 head, tail element가 올바른지 비교
i = dlist_is_head(&list, dlist_head(&list));
fprintf(stdout, "Testing list is head value = %d (1=OK)\n", i);
i = dlist_is_head(&list, dlist_tail(&list));
fprintf(stdout, "Testing dlist is head value = %d (0=OK)\n", i);
i = dlist_is_tail(dlist_tail(&list));
fprintf(stdout, "Testing dlist is tail value = %d (1=OK)\n", i);
i = dlist_is_tail(dlist_head(&list));
fprintf(stdout, "Testing dlist is tail value = %d (0=OK)\n", i);
// DList 제거
fprintf(stdout, "Destroying the list\n");
dlist_destroy(&list);
return 0;
}
실 행
[0120][mwyun@mwyun:~/Project/double_linked_list_ex]$ gcc -c dlist.c dlist_ex.c
[0120][mwyun@mwyun:~/Project/double_linked_list_ex]$ gcc -o dlist_ex dlist_ex.o dlist.o
[0120][mwyun@mwyun:~/Project/double_linked_list_ex]$ ./dlist_ex
DList size is 10
list[000] = 001 <=== head
list[001] = 002
list[002] = 003
list[003] = 004
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 009
list[009] = 010 <=== tail
Removing an element after the one containing 009
DList size is 9
list[000] = 001
list[001] = 002
list[002] = 003
list[003] = 004
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 010
Inserting 011 at the tail of the list
DList size is 10
list[000] = 001
list[001] = 002
list[002] = 003
list[003] = 004
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 010
list[009] = 011
Removing an element at the tail of the list
DList size is 9
list[000] = 001
list[001] = 002
list[002] = 003
list[003] = 004
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 010
Inserting 012 just before the tail of list
DList size is 10
list[000] = 001
list[001] = 002
list[002] = 003
list[003] = 004
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 012
list[009] = 010
Interatin and removing the fourth element
DList size is 9
list[000] = 001
list[001] = 002
list[002] = 003
list[003] = 005
list[004] = 006
list[005] = 007
list[006] = 008
list[007] = 012
list[008] = 010
Inserting 013 before the first element
DList size is 10
list[000] = 013
list[001] = 001
list[002] = 002
list[003] = 003
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 012
list[009] = 010
Removing an element at the head of the list
DList size is 9
list[000] = 001
list[001] = 002
list[002] = 003
list[003] = 005
list[004] = 006
list[005] = 007
list[006] = 008
list[007] = 012
list[008] = 010
Inserting 014 just after the head of the list
DList size is 10
list[000] = 001
list[001] = 014
list[002] = 002
list[003] = 003
list[004] = 005
list[005] = 006
list[006] = 007
list[007] = 008
list[008] = 012
list[009] = 010
Inserting 015 two element after the head of the list
DList size is 11
list[000] = 001
list[001] = 014
list[002] = 015
list[003] = 002
list[004] = 003
list[005] = 005
list[006] = 006
list[007] = 007
list[008] = 008
list[009] = 012
list[010] = 010
Testing list is head value = 1 (1=OK)
Testing dlist is head value = 0 (0=OK)
Testing dlist is tail value = 1 (1=OK)
Testing dlist is tail value = 0 (0=OK)
Destroying the list
[0120][mwyun@mwyun:~/Project/double_linked_list_ex]$
그 림
그림으로 보면 더욱 이해가 빠르므로 다음 3개의 함수에 대해 도식화하였습니다.
dlist_ins_next 함수dlist_ins_prev 함수dlist_remove 함수
강좌로 올린 소스는 다음 서적에 실린 예제로 알고 있습니다.
C로 구현한 알고리즘, 카일 루든(Kyle Loudon) 저/허욱 역, 한빛미디어
URL: http://hanbitbook.co.kr/look.php?isbn=89-7914-063-0
독자서평을 보니 대체적으로 좋은 반응이군요
깊은 이해를 원하시는 분들은 서적을 구입해서 보셨으면 합니다.
Contents
자료구조를 익히자!!
링크드 리스트
선형 링크드 리스트
더블 링크드 리스트
환형 링크드 리스트
소 스
예 제
실 행
링크드 리스트를 이용한 스택
소 스
예 제
실 행
배열을 이용한 스택
소 스
예 제
실 행
template을 이용한 스택
소 스
예 제
실 행
링크드 리스트를 이용한 큐
소 스
예 제
실 행
배열을 이용한 큐
소 스
예 제
실 행
더블 링크드 리스트
소 스
예 제
실 행
그 림
프로젝트
참고문헌
Recent Posts
Archive Posts
Tags