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
Get the head element of the queue,
Get the tail element of the queue
Get the number of valid elements in the 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->head
to 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
cur
to the head node of the queuepq->head
. - In a loop, iterate over all nodes of the queue until
cur
empty. - 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);
}