[Data Structure] Stack and Queue (Queue)

In the last issue, we have learned about the stack in the data structure. In this issue, we will start to learn about the queue.

Table of contents

1. The concept and structure of the queue

2. Implementation of the queue

queue structure definition

Common interface functions

Initialize the queue

end of queue

head out of queue

Get the head element of the queue,

Get the tail element of the queue

Get the number of valid elements in the queue

Check if the queue is empty

destroy queue

3. Circular Queue


1. The concept and structure of the queue

Queue: A special linear table that only allows inserting data at one end and deleting data at the other end. The queue has a first-in-first- out FIFO (First In First Out) . The end of the delete operation is called the head of the queue

 Schematic diagram of the queue structure found on the Internet:

2. Implementation of the queue

Queues can also be implemented in the structure of arrays and linked lists. It is better to use the structure of linked lists, because if you use the structure of arrays, the efficiency of dequeueing and outputting data at the head of the array will be relatively low.

queue structure definition

We use a one-way linked list without a head node to implement the queue, and the queue needs to insert data at the end of the queue, so we have to traverse the linked list to find the end of the queue every time, which will make the running efficiency of the program lower. A solution is adopted here: define a pointer tail pointing to the end of the queue , and every time new data is inserted, just point the next pointer in tail to a new node, and update the position of tail after inserting new data at the same time . 

typedef int QDataType;
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

A separate structure is defined here to store the head pointer head and the tail pointer tail .

typedef struct Queue
{
	QNode* head;
	QNode* tail;
}Queue;

Common interface functions

//初始化
void QueueInit(Queue* pq);
//插入数据
void QueuePush(Queue* pq, QDataType x);
//销毁
void QueueDestroy(Queue* pq);
//弹出
void QueuePop(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队尾数据
QDataType QueueBack(Queue* pq);
//判断是否为空
bool QueueEmpty(Queue* pq);
//计算大小
int QueueSize(Queue* pq);

Initialize the queue

Since a one-way linked list without a head node is used here, it is only necessary to initialize the queue by setting both the head pointer and the tail pointer to null. If you want to define a linked list with a head node, you need to create a head node here

//初始化
void QueueInit(Queue* pq)
{
	assert(pq);
	pq->head = pq->tail = NULL;
}

end of queue

Since we have already recorded the tail of the linked list, there is no need to traverse the linked list, as long as the next pointer in the tail node points to the new node

 At the same time, it is necessary to judge whether the linked list is empty. If the linked list is inserting data for the first time, both the head pointer and the tail pointer need to point to the new node.

//队尾插入数据
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	//创建新节点
	QNode* newNode = (QNode*)malloc(sizeof(QNode));
	if (newNode == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	newNode->data = x;
	newNode->next = NULL;
	
	//将新节点链接起来
	if (pq->tail == NULL)
	{
		pq->head = pq->tail = newNode;
	}
	else
	{
		pq->tail->next = newNode;
		pq->tail = newNode;
	}
}

head out of queue

Before popping the data, to determine whether the linked list is empty, here you can use assert assertion:

Next is the specific pop-up operation:

  • Determine whether the queue has only one node. If it is, it means that there is only one node left in the queue, and the node is directly released, and at the same time, both the head pointer and the tail pointer are set to null.
  • If there are multiple nodes in the queue, point the head pointer pq->headto the next node, and then release the original head node. This completes the pop-up operation of a node.
//弹出
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	if (pq->head->next == NULL)
	{
		free(pq->head);
		pq->head = pq->tail = NULL;
	}
	else
	{
		QNode* next = pq->head->next;
		free(pq->head);
		pq->head = next;
	}
}

Get the head element of the queue,

Just return the data of the queue head node  pq->head->data .

//取队头数据
QDataType QueueFront(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->head->data;
}

Get the tail element of the queue

  • Just return the data of the tail node of the queue  pq->tail->data .
//取队尾数据
QDataType QueueBack(Queue* pq)
{
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->tail->data;
}

Get the number of valid elements in the queue

There are many ways to calculate the size, the two commonly used are:

One is to add a variable for counting when defining the Queue structure. Every time the data is inserted, it will be added, and the data will be popped out.

typedef struct Queue
{
	int size;
	QNode* head;
	QNode* tail;
}Queue;

The other is to traverse the linked list. This method is relatively inefficient. Of course, if you don’t care about this efficiency issue, you can use this method. We also use this method here:

//计算大小
int QueueSize(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	int size = 0;

	while (cur)
	{
		++size;
		cur = cur->next;
	}

	return size;
}

Check if the queue is empty

If the head node head is empty, the queue is empty.

//判断是否为空
bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->head == NULL;
} 

destroy queue

Destroying a queue is the same as destroying a normal linked list:

  • Use a pointer curto the head node of the queue pq->head.
  • In a loop, iterate over all nodes of the queue until curempty.
  • After the loop ends, both the head pointer and the tail pointer of the queue are set to null, indicating that the queue has been destroyed.
//销毁
void QueueDestroy(Queue* pq)
{
	assert(pq);
	QNode* cur = pq->head;
	while (cur)
	{
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}

	pq->head = pq->tail = NULL;
}

3. Circular Queue

The concept of a circular queue

In practice, we sometimes use a queue called a circular queue. For example, when the operating system course explains the producer-consumer model, a circular queue can be used. A circular queue can be implemented using an array or a circular linked list.

A circular queue is a queue implemented on a fixed-size array or linked list, which can implement enqueue and dequeue operations by recycling the space in the array or linked list . When the queue is full, new elements will re-enter the queue from the beginning to realize recycling.                                                                                                

Usually, the circular queue has two pointers, one is the head pointer head, and the other is the tail pointer tail. When the head and tail point to the same node, it means that the queue is empty.

When a data is inserted from the tail of the queue, the tail will move backwards. When popping head data, head moves backwards.

Empty circular queue:

Insert 3 data:

 Pop the data of a queue head:

According to the above situation, when the queue is full, head and tail will also point to the same node. This way we cannot distinguish whether the queue is empty or full. So came up with these two approaches:

Solution 1: Set a variable size to calculate the number of queue elements. If the size is equal to the queue capacity, the queue is full.

Solution 2: Open an extra space without storing data. When tail->next==head, it means it is full.

Generally, the practicability of the second scheme is stronger.

 Using the method of Scheme 2, we will find that if the queue is implemented in the form of a linked list, since the tail points to the next node of the tail node every time, it is not very convenient when we want to fetch the data of the tail node.

Therefore, we can use arrays to implement queues.

Implement circular queue with array

Link to the original title: Likou

 

typedef struct 
{
    int* a;
    int k;
    int head;
    int tail;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) 
{
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    //带有一个不存储数据的头
    obj->a = (int*)malloc(sizeof(int)*(k+1));
    obj->k = k;
    obj->head = obj->tail = 0;

    return obj;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) 
{
    return obj->head == obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) 
{
    int next = obj->tail + 1;
    if(obj->tail == obj->k)
    {
        next = 0;
    }

    return next == obj->head;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) 
{
    if(myCircularQueueIsFull(obj))
    {
        return false;
    }

    obj->a[obj->tail] = value;
    obj->tail++;

    //tail到达数组临界,调整tail位置到开头,以达到循环队列效果
    if(obj->tail == obj->k + 1)
    {
        obj->tail = 0;
    }

    return true;
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return false;
    }

    obj->head++;

    if(obj->head == obj->k + 1)
    {
        obj->head = 0;
    }

    return true;
}

int myCircularQueueFront(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }

    return obj->a[obj->head];
}

int myCircularQueueRear(MyCircularQueue* obj) 
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }

    int prev = obj->tail - 1;
    if(obj->tail == 0)
    {
        prev = obj->k;
    }

    return obj->a[prev];
}

void myCircularQueueFree(MyCircularQueue* obj) 
{
    free(obj->a);
    free(obj);
}

Guess you like

Origin blog.csdn.net/m0_73648729/article/details/131503510