Lecture 6 : Queue

이은상·2023년 10월 15일

Queue : Data structure where an incoming data comes first

First-In-First-Out (FIFO)

Ex) Queue at ticket office

Insertion and deletion

insertion과 deletion이 같은 location에서 일어나는 stack과 다름

  • Insertion takes place at the rear of the queue
  • Deletion takes place at the front of the queue

ADT of Queue

1. Object

an ordered group consisting of n elements

2. Operation

  1. Create() ::= Creates a queue
  2. Init(q) ::= Initialize the queue
  3. is_empty(q) ::= Checks if the queue is empty
  4. is_full(q) ::= Checks whether the queue is full
  5. enqueue(q, e) ::= Add an element at the rear of the queue
  6. dequeue(q) ::= Return the element at the front of the queue and delete it
  7. peek(q) ::= Returns the previous element without deleting it from the queue

dequeue와 peek는 맨앞의 원소를 제거하는지 아닌지에만 차이점을 가지고 있음

Applications

Application

  1. simulation queues(airplanes at the airport, queues at the bank)
  2. modeling data packets in communication
  3. buffering between printer and computer
  4. like the stack, the programmer's tool
  5. used in many algorithms

Queue using Array

Linear queue

implements a queue using arrays linearly

  • Moving elements for insertion when reaching at the end of array
  • Not used due to computational overhead

Circular queue

implements a queue using arrays in a circular form

Two variables to manage the front and back of the queue
1. front : index before the first element
2. rear : index of the last element

Empty : front == rear
Full : front % M == (rear + 1) % M

Note) One space is always left empty to distinguish between 'blank' and 'full' state

순서대로(circular, counterwise) element가 저장되기 때문에 Full의 공식이 성립할 수 있음

Operation

  1. Empty state detection function
int is_empty(QueueType * q){
	return (q->front == q->rear);
}
  1. Full state detection function
int is_full(QueueType * q){
	return ((q->rear + 1) % MAX_QUEUE_SIZE == q->front);  
}              
//don't have to use remain operator at q->front
  1. Rotate the index into a circle using the modulo function
    3.1 Insert function
void enqueue(QueueType *q, element item){
	if (is_full(q)) error("Queue is full\n");  //종료
    
    q->rear = (q->rear + 1) % MAX_QUEUE_SIZE;
             //circular queue이기 때문에 나머지 연산
    q->queue[q->rear] = item;
}

circular queue이기 때문에 index 최고값에 rear가 있을 때 원소를 삽입하면, rear는 0으로 변경됨.

따라서 나머지 연산을 통해 이를 수행하도록 함

3.2 Delete function

element dequeue(QueueType *q){
	if (is_empty(q)) error("Queue is empty\n");  //종료
    
    q->front = (q->front + 1) % MAX_QUEUE_SIZE;
             //circular queue이기 때문에 나머지 연산
    return q->queue[q->front];
}

Example

#define MAX_QUEUE_SIZE
typedef int element;
typedef struct QueueType{
	element queue[MAX_QUEUE_SIZE];
    int front, rear;
}QueueType;

void error(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(QueType *q) {
	return (q->rear + 1) % MAX_SIZE_QUEUE == q->front ;
}

void enqueue(QueueType *q, element item){
	if (is_full(q)) error("Queue is full\n");  //종료
    
    q->rear = (q->rear + 1) % MAX_QUEUE_SIZE;
             //circular queue이기 때문에 나머지 연산
    q->queue[q->rear] = item;
}

element dequeue(QueueType *q){
	if (is_empty(q)) error("Queue is empty\n");  //종료
    
    q->front = (q->front + 1) % MAX_QUEUE_SIZE;
             //circular queue이기 때문에 나머지 연산
    return q->queue[q->front];
}

element peek(QueType *q){
	if (is_empty(q)) error ("Queue is empty\n");
    return q->queue[(q->front + 1) % MAX_QUEUE_SIZE];
    //don't update front ← (front + 1) % MAX_QUEUE_SIZE
}

void 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

A queue that is implemented as a linked list

front

related to deletion and points to the element at the front of the linked list

rear

related to insertion and points to the element at the last of the linked list

If there are no elements in the queue, front and rear are NULL

Insertion at Linked Queue

void enqueue(QueueType *q, element item){
	QueueNode *temp = (QueueNode *)malloc(sizeof(QueueNode));
    if (temp == NULL) 
    	printf("Memory cannot be allocated.\n")'
    else{
    	temp->item = item;
        temp->link = NULL;
        if(is_empty(q)){
        	q->front = temp;
            q->rear = temp;
        }
        else{
        	q->rear->link = temp;
            q->rear = temp;				//linked list와 똑같음!
        }
    }
}

Deletion at Linked Queue

element dequeue(QueueType *q){
	QueueNode *temp = q->front;
    element item;
    if(is_empty(q))
    	error("Queue is empty");
    else {
    	item = temp->item;
        q->front = q->front->link;
        if(q->front == NULL)	//삭제하니 empty queue가 된 경우
        	q->rear = NULL;
    }
    free temp;
    
    return item;
}

Example code

typedef int element;
typedef struct QueueNode{
	element item;
    struct QueueNode *link;
}QueueNode;

typedef struct QueueType{
	QueueNode* front;
    QueueNode* rear;
}QueueType;

void error(char *message) {
	fprintf(stderr, "%s\n", message);
    exit(1);
}

int is_empty(QueueType *q){
	return (q->front == NULL);
}

void init(QueueType *q) {
	q->front = NULL;
    q->rear = NULL;
}

void enqueue(QueueType *q, element item){
	QueueNode *temp = (QueueNode *)malloc(sizeof(QueueNode));
    if (temp == NULL) 
    	printf("Memory cannot be allocated.\n")'
    else{
    	temp->item = item;
        temp->link = NULL;
        if(is_empty(q)){
        	q->front = temp;
            q->rear = temp;
        }
        else{
        	q->rear->link = temp;
            q->rear = temp;				
        }
    }
}

element dequeue(QueueType *q){
	QueueNode *temp = q->front;
    element item;
    if(is_empty(q))
    	error("Queue is empty");
    else {
    	item = temp->item;
        q->front = q->front->link;
        if(q->front == NULL)	
        	q->rear = NULL;
    }
    free temp;
    
    return item;
}

element peek(QueueType *q){    //don't need to update front
	if(is_empty(q))
    	error("Queue is empty");
    else {
    	return (q->front->item);
    }
}

void main(){
	QueueType q;
    
    init(&q);
    enqueue(&q, 1);
    enqueue(&q, 1);
    enqueue(&q, 1);
    
    printf("dequeue() = %d\n", dequeue(&q));
    printf("dequeue() = %d\n", dequeue(&q));
    printf("dequeue() = %d\n", dequeue(&q));
}

Deque(Double-ended queue)

It can be inserted and deleted at the front and rear of the queue

ADT of Deque

1. Object

an ordered group of n elements

2. Operation

  1. Create() ::= Creates a deque
  2. Init(dq) ::= Initialize the deque
  3. is_empty(dq) ::= Checks if the deque is empty
  4. is_full(qd) ::= Checks whether the deque is full
  5. add_front(dq, e) ::= Add an element before the deque
  6. add_rear(dq, e) ::= Add an element after the deque
  7. delete_front(dq) ::= Return the element before the deque and then delete it
  8. delete_rear(dq) ::= Return the element after the deque and then delete it
  9. get_front(dq) ::= Returns the element before the deque without deleting it
  10. get_rear(dq) ::= Returns the element after the deque without deleting it

Deque는 front와 rear에서 모두 삽입, 삭제가 가능해야 하므로 doubly linked list를 사용
simple linked list를 사용해도 되지만 그래도 doubly linked list가 선호됨

typedef int element;
  
typedef struct DlistNode{
	element data;
  	struct DlistNode *llink;
  	struct DlistNode *rlink;
}DlistNode;

typedef struct DequeType{
	DlistNode* head;	//front
  	DlistNode* tail;	//rear
}DequeType;

Doubly linked list can be implemented in two way
1. head node : node with no actual data
→ if the list is blank, we only have head node
circular connection
2. head, tail : just addresses
→ if list is blank, head and tail are NULL
circular connection is not needed

Insertion in Deque

1. create_node

DlistNode *create_node(DlistNode *llink, element item, DlistNode *rlink){
	DlistNode *node = (DlistNode *)malloc(sizeof(DlistNode));
	if (node == NULL) error("Memory allocation error");
	node->llink = llink;
	node->data = item;
	node->rlink = rlink;
	return node;
}

2. add_rear

void add_rear(DequeType *dq, element item){
	DlistNode *new_node = create_node(dp->tail, item, NULL);
  	if(is_empty(dq))		//case1. the deque is empty
  		dq->head = new_node;
  	else					//case2. the deque is not empty
  		dq->tail->rlink = new_node;
  	dq->tail = new_node;
}

3. add_front

void ad_front(DequeType *dq, element item){
	DlistNode *new_node = create_node(NULL, item, dq->head);
  	if (is_empty(dq))		//case1. the deque is empty
  		dq->tail = new_node;
    else 					//case2. the deque is not empty
  		dq->head->llink = new_node;
   dq->head = new_node;
}

Deletion at Deque

1. delete_rear

element delete_rear(DequeType *dq){
	element item;
  	DlistNode *removed_node;
  
    if(is_empty(dq)) printf("Deque is empty\n");
    else{
  		removed_node = dq->tail;
  		item = removed_node->data;
  		dq->tail = dq->tail->llink;
   		free(removed_node);	
  		if(dq->tail == NULL)	//삭제 후 deque에 더이상 노드가 없을 때
  			dq->head = NULL;
   		else
  			dq->tail->rlink = NULL;
    }
  	return item;
}

2. delete_front

element delete_front(DequeType *dq){
	element item;
  	DlistNode *removed_node;
  
  	if(is_empty(dq)) {
  		printf("Deque is empty\n");
  	}
    else {
  		removed_node = dq->head;
        item = removed_node->data;
    	dq->head = dq->head->rlink;
  		free(removed_node);
  		if (dq->head == NULL)	//삭제 후 deque에 더이상 노드가 없을 때
  			dq->tail = NULL;
  		else
  			dq->head->llink = NULL;
    }
  	return item;
}

Queue Application

1. Buffer

Queues can work as buffers that coordinate an interaction between two processes running at different speeds

Buffer links between a producer process that produces data and a consumer process that consumes data

Ex) Computer → Data(Queue) → Printer

Code

QueueType buffer;

/*Producer Process*/
producer(){
	while(1){
  		Produce data;
  		while(lock(buffer) != SUCCESS);
  		/*lock(): function to avoid producer and consumer
  				  access the buffer simultaneously*/
  		/*while lock(buffer) does not succeed,
  		  no 'enqueue' operation is performed*/
  		if(!is_full(buffer)){
  			enqueue(buffer, data);
           //if lock(buffer) succeeds, then it is executed
  		}
  		unlock(buffer);
  	}
}
  
/*Consumer Process*/
consumer(){
	while(1){
  		while(lock(buffer) != SUCCESS);
  		if(!is_empty(buffer)){
  			data = dequeue(buffer);
  			Consume data;
  		}
  		unlock(buffer);
  	}  
}

이 예시의 자세한 부분은 OS(Operating system) lecture에서 다룰 것임

2. Simulation

  • queue is used to simulate and analyze system characteristics according to queuing theory
  • the queuing model consists of a server that performs services for customers and a customer who receives services
  • Example
    : In the process of getting customers in and out of the bank, we wish to calculate the average waiting time of customers

Example code

구조체 정의 및 필요한 변수 선언

typedef struct element{		//customer structure
	int id;
    int arrival_time;
    int service_time;
}element;

typedef struct QueueType{
	element queue[MAX_QUEUE_SIZE];
    int front, rear;
}QueueType;
Queue queue;

double random(){
	return rand() / (double)RAND_MAX;
}

/*various state variables needed for simulation*/
int duration = 10;
//simulation time
double arrival_prob = 0.7;
//average number of customers arriving in one time unit
int max_serv_time = 5;		
//maximum service time for one customer
int clock;

/*results of the simulation*/
int customers;	
//total number of customers
int served_customers;	
//number of customers served
int waited_time;	
//time the customers waited

관련 함수들 정의

int is_customer_arrived(){
	if (random() < arrival_prob)
    		return TRUE;
    else 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 comes in %d minutes. Service time is %d minutes", 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 starts service in %d minutes. 
    		Wait time was %d minutes", 
    		customer.id, clock, clock - customer.arrival_time);
            
    return service_time;
}

void print_stat(){
	printf("Number of customers served = %d", served_customers);
    printf("Total wait time = %d minutes", wated_time);
    printf("Average wait time per person = %f minutes", (double)waited_time/served_customers);
    printf("Number of customers sill waithing = %d", customers - served_customers);
}

simulation program

void 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();
}

0개의 댓글