一、队列
队列是一种只有入队和出队的数据结构。队列可以是单线队列、循环队列、优先队列。它是一种先进先出的数据结构。
1.线性队列:
它有两个指针:head和tail
其中head指向头元素的前一位(也可以是指向头元素),tali指向末尾元素的位置,如果我们要出队一个元素,那么就把head指针向后移动一位。如果我们要入队一个元素,也是给tail指针后面的元素赋值后,向后移动一位。
缺点:线性队列的缺点是容易造成对储存空间的浪费,如果tail指针已经走到最后面,那么即使队列中即使还有元素,也无法入队。它可以通过依次移动元素的位置解决,但太浪费时间了,因此我们使用循环队列。
2.循环队列:如它名字一样它是一个头尾相连的队列,也拥有两个指针head和tail
假设队列有n+1个空间,那么位置0我们是不存储元素的,因此实际上存储空间只有n个。
其中当队列为空的时候,head和tail它们在同一个位置0;
当入队一个元素,tail向后移动一位,为tail==tail+1,但是为了防止tail指针在n时,无法移动,因此我们实际上为tail == (tail+1)%n;
当我们插入元素,使得队列满了以后,会导致tail+1 == head;
当我们出队时,与入队一样,实际上为head == (head+1)%n;
当我们拥有一个队列时,它的实际元素个数为(tail-head+n)%n,这样的目的是为了防止head>tail;
这里的head可以叫做front、tail可以叫做rear。
3.优先队列:它是按照元素优先级排列的队列。
4.双端队列:它是一种可以在前端和后端都进行插入和删除的队列。
双端队列既可以用作堆栈,也可以用作队列,因为它允许在两端进行插入和删除操作。
二、队列的操作:
Enqueue:在后端插入元素,无返回值,或返回无效。
Dequeue:从队列前端执行删除操作。返回删除的元素。
Peek:该元素由队列中的front指针(head)指向,但不删除它。
队列溢出(isfull):表示队列完全满时的溢出情况。
队列下溢(isempty):表示队列为空时的下溢情况。
三、队列的实现:(以c为例)
它包括两种方式:「顺序存储的队列」 和 「链式存储的队列」。
顺序存储的队列」:利用一组地址连续的存储单元依次存放队列中从队头到队尾的元素,同时使用指针 front(head) 指向队头元素在队列中的位置,使用指针 rear(tail)指示队尾元素在队列中的位置。
链式存储的队列」:利用单链表的方式来实现队列。队列中元素按照插入顺序依次插入到链表的第一个节点之后,并使用队头指针 front(head) 指向链表头节点位置,也就是队头元素,rear(tail) 指向链表尾部位置,也就是队尾元素。
注意:front 和 rear 的指向位置并不完全固定。有时候算法设计上的方便以及代码简洁,也会使 front 指向队头元素所在位置的前一个位置。rear 也可能指向队尾元素在队列位置的下一个位置。具体还是要看算法是如何实现的。
1.队列的数组表示:front和rear初始化为0
我们可以使用线性数组轻松表示队列。有两个变量,即前和后,在每个队列的情况下实现。front和rear变量指向队列中执行插入和删除的位置。最初,front 和queue 的值为-1,表示队列为空。
由于到目前为止,队列中还没有执行删除操作,因此 front 的值仍然是 -1 。然而,每次向队列中执行插入操作时,rear 的值都会加一。将元素插入到上图所示的队列中后,队列将如下所示。后部的值将变为5,而前部的值保持不变。
插入函数:
void insert (int queue[], int max, int front, int rear, int item)
{
if (rear + 1 == max)
{
printf("overflow");
}
else
{
if(front == -1 && rear == -1)
{
front = 0;
rear = 0;
}
else
{
rear = rear + 1;
}
queue[rear]=item;
}
}
删除函数:
int delete (int queue[], int max, int front, int rear)
{
int y;
if (front == -1 || front > rear)
{
printf("underflow");
}
else
{
y = queue[front];
if(front == rear)
{
front = rear = -1;
else
front = front + 1;
}
return y;
}
}
2.链表实现:
具有 n 个元素的队列的链接表示的存储要求是 o(n),而操作的时间要求是 o(1)。
在链接队列中,队列的每个节点由两部分组成,即数据部分和链接部分。队列的每个元素都指向内存中紧邻的下一个元素。
在链接队列中,内存中维护着两个指针,即前指针和后指针。front 指针包含队列起始元素的地址,rear 指针包含队列最后一个元素的地址。
插入和删除分别在后端和前端进行。如果front和rear都为NULL,则表明队列为空。
插入函数:
void insert(struct node *ptr, int item; )
{
ptr = (struct node *) malloc (sizeof(struct node));
if(ptr == NULL)
{
printf("\nOVERFLOW\n");
return;
}
else
{
ptr -> data = item;
if(front == NULL)
{
front = ptr;
rear = ptr;
front -> next = NULL;
rear -> next = NULL;
}
else
{
rear -> next = ptr;
rear = ptr;
rear->next = NULL;
}
}
}
删除函数:
void delete (struct node *ptr)
{
if(front == NULL)
{
printf("\nUNDERFLOW\n");
return;
}
else
{
ptr = front;
front = front -> next;
free(ptr);
}
}
四、c++中对队列的使用:
C++队列queue
1.头文件#include< queue>
2.初始化:
queue<int>q1;
queue<double>q2;
queue<char>q3;
其他初始化:
queue<char, list<char>>q1;
//用list容器实现的queue
queue<int, deque<int>>q2;
//用deque容器实现的queue
3.因为queue转换器要求容器支持front()、back()、push_back()及 pop_front(),说明queue的数据从容器后端入栈而从前端出栈。所以可以使用deque和list对queue初始化,而vector因其缺少pop_front(),不能用于queue。
4.常用函数:
push() 在队尾插入一个元素
pop() 删除队列第一个元素
size() 返回队列中元素个数
empty() 如果队列空则返回true
front() 返回队列中的第一个元素
back() 返回队列中最后一个元素
五、例题:
题目:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
class MyStack {
public:
queue<int> queue1;//用来存储入栈的元素
queue<int> queue2;//帮助queue1完成栈的操作
MyStack() {
//无,由于上面创建了
}
void push(int x) {
queue2.push(x);
while(!queue1.empty()){
queue2.push(queue1.front());
queue1.pop();
}
swap(queue1,queue2);
}
int pop() {//栈的pop操作是要返回值的,而队列的没有,因此要用一个值记录front,在删除
int r = queue1.front();
queue1.pop();
return r;
}
int top() {//栈的top和队列的front是一致的,因此只有记录就可以了
return queue1.front();
}
bool empty() {
return queue1.empty();
}
};
//思路:用两个队列实现栈。一个队列1用来专门存储、另外一个队列2用来记录我们插入的值。我们给队列插入值,把它记录到队列2,判断队列1是否有值,如果有就把队列1的元素从前到后放入队列2,最后交换队列1和队列2.如果队列1没有值,也交换队列1和队列2.因此队列始终保持前面的元素是后进的,后面的元素是先进的。这样对于队列1的front()操作就是栈的top()操作。pop()就是pop操作,empty()操作就是empty();