큐(queue): 먼저 들어온 데이터가 먼저 나가는 구조, 선입 선출(FIFO: First-In First-out) 특성을 지닌 자료구조
객체: n개의 element 타입의 요소들의 순서 있는 모임
연산:
create() ::= 큐 생성
init(q) ::= 큐 초기화
is_empty(q) ::= 큐가 비어 있는지 검사
is_full(q) ::= 큐가 가득 찼는지 검사
enqueue(q,e) ::= 큐의 뒤에 요소를 추가
dequeue(q) ::= 큐의 앞에 있는 요소를 반환한 다음 삭제
peek(q) ::= 큐에서 삭제하지 않고 앞에 있는 요소를 반환
선형 큐(linear queue): 1차원 배열을 이용해서 데이터를 삽입, 삭제하는 queue를 구현 하는 경우
1. front와 rear의 초기값은 -1
2. 데이터가 삽입되면 rear를 하나 증가, 그 위치에 데이터가 저장됨
3. 삭제할 때는 front를 하나 증가하고 front가 가리키는 위치에 있는 데이터 삭제
단점: front와 rear의 값이 계속 증가만 하기 때문에 언젠가 배열의 끝에 도달하게 됨. 배열의 앞부분이 비어 있더라도 사용하지 못함. -> 따라서, 주기적으로 모든 요소들을 왼쪽으로 이동시켜야 함
원형 큐: 선형 큐의 문제를 해결함, 배열의 처음과 끝이 연결되어 있다고 생각하는 큐
=> front, rear의 값이 배열의 끝인 (MAX_QUEUE_SIZE-1)에 도달하면 다음에 증가되는 값은 0이 되도록 하는 것
1. front와 rear의 초기값은 0, front는 항상 큐의 첫 번째 요소의 하나 앞, rear는 마지막 요소를 가리킴
2. 삽입 시에 rear가 먼저 증가되고, 증가된 위치에 새로운 데이터가 삽입 됨
3. 삭제 시에도front가 먼저 증가되고, 증단된 위치에서 데이터를 삭제 함
=> 원형 큐에서 하나의 자리는 항상 비워 둠 -> 포화 상태와 공백 상태를 구별하기 위해
enqueue(Q,x)
rear <- (rear + 1) mod MAX_QUEUE_SIZE;
Q[rear] <- x;
dequeue(Q)
front <- (front + 1) MAX_QUEUE_SIZE;
return Q[front];
#include <stdio.h>
#include <stdlib.h>
#define MAX_QUEUE_SIZE 100
typedef int element;
typedef struct {
element queue[MAX_QUEUE_SIZE];
int front, rear;
} QueueType;
void error(const char *message){
fprintf(stderr, "%s\n", message);
exit(1);
}
// 초기화 함수
void init(QueueType *q){
q->front = 0;
q->rear = 0;
}
// 공백 상태 검출 함수
int is_empty(QueueType *q){
return (q->front == q->rear);
}
// 포화 상태 검출 함수
int is_full(QueueType *q){
return (q->rear + 1 % MAX_QUEUE_SIZE == q->front);
}
// 삽입 함수
void enqueue(QueueType *q, element item){
if(is_full(q)){
error("overflow");
}
q->rear = (q->rear + 1) % MAX_QUEUE_SIZE;
q->queue[q->rear] = item;
}
// 삭제 함수
element dequeue(QueueType *q){
if(is_empty(q)){
error("underflow");
}
q->front = (q->front + 1) % MAX_QUEUE_SIZE;
return q->queue[q->front];
}
// 피크 함수
element peek(QueueType* q)
{
if (is_empty(q)) {
error("underflow");
}
return q->queue[(q->front + 1) % MAX_QUEUE_SIZE];
}
int main(){
QueueType q;
init(&q);
printf("front: %d, rear: %d\n", q.front, q.rear);
enqueue(&q, 1);
enqueue(&q, 2);
enqueue(&q, 3);
printf("dequeue(): %d\n", dequeue(&q));
printf("dequeue(): %d\n", dequeue(&q));
printf("dequeue(): %d\n", dequeue(&q));
printf("front: %d, rear: %d\n", q.front, q.rear);
}
연결된 큐(linked queue): 연결 리스트로 만들어진 큐
장점: 배열로 구현된 큐에 비해 크기가 제한되지 않음
단점: 코드가 더 복잡, 링크 필드 때문에 메모리 공간을 더 많이 사용
// 삽입 연산
void enqueue(QueueType *q, element item){
QueueNode *tmp = (QueueNode*)malloc(sizeof(QueueNode));
if(tmp == NULL){
error("memory allocate error");
}else{
tmp->item = item; // 데이터 저장
tmp->link = NULL; // 링크 필드를 NULL로 저장
if(is_empty(q)){ // 큐가 공백이면
q->front = tmp;
q->rear = tmp;
}else{ // 큐가 공백이 아니면
q->rear->link = tmp; // 순서가 중요
q->rear = tmp;
}
}
삭제 연산: 연결 리스트의 처음에서 노드를 꺼내오면 됨
// 삭제 연산
element dequeue(QueueType *q){
QueueNode *tmp = q->front;
element item;
if(is_empty(q)){ // 공백 상태
error("underflow");
}else{
item = tmp->item; // 데이터를 꺼냄
q->front = q->front->link; // front를 다음 노드를 가리키도로 함
if(q->front == NULL){ // 공백 상태
q->rear = NULL;
}
free(tmp); // 노드 메모리 해제
return item; // 데이터 반환
}
}
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#define MAX_QUEUE_SIZE 100
typedef int element; // 요소의 타입
typedef struct QueueNode{ // 큐의 노드의 타입
element item;
struct QueueNode* link;
} QueueNode;
typedef struct { // 큐 ADT 구현
QueueNode *front, *rear;
} QueueType;
// 오류 함수
void error(const char *message){
fprintf(stderr, "%s\n", message);
exit(1);
}
// 초기화 함수
void init(QueueType *q){
q->front = q->rear = NULL;
}
// 공백 상태 검출 함수
int is_empty(QueueType *q){
return (q->front == NULL);
}
// 삽입 함수
void enqueue(QueueType *q, element item){
QueueNode* tmp = (QueueNode*)malloc(sizeof(QueueNode));
if(tmp == NULL){
error("memory allocate error");
}else{
tmp->item = item;
tmp->link = NULL;
if (is_empty(q)) {
q->front = q->rear = tmp;
}else{
q->rear->link = tmp;
q->rear = tmp;
}
}
}
// 삭제 함수
element dequeue(QueueType *q){
QueueNode* tmp = q->front;
if(is_empty(q)){
error("underflow");
}else{
element item = tmp->item;
q->front = q->front->link;
if(q->front == NULL){
q->rear = NULL;
}
free(tmp);
return item;
}
}
// 피크 함수
element peek(QueueType* q)
{
if (is_empty(q)) {
error("underflow");
}
return q->front->item;
}
int main(){
QueueType q;
init(&q);
enqueue(&q, 1);
enqueue(&q, 2);
enqueue(&q, 3);
printf("dequeue(): %d\n", dequeue(&q));
printf("dequeue(): %d\n", dequeue(&q));
printf("dequeue(): %d\n", dequeue(&q));
}
덱(deque): double-ended queue의 줄임말, 큐의 전단(front)과 후단(rear)에서 모두 삽입과 삭제가 가능한 큐를 의미
덱에서 가능한 연산: add_front(스택의 push), delete_front(스택의 pop, 큐의 dequeue), add_rear(큐의 enqueue), delete_rear, get_front, get_rear
객체: n개의 element형의 요소들의 순서 있는 모임
연산:
create() ::= 덱을 생성
init(dq) ::= 덱을 초기화
is_empty(dq) ::= 덱이 공백 상태인지 검사
is_full(dq) ::= 덱이 포화 상태인지 검사
add_front(dq,e) ::= 덱의 앞에 요소를 추가
add_rear(dq,e) ::= 덱의 뒤에 요소를 추가
delete_front(dq) ::= 덱의 앞에 있는 요소를 반환한 다음 삭제
delete_rear(dq) ::= 덱의 뒤에 있는 요소를 반환한 다음 삭제
get_front(dq) ::= 덱의 앞에서 삭제하지 않고 앞에 있는 요소를 반환
get_rear(dq) ::= 덱의 뒤에서 삭제하지 않고 뒤에 있는 요소를 반환
typedef int element; // 요소의 타입
typedef struct DlistNode{ // 노드의 타입
element data;
struct DlistNode *llink;
struct DlistNode *rlink;
}DlistNode;
typedef struct DequeType{ // 덱의 타입
DlistNode *head; // 첫 번째 노드를 가리키는 변수
DlistNode *tail; // 마지막 노드를 가리키는 변수
}DequeType;
// 하나의 노도를 동적 메메로 할당에 의하여 생성하고 노드의 llink와 rlink 값을 함수의 매개변수 값으로 설정
DlistNode *create_node(DlistNode *llink, element item, DlistNode *rlink){
DlinkNode *node =(DlistNode*)malloc(sizeof(DlistNode));
if(node == NULL){
error("memory allocate error");
}
node->llink = llink;
node->data = item;
node->rlink = rlink;
return node;
}
void add_rear(DequeType *dq, element item){
DlistNode *new_node = create_node(dq->tail,item,NULL);
if(is_empty(dq)){ // 덱이 공백이라면
dq->head = new_node; // head와 tail 모두 new_node가리킴
}else{
dq->tail->rlink = new_node; // tail 노드 다음 노도로 새로운 노드 추가
}
dq->tail = new_node; // tail 포인터 변경
}
void add_front(DequeType *dq, element item){
DlistNode *new_node = create_node(NULL,item,dq->head);
if(is_empty(dq)){ // 덱이 공백이라면
dq->tail = new_node; // head와 tail 모두 new_node가리킴
}else{
dq->front->llink = new_node; // head 노드의 앞 노드로 새로운 노드 추가
}
dq->front = new_node; // head 포인터 변경
}
element delete_front(DequeType *dq){
element item;
DlistNode *removed_node;
if(is_empty(dq)) error("underflow");
else{
removed_node = dq->head; // 삭제할 노드
item = removed_node->data; // 데이터 추출
dq->head = dq->head->rlink; // head 포인터 변경
free(remove_node); // 메모리 공간 반납
if(dq->head == NULL){ // 공백 상태라면
dq->tail = NULL;
}else{ // 공백 상태가 아니라면
dq->head->llink = NULL;
}
}
return item;
}
element delete_rear(Dequeue *dq){
element item;
DlistNode * removed_node;
if(is_empty(dq)) error("underflow");
else{
removed_node = dq->tail; // 삭제할 노드
item = removed_node->data; // 데이터 추출
dq->tail = dq->tail->llink; // tail 포인터 변경
free(removed_node); // 메모리 공간 반납
if(dq->tail == NULL){ // 공백 상태라면
dq->head = NULL;
}else{ // 공백 상태가 아니라면
dq->tail->rlink = NULL;
}
}
return item;
}
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
void error(const char*message){
fprintf(stderr, "%s\n", message);
exit(1);
}
void init(DequeType *dq){
dq->head = dq->tail = NULL;
}
int is_empty(DequeType *dq){
return dq->head == NULL; // tail 포인터로 검사해도 상관 없음
}
void display(DequeType *dq){
DlistNode* p;
printf("(");
for (p = dq->head; p != NULL;p = p->rlink){
printf("%d ", p->data);
}
printf(")\n");
}
int main(){
DequeType dq;
init(&dq);
add_front(&dq, 10); // 전단에 10 삽입
add_front(&dq, 20); // 전단에 20 삽입
add_rear(&dq, 30); // 후단에 30 삽입
display(&dq); // 덱 내용 출력
delete_front(&dq); // 전단에서 삭제
delete_rear(&dq); // 후단에서 삭제
display(&dq); // 덱 내용 출력
return 0;
}
QueueType buffer;
// 생산자 프로세스
producer(){
while(1){
// 데이터 생산
while(lock(buffer) != SUCCESS);
if(!is_full(buffer)){
enqueue(buffer,데이터);
}
unlock(buffer);
}
}
// 소비자 프로세스
consumer(){
while(1){
while(lock(buffer) != SUCCESS);
if(!is_empty(buffer)){
데이터 = dequeue(buffer);
데이터 소비;
}
unlock(buffer);
}
}
큐는 큐잉 이론에 따라 컴퓨터로 시스템의 특성을 시뮬레이션하여 분석하는데 이용
큐잉 모델: 고객에 대한 서비스를 수행하는 서비와 서비스를 받는 고객들로 이루어짐, 제한된 수의 서버로 인해 고객들은 서비스를 받기 위해 대기행렬에서 기다리게 됨
대기 행렬: 서비스를 받기 위해서 기다리는 것
ex)은행 서비스 시뮬레이션
1. 서비스 하는 행원은 한 사람으로 가정
2. 고객의 대기 행렬은 큐로 시뮬레이션 됨
3. 주어진 시간 동안 고객은 랜덤한 간격으로 큐에 들어옴
4. 고객들의 서비스 시간도 한계 값 안에서 랜덤하게 결정됨
5. 큐에 들어있는 고객들은 순서대로 서비스를 받기 시작함
5. 정해진 시간 동안 시뮬레이션이 끝나면 고객들의 평균 대기 시간을 계산하여 출력
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <math.h>
#define TRUE 1
#define FALSE 0
#define MAX_QUEUE_SIZE 100
typedef struct{
int id;
int arrival_time;
int service_time;
} element;
typedef struct{
element queue[MAX_QUEUE_SIZE];
int front, rear;
} QueueType;
QueueType queue;
void error(const char*message){
fprintf(stderr, "%s\n", message);
exit(1);
}
// 초기화 함수
void init(QueueType *q){
q->front = q->rear = 0;
}
// 공백 상태 검출 함수
int is_empty(QueueType *q){
return q->front == q->rear;
}
// 포화 상태 검출 함수
int is_full(QueueType* q)
{
return ((q->rear+1%MAX_QUEUE_SIZE) == q->front);
}
// 삽입 함수
void enqueue(QueueType *q, element item){
if(is_full(q)){
error("overflow");
}
q->rear = (q->rear + 1) % MAX_QUEUE_SIZE;
q->queue[q->rear] = item;
}
// 삭제 함수
element dequeue(QueueType *q){
if(is_empty(q)){
error("underflow");
}
q->front = (q->front + 1) % MAX_QUEUE_SIZE;
return q->queue[q->front];
}
// 피크 함수
element peek(QueueType* q)
{
if (is_empty(q)) {
error("underflow");
}
return q->queue[(q->front + 1) % MAX_QUEUE_SIZE];
}
// 0과 1 사이의 실수 난수를 생성하는 함수
double random(){
return rand() / (double)RAND_MAX;
}
// 시뮬레이션에 필요한 여러 가지 상태 변수
int duration = 10; // 시뮬레이션 시간
double arrival_prob = 0.7; // 하나의 시간 단위에 도착하는 평균 고객의 수
int max_serv_time = 5; // 하나의 고객에 대한 최대 서비스 시간
int clock;
// 시뮬레이션 결과
int customers; // 전체 고객 수
int served_customers; // 서비스를 받은 고객의 수
int waited_time; // 고객이 기다린 시간
// 랜덤 숫자를 생성하여 고객이 도착했는지 도착하지 않았는지를 판단
int is_customer_arrived(){
if(random() < arrival_prob){
return true;
}
return false;
}
// 새로 도착한 고객을 큐에 삽입
void insert_customer(int arrival_time){
element customer;
customer.id = customers++;
customer.arrival_time = arrival_time;
customer.service_time = (int)(max_serv_time * random()) + 1;
enqueue(&queue, customer);
printf("Customer %d is come in at %d min Service time is %d min\n", customer.id, customer.arrival_time, customer.service_time);
}
// 큐에서 기다리는 고개을 꺼내어 고객의 서비스 시간을 반환
int remove_customer(){
element customer;
int service_time = 0;
if(is_empty(&queue))
return 0;
customer = dequeue(&queue);
service_time = customer.service_time - 1;
served_customers++;
waited_time += clock - customer.arrival_time;
printf("Customer %d's service start at %d min Waited time is %d min\n", customer.id, clock,clock-customer.arrival_time);
return service_time;
}
// 통계치를 출력
void print_stat(){
printf("Service customer number = %d\n", served_customers);
printf("Total waited time = %d\n", waited_time);
printf("Waited time per one person = %f\n", (double)waited_time /served_customers);
printf("Still waited customer number = %d\n", customers - served_customers);
}
// 시뮬레이션 프로그램
int main(){
int service_time = 0;
clock = 0;
while(clock < duration){
clock++;
printf("Current time = %d\n", clock);
if(is_customer_arrived()){
insert_customer(clock);
}
if(service_time > 0){
service_time--;
}else{
service_time = remove_customer();
}
}
print_stat();
}
<참고자료>
천인국 · 공용해 · 하상호 지음,『C언어로 쉽게 풀어쓴 자료구조』, 생능출판(2016)