*본 내용은 [윤성우의 열혈 자료구조] 책과 강의를 보고 공부하면서 요점 정리한 내용입니다.
먼저 들어간 것이 먼저 나오는
, 일종의 줄서기
에 비유할 수 있는 자료구조이다.FIFO(First-in, First-out)구조
의 자료구조이다.ADT를 대상으로 '배열 기반의 큐' 또는 '연결 리스트 기반의 큐'를 구현할 수 있다.
void QueueInit(Queue * pq);
∙ 큐의 초기화를 진행한다.
∙ 큐 생성 후 제일 먼저 호출되어야 하는 함수이다.
void QIsEmpty(Queue * pq);
∙ 큐가 빈 경우 TRUE(1)을, 그렇지 않은 경우 FALSE(0)을 반환한다.
void Enqueue(Queue * pq, Data data);
∙ 큐에 데이터를 저장한다. 매개변수 data로 전달된 값을 저장한다.
Data Dequeue(Queue * pq);
∙ 저장순서가 가장 앞선 데이터를 삭제한다.
∙ 삭제된 데이터는 반환된다.
∙ 본 함수의 호출을 위해서는 데이터가 하나 이상 존재함이 보장되어야 한다.
Data QPeek(Queue * pq);
∙ 저장순서가 가장 앞선 데이터를 반환하되 삭제하지 않는다.
∙ 본 함수의 호출을 위해서는 데이터가 하나 이상 존재함이 보장되어야 한다.
[보편적이고도 올바른 enqueue 연산에 대한 방식]
[보편적이고도 올바른 dequeue 연산에 대한 방식]
원형 큐
이다![원형 큐의 enqueue 연산]
[원형 큐의 dequeue 연산]
R == F
→ EmptyR+1 == F
→ Full#define QUE_LEN 100
typedef int Data;
/*** 원형 큐 ***/
typedef struct _cQueue
{
int front; // F
int rear; // R
Data queArr[QUE_LEN]; // 배열
} CQueue;
typedef CQueue Queue;
void QueueInit(Queue * pq);
int QIsEmpty(Queue * pq);
void Enqueue(Queue * pq, Data data);
Data Dequeue(Queue * pq);
Data QPeek(Queue * pq);
#endif
int NextPosIdx(int pos)
{
if(pos == QUE_LEN-1) // 배열의 마지막 인덱스 값일 때
return 0; // 0 반환 (배열의 첫 번째 인덱스 값)
else
return pos+1;
}
void QueueInit(Queue * pq)
{
pq->front = 0;
pq->rear = 0;
// 0이 아닌 다른 인덱스 값이어도 괜찮다.
}
int QIsEmpty(Queue * pq)
{
if(pq->front == pq->rear)
return TRUE;
else
return FALSE;
}
void Enqueue(Queue * pq, Data data)
{ // rear을 이동시키고(NextPosIdx 함수의 호출을 통해), 그 위치에 데이터 저장
if(NextPosIdx(pq->rear) == pq->front) // R+1 == F인 경우(full인경우)
{
printf("Queue Memory Error!");
exit(-1);
}
pq->rear = NextPosIdx(pq->rear); // R++
pq->queArr[pq->rear] = data; // 데이터 저장
}
Data Dequeue(Queue * pq)
{ // front를 이동시키고(NextPosIdx 함수의 호출을 통해), 그 위치의 데이터 반환
if(QIsEmpty(pq)) // 비어있는 경우
{
printf("Queue Memory Error!");
exit(-1);
}
pq->front = NextPosIdx(pq->front); // F++
return pq->queArr[pq->front]; // 데이터 반환
// 굳이 데이터를 삭제할 필요는 없다.
}
Data QPeek(Queue * pq)
{
if(QIsEmpty(pq))
{
printf("Queue Memory Error!");
exit(-1);
}
return pq->queArr[NextPosIdx(pq->front)];
}
int main(void)
{
// Queue의 생성 및 초기화 ///////
Queue q;
QueueInit(&q);
// 데이터 넣기 ///////
Enqueue(&q, 1); Enqueue(&q, 2);
Enqueue(&q, 3); Enqueue(&q, 4);
Enqueue(&q, 5);
// 데이터 꺼내기 ///////
while(!QIsEmpty(&q))
printf("%d ", Dequeue(&q));
return 0;
}
typedef int Data;
typedef struct _node
{
Data data;
struct _node * next;
} Node;
typedef struct _lQueue
{
Node * front; // F (삭제 위해)
Node * rear; // R (삽입 위해)
} LQueue;
typedef LQueue Queue;
void QueueInit(Queue * pq);
int QIsEmpty(Queue * pq);
void Enqueue(Queue * pq, Data data);
Data Dequeue(Queue * pq);
Data QPeek(Queue * pq);
void QueueInit(Queue * pq)
{
pq->front = NULL;
pq->rear = NULL;
}
int QIsEmpty(Queue * pq)
{
if(pq->front == NULL)
return TRUE;
else
return FALSE;
}
F = NULL
을 만들어 줄 때 R = NULL
로 만들어 줄 필요가 없기 때문이다. 자세한 건 Dequeue에서 알아보자.void Enqueue(Queue * pq, Data data)
{
Node * newNode = (Node*)malloc(sizeof(Node));
newNode->next = NULL;
newNode->data = data;
if(QIsEmpty(pq)) // 첫 번째 노드 삽입
{
pq->front = newNode;
pq->rear = newNode;
}
else // 두 번째 이후 노드 삽입
{
pq->rear->next = newNode;
pq->rear = newNode;
}
}
Data Dequeue(Queue * pq)
{
Node * delNode;
Data retData;
if(QIsEmpty(pq))
{
printf("Queue Memory Error!");
exit(-1);
}
// 백업
delNode = pq->front;
retData = delNode->data;
// 이동
pq->front = pq->front->next;
free(delNode); // 삭제
return retData;
}
F가 다음 노드를 가리키게 하고, F가 이전에 가리키던 노드를 소멸시킨 결과
마찬가지로, F가 다음 노드를 가리키게 하고, F가 이전에 가리키던 노드를 소멸시킨 결과
R은 그냥 내버려 둔다. 그래야 dequeue의 흐름을 하나로 유지할 수 있다.
그리고 R은 그냥 내버려 두어도 된다.
#include <stdio.h>
#include "ListBaseQueue.h"
int main(void)
{
// Queue의 생성 및 초기화 ///////
Queue q;
QueueInit(&q);
// 데이터 넣기 ///////
Enqueue(&q, 1); Enqueue(&q, 2);
Enqueue(&q, 3); Enqueue(&q, 4);
Enqueue(&q, 5);
// 데이터 꺼내기 ///////
while(!QIsEmpty(&q))
printf("%d ", Dequeue(&q));
return 0;
}
시뮬레이션 할 상황.
햄버거가게에서 대기실(큐)의 크기를 결정하는데 필요한 정보를 추출하는 것이 목적!
시뮬레이션을 통해서 추출된 정보의 형태
∙ 수용인원이 30명인 공간 - 안정적으로 고객을 수용할 확률 50%
∙ 수용인원이 50명인 공간 - 안정적으로 고객을 수용할 확률 70%
∙ 수용인원이 100명인 공간 - 안정적으로 고객을 수용할 확률 90%
∙ 수용인원이 200명인 공간 - 안정적으로 고객을 수용할 확률 100%
종류별 햄버거를 만드는데 걸리는 시간은 다음과 같다.
∙ 치즈버거 12초
∙ 불고기버거 15초
∙ 더블버거 24초
CircularQueue.h
CircularQueue.c
HamburgerSim.c
실행 결과 1
Simulation Report!
- Cheese burger: 80
- Bulgogi berger: 72
- Double burger: 88
- Waiting room size: 100 // 큐 사이즈
Queue Memorry Error!
덱은 앞으로도 뒤로도 넣을 수 있고, 앞으로도 뒤로도 뺄 수 있는 자료구조
덱의 4가지 연산
∙ 앞으로 넣기
∙ 앞에서 빼기
∙ 뒤로 넣기
∙ 뒤에서 빼기
Deque는 double ended queue의 줄인 표현으로, 양쪽 방향으로 모두 입출력이 가능함을 의미한다. 그리고 스택과 큐의 특성을 모두 지니고 있다고도 말한다. 덱을 스택으로도 큐로도 활용할 수 있기 때문이다.
void DequeInit(Deque * pdeq);
∙ 덱의 초기화를 진행한다.
∙ 덱 생성 후 제일 먼저 호출되어야 하는 함수이다.
int DQIsEmpty(Deque * pdeq);
∙ 덱이 빈 경우 TRUE(1)을, 그렇지 않은 경우 FALSE(0)을 반환한다.
void DQAddFirst(Deque * pdeq, Data data); // 앞으로 넣기
∙ 덱의 머리에 데이터를 저장한다. data로 전달된 값을 저장한다.
void DQAddLast(Deque * pdeq, Data data); // 뒤로 넣기
∙ 덱의 꼬리에 데이터를 저장한다. data로 전달된 값을 저장한다.
Data DQRemoveFirst(Deque * pdeq); // 앞에서 빼기
∙ 덱의 머리에 위치한 데이터를 반환 및 소멸한다.
Data DQRemoveLast(Deque * pdeq); // 뒤로 빼기
∙ 덱의 꼬리에 위치한 데이터를 반환 및 소멸한다.
Data DQGetFirst(Deque * pdeq); // 앞에서 PEEK
∙ 덱의 머리에 위치한 데이터를 소멸하지 않고 반환한다.
Data DQGetLast(Deque * pdeq); // 뒤에서 PEEK
∙ 덱의 꼬리에 위치한 데이터를 소멸하지 않고 반환한다.
typedef int Data;
typedef struct _node
{
Data data;
struct _node * next;
struct _node * prev;
} Node;
typedef struct _dlDeque
{
Node * head;
Node * tail;
} DLDeque;
typedef DLDeque Deque;
void DequeInit(Deque * pdeq);
int DQIsEmpty(Deque * pdeq);
void DQAddFirst(Deque * pdeq, Data data);
void DQAddLast(Deque * pdeq, Data data);
Data DQRemoveFirst(Deque * pdeq);
Data DQRemoveLast(Deque * pdeq);
Data DQGetFirst(Deque * pdeq);
Data DQGetLast(Deque * pdeq);