자료구조

Queue

cwchoiit 2024. 4. 15. 10:14
728x90
반응형
SMALL

이번엔 Queue를 정리해보자.

Queue는 위 아래 모두 구멍이 있는 박스라고 생각을 하면 된다.

그림을 보면 데이터는 위에서만 넣을 수 있고 빠지는 건 아래에서부터 빠진다.

이런 구조라면 먼저 들어간 데이터가 먼저 나오는 구조가 된다.

QueueFIFO(First In First Out) 구조이다.

 

코드적으로 생각해보기

Queue가 가지고 있는 보통의 대표적인 메서드는 다음과 같은 것들이 있다.

  • enqueue(item): 데이터 추가하기
  • dequeue(): 가장 오래된 데이터 빼기
  • peek(): 가장 오래된 데이터 보기
  • isEmpty(): Queue가 비었는지 확인하기

 

비어있는 사이즈가 7인 큐가 있다.

 

1. 이 상태에서 데이터 1을 넣으면 다음 모양이 된다.

 

2. 그리고 데이터 2를 또 추가적으로 넣는다면 이런 모양이 된다.

 

이 상태에서 peek() 메서드를 호출하면 결과는 1이다.

가장 먼저 넣은 데이터가 가장 오래된 데이터.

이 상태에서 isEmpty() 메서드를 호출하면  결과는 false다.

 

3. 데이터를 빼보자. 가장 먼저 들어간 1이 나온다.

4. 또 빼보자. 그 다음으로 들어간 2가 나온다.

 

 

이런 구조가 큐이고 가장 먼저 들어간 녀석을 빼는 작업과, 비었는지 안 비었는지를 확인할 수 있으려면 큐 구조는 시작과 끝을 알고 있어야 한다.

 

struct Queue

#define MAX_SIZE = 100

struct queue {
	int data[100];
    int front, rear;
}

void init_queue(struct queue *q) {
	q->front = q->rear = 0;
}

void enqueue(struct queue *q, int value) {
	if (q->rear == 100) {
    	printf("Queue is full.\n");
        return;
    }
    q->data[q->rear++] = value;
}

int dequeue(struct queue *q) {
	if (q->front == q->rear) {
    	print("Queue is empty\n");
        return -1;
    }
    return q->data[q->front++];
}

int is_empty(struct queue *q) {
	return q->front == q->rear;
}

int peek(struct queue *q) {
	if (q->front == q->rear) {
    	printf("Queue is empty\n");
        return -1;
    }
    return q->data[q->front];
}

 

 

Queue test

int main() {
    struct queue q;
    init_queue(&q);
	...
}

이 코드를 수행하면 다음과 같은 그림이 된다.

int main() {
    struct queue q;
    init_queue(&q);
    enqueue(&q, 1);
    ...
}

이 코드를 수행하면 큐에 1을 넣는다. 그러면 가장 오래된 데이터를 가르키는 위치와 다음에 넣을 수 있는 위치에 대한 정보가 변한다.

다음 그림처럼.

int main() {
    struct queue q;
    init_queue(&q);
    enqueue(&q, 1);
    enqueue(&q, 2);
    enqueue(&q, 3);
    ...
}

이렇게까지 코드가 수행되면 다음 그림이 된다.

int main() {
    struct queue q;
    init_queue(&q);
    enqueue(&q, 1);
    enqueue(&q, 2);
    enqueue(&q, 3);
    int value = dequeue(&q);
    ...
}

이제 dequeue() 메서드를 호출한다. 가장 오래된 데이터(1)가 나오게 된다. 그리고 가장 오래된 데이터를 큐에서 뺐으니 가장 오래된 데이터를 가르키는 포인터는 변경된다.

만약 이 상태에서 peek() 메서드를 호출하면 나오는 값은 2가 된다. 현재 front가 가르키는 값이 2니까.

만약 이 상태에서 is_empty() 메서드를 호출하면 결과는 false다.

 

이런것을 라고 한다.

 

 

큐에서 발생할 수 있는 문제점

다음 그림을 보자.

이렇게 사이즈가 7인 큐에 값을 계속 넣었고 마지막 자리까지 넣었다. 그럼 꽉 찬 상태라 더 넣을 수 없다. 

이 모양일 때 dequeue()를 2번 수행했다고 해보자.

이런 그림이 될 것이다. 그럼 큐는 이제 꽉 차지 않았다. 이때 큐에 넣을 수 있을까? rear가 마지막 자리라 넣을 수가 없다. 

이런 문제를 어떻게 해결할까? Circular Queue.

 

Circular Queue는 이 문제를 어떻게 해결할까? rear 포인터가 밖으로 벗어나면 맨 앞으로 다시 가져오는 방법이다.

 

이런 그림이 될 수 있도록.

그렇게 또 다시 채울 수 있게 되고 두번을 더 채우다 보면 다음 그림이 된다.

이렇게 다시 front, rear 포인터가 같은 자리를 가르키면 꽉 찼다는 걸 알 수 있게 된다.

rear, front를 다시 회전시키는 방법은 간단하다.

rear = (rear + 1) % queue_size
front = (front + 1) % queue_size

rear 포인터가 6이 되어서 더 채울 수 없는 공간까지 오면 rear + 1은 7이 된다.

그걸 큐의 사이즈(7)로 나눈 나머지는 0이니까 다시 0을 가리키게 된다.

front 포인터도 같은 방식.

 

Queue 활용

그래서 큐는 대표적으로 Graph / Tree에서 BFS할 때 사용한다.

728x90
반응형
LIST

'자료구조' 카테고리의 다른 글

Hash Table  (2) 2024.04.16
Binary Search Tree  (0) 2024.04.16
Tree와 BFS / DFS  (0) 2024.04.16
Stack  (0) 2024.04.15
Array vs Linked List  (0) 2024.04.15