큐(순환 큐 / 링크드 큐)

WanJu Kim·2022년 12월 28일

Algorithm

목록 보기
3/6

큐는 스택과 달리 선입선출(FIFO : Fist In First Out) 구조이다. 먼저 들어간 데이터가 먼저 나온다.

순환 큐

순환 큐는 무엇인가? 마치 환형 링크드 리스트처럼, 맨 뒤 노드랑 맨 앞 노드랑 연결돼있는 큐이다. 근데 순환 큐는 문제점이 있다. 무엇인가? 큐가 비어있을 때랑 가득 차 있을 때랑 구분할 수 없다는 것이다.

어떻게 해결하는가? 일반적으로는 큐 배열을 생성할 때 실제 용량보다 1만큼 더 크게 만들어서 전단과 후단 사이를 비우는 것이다. (비워둬야 한다. 안 그러면 의미 없다.) 전단은 맨 앞 인덱스, 후단은 맨 뒤 인덱스 + 1이다. 그럼 비어있을 땐 전단과 후단이 같은 곳을 가리키고, 가득 차면 후단은 전단보다 1만큼 작은 곳을 가리킨다.

이 점을 기억하며 순환 큐를 만들어보자.

typedef int ElementType;

struct Node
{
	ElementType data;
};

struct QueueCircular
{
	int capacity;	// 용량.
	int front;		// 전단 인덱스.
	int rear;		// 후단 인덱스.
	Node* nodes;	// 노드 배열.
};

void QC_CreateQueue(QueueCircular** queue, int capacity)
{
    if (QC_IsFull(queue))
	{
    	cout << "가득 차 있음";
        return;
	}
    
	*queue = new QueueCircular;
	(*queue)->nodes = new Node[capacity + 1];
	(*queue)->capacity = capacity;
	(*queue)->front = 0;
	(*queue)->rear = 0;
}

void QC_DestroyQueue(QueueCircular* queue)
{
	delete[] queue->nodes;
	delete queue;
}

기본적인 함수는 지금까지 해왔던 거랑 거의 똑같아서 쉽게 할 수 있다. 주의할 건 Enqueue에서 만약 가득 차 있다면 바로 리턴하게 했다.

int QC_IsEmpty(QueueCircular* queue)
{
	return (queue->front == queue->rear);
}

int QC_IsFull(QueueCircular* queue)
{
	if (queue->front < queue->rear)
		return (queue->rear - queue->front) == queue->capacity;
	else
		return (queue->rear + 1) == queue->front;
}

배열 1칸 늘린 것을 고려해서 가득 차 있음 / 비어 있음을 만들어주었다.

int CQ_GetSize(QueueCircular* queue)
{
	if (queue->rear >= queue->front)
		return queue->rear - queue->front;
	else
		return queue->capacity + 1 - (queue->front - queue->rear);
}

사이즈도 QC_IsFull()가 마찬가지로 rear과 front의 위치차를 고려해서 만들었다.

다음 예제를 통해 제대로 만들었는지 확인한다.

int main()
{
	int i;
	QueueCircular* queue;
	QC_CreateQueue(&queue, 10);

	QC_Enqueue(queue, 1);
	QC_Enqueue(queue, 2);
	QC_Enqueue(queue, 3);
	QC_Enqueue(queue, 4);

	for (i = 0; i < 3; i++)
		cout << "Dequeue : " << QC_Dequeue(queue) << ", front : " << queue->front << ", rear : " << queue->rear << endl;
	i = 100;
	while (QC_IsFull(queue) == 0)
		QC_Enqueue(queue, i++);

	cout << "Capacity : " << queue->capacity << ", Size : " << CQ_GetSize(queue) << endl;
	while (QC_IsEmpty(queue) == 0)
		cout << "Dequeue : " << QC_Dequeue(queue) << ", front : " << queue->front << ", rear : " << queue->rear << endl;

	QC_DestroyQueue(queue);
	int debug = -1;
}

실행 결과.

링크드 큐

링크드 큐는 무엇인가? 링크드 리스트로 스택 구현한 것처럼, 링크드 리스트로 큐를 구현한 것이다. 노드 포인터로 구성되어있기 때문에, 순환 큐보다 구현하기 더 쉽다. 그럼 순환 큐 보다 더 좋다는 건가? 그렇다고는 할 수 없다. 왜냐하면 순환 큐는 동적 할당을 할 필요가 없기 때문이다. 크기가 예측 가능하고 고성능이 필요한 버퍼와 같은 사례에서는 링크드 큐보다 순환 큐가 더 적합하다.

링크드 큐는 다음과 같이 구현하면 된다. 지금까지 많이 했으므로 코드만 적겠다!

#include <string>
struct Node
{
	char* data;
	Node* nextNode;
};

struct QueueLinked
{
	Node* front;	// 큐의 시작.
	Node* rear;		// 큐의 끝.
	int count;		// 노드의 수.
};

void QL_CreateQueue(QueueLinked** queue);
void QL_DestroyQueue(QueueLinked* queue);
Node* QL_CreateNode(char* newData);
void QL_DestroyNode(Node* node);
void QL_Enqueue(QueueLinked* queue, Node* newNode);
Node* QL_Dequeue(QueueLinked* queue);
int QL_IsEmpty(QueueLinked* queue);

inline void QL_CreateQueue(QueueLinked** queue)
{
	*queue = new QueueLinked;
	(*queue)->front = nullptr;
	(*queue)->rear = nullptr;
	(*queue)->count = 0;
}

inline void QL_DestroyQueue(QueueLinked* queue)
{
	while (!QL_IsEmpty(queue))
	{
		Node* temp = QL_Dequeue(queue);
		QL_DestroyNode(temp);
	}
	delete queue;
}

inline Node* QL_CreateNode(const char* newData)
{
	Node* node = new Node;
	node->data = new char[strlen(newData) + 1];
	strcpy_s(node->data, strlen(newData) + 1, newData);
	return node;
}

inline void QL_DestroyNode(Node* node)
{
	delete[] node->data;
	delete node;
}

inline void QL_Enqueue(QueueLinked* queue, Node* newNode)
{
	if (queue->front == nullptr)
		queue->front = newNode;
	else
		queue->rear->nextNode = newNode;
	queue->rear = newNode;
	queue->count++;
}

inline Node* QL_Dequeue(QueueLinked* queue)
{
	if (queue->front == nullptr)
		return nullptr;

	Node* temp = queue->front;
	if (temp->nextNode == nullptr)
	{
		queue->front = nullptr;
		queue->rear = nullptr;
	}
	else
		queue->front = queue->front->nextNode;
	queue->count--;
	return temp;
}

inline int QL_IsEmpty(QueueLinked* queue)
{
	return queue->front == nullptr;
}
profile
Question, Think, Select

0개의 댓글