队列简介
队列也是一种线性结构。但它只能在表的一端追加元素(这端叫做队尾),另一端删除元素(这端叫做队头) 。因此队列是一种FIFO (先进先出)特性的线性数据结构。
从队头删除元素的操作叫做出队,从队尾追加元素的操作叫做入队。
在这里插入代码片
如图是含有n个元素的队列的模型。根据队列的出入元素特点,可以确定,元素a1最先入队,紧接着a2,s3 … 如果a2要出队,必须等a1出队。a1最先入队,也是最先出队,an最后入队,也是最后出队。
链式队列
链式队列是队列的实现方式之一。链式队列内部使用带头结点的单向链表来实现。它的好处的是灵活,队列容量理论上是不受限制的。
我们使用链表的首结点来表示队列的队头,链表的尾结点代表队尾。
当队列为空时,队尾元素指针指向头结点headNode。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>
#define NDEBUG
struct Node{
int element; //结点保存的元素
Node*next; //next指针
Node(int e=0, Node*nxt=NULL):element(e),next(nxt)
{
}
};
class LinkedQueue
{
private:
Node headNode; //头结点 ,headNode.next 就是队头结点的指针
Node* pRearNode; //表的最后一个结点的指针,队尾结点的指针
int size; //队列实际元素个数
public:
LinkedQueue():headNode(),pRearNode(&headNode),size(0)
{
//初始化时, pRearNode 指向 headNode
}
~LinkedQueue()
{
clear();
}
void clear()
{
Node*p = headNode.next;
Node*t;
while(p!=NULL){
t = p;
p=p->next;
delete t;
}
//回归初始状态
headNode.next = NULL;
pRearNode = &headNode;
size=0;
}
bool isEmpty()const
{
#ifndef NDEBUG
if(size==0){ //调试用
assert(pRearNode == &headNode);
}
#endif
return (size==0 && pRearNode == &headNode ) ;
}
int length()const
{
return size;
}
//入队
bool enQueue(int e)
{
Node*p_new = new Node(e,NULL);
pRearNode->next = p_new;
pRearNode = p_new;
size++;
}
//出队
bool deQueue()
{
if(isEmpty()) return false; //如果队为空
Node*p_del = headNode.next; //获取待删结点的指针
headNode.next = (headNode.next)->next; //跳过,链接
//如果删除的是最后一个结点。则应该重新赋值pRearNode,指向headNode
if(pRearNode == p_del) pRearNode = &headNode;
delete p_del;
size--;
}
//获取队尾元素
bool getRear(int& e) const
{
if(!isEmpty()){
e = pRearNode->element;
return true;
}
return false;
}
//获取队头元素
bool getFront(int& e)const
{
if(!isEmpty()){
e = (headNode.next)->element;
return true;
}
return false;
}
};
int main()
{
using namespace std;
LinkedQueue queue;
printf("length is :%d\n",queue.length());
printf("is empty? :%d\n",queue.isEmpty());
puts("\n----------put 10 elements to the queue-----------");
for(int i=0;i<10;++i){
queue.enQueue(i);
}
printf("length is :%d\n",queue.length());
printf("is empty? :%d\n",queue.isEmpty());
printf("--------pop 15 elements from the queue-------------\n");
for(int i=0,e;i<15;++i){
queue.getFront(e);
if(queue.deQueue())
printf("%d ",e);
}
printf("\nlength is :%d\n",queue.length());
printf("is empty? :%d\n",queue.isEmpty());
return 0;
}
循环队列
也可以使用顺序结构(数组)来实现队列,将队头存放在数组开头的位置, 但是我们会遇到一个问题:如果执行出队后,array[0]就空出来了,但是又不能被利用,因为队列只能在队尾追加元素,这样就会造成“假满”的情况。
我们可以将一个固定长度的数组在逻辑上臆造成一个环形。这样,队头元素存放的位置不是固定的,他会随着 出队 动态改变,而队尾元素也会随着入队 动态改变。可以将front和rear变量看做是2个小孩子围着一颗树你追我赶一样,只要他们在追赶过程中没有相遇,队列就不是满的。 这样就会让每个数组空间都能得到利用。
具体实现要点
1、设一个变量front,保存队头元素在数组中的位置。设一个rear变量,保存下一个即将入队元素在数组中的位置。初始化时: front=rear = 0
2、设一个常量QUEUE_CAPACITY,保存队列的最大容量。
3、在出队后,front变为 front = (front + 1) %QUEUE_CAPACITY 。例如队列最多容量为5,某一时刻front的值为4,则 (4+1)%5 =0 ,front就又会变成0。
在入队后,rear变为: rear = (rear + 1) %QUEUE_CAPACITY。
我们必须让循环队列的索引值限制在一定的范围内(长度我n的数组的索引一定是0~n-1),而不是让rear一直加1或者front一直减1。可以使用数学问题去解决:我们知道:将一个数M对n取模后,得到的结果将被映射到 0~n-1之间,循环队列就是利用的这个特点来完成索引的变化的。
实现方式1
设一个变量size,保存队列中元素的实际个数。每次出队,size减1,入队,size加1。而队列的 空,满,队列长度都需要使用他来实现。
复制代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>
class CycleQueue
{
private:
enum{QUEUE_CAPACITY = 10}; //内部常量,存储循环队列的最大容量
int*elements ; //存储元素的数组
int front; //保存队头结点的索引
int rear; //保存下一个即将入队元素在数组中的索引。
int size; //保存队列的实际容量
public:
CycleQueue()
{
elements = new int[QUEUE_CAPACITY];
front = rear=0;
size =0;
}
~CycleQueue()
{
delete[] elements;
}
void clear()
{
//回归初始状态
front = rear=0;
size =0;
}
bool isEmpty()const
{
return size ==10;
}
bool isFull()const
{
return size == QUEUE_CAPACITY;
}
int length()const
{
return size;
}
//入队
bool enQueue(int e)
{
if(isFull()) return false;
elements[rear] = e;
rear = (rear+1)%QUEUE_CAPACITY;
size++;
return true;
}
//出队
bool deQueue()
{
if(isEmpty()) return false;
front = (front+1)%QUEUE_CAPACITY;
size --;
return true;
}
//获取队尾元素
bool getRear(int& e) const
{
if(isEmpty()) return false;
e= elements[rear-1];
return true;
}
//获取队头元素
bool getFront(int& e)const
{
if(isEmpty()) return false;
e= elements[front];
return true;
}
};
实现方式2
可以不使用size变量,另一种方法也可以实现队列的 空,满,队列长度的获取。即:让队列空出一个元素空间,我称他为标记空间。因此数组长度为n的循环队列,则只能存储n-1个元素。
这个标记空间是循环队列中队尾元素逻辑上的后一个数组空间,但是这个空间在数组中的实际位置也是随着出队,入队动态变化的。
ARRAY_CAPACITY是内部数组实际的容量,他的值是QUEUE_CAPACITY+1。因为队列容器比数组容量少一。
空判断:rear == front ? ”空“:“不为空”
满判断:(rear+1)%ARRAY_CAPACITY == front ? ”满“:“不为满”
实际元素个数:(rear - front + ARRAY_CAPACITY) % ARRAY_CAPACITY
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cassert>
class CycleQueue
{
private:
enum{QUEUE_CAPACITY=10}; //储循环队列的最大容量.
enum{ARRAY_CAPACITY=QUEUE_CAPACITY+1}; //数组容量
int*elements ; //存储元素的数组
int front; //保存队头结点的索引
int rear; //保存下一个即将入队元素在数组中的索引。
public:
CycleQueue()
{
elements = new int[ARRAY_CAPACITY];
front = rear=0;
}
~CycleQueue()
{
delete[] elements;
}
void clear()
{
//回归初始状态
front = rear=0;
}
bool isEmpty()const
{
return front == rear;
}
bool isFull()const
{
return (rear+1)%ARRAY_CAPACITY == front;
}
int length()const
{
return (rear-front + ARRAY_CAPACITY)%ARRAY_CAPACITY;
}
//入队
bool enQueue(int e)
{
if(isFull()) return false;
elements[rear] = e;
rear = (rear+1)%ARRAY_CAPACITY;
return true;
}
//出队
bool deQueue()
{
if(isEmpty()) return false;
front = (front+1)%ARRAY_CAPACITY;
return true;
}
//获取队尾元素
bool getRear(int& e) const
{
if(isEmpty()) return false;
e= elements[rear-1];
return true;
}
//获取队头元素
bool getFront(int& e)const
{
if(isEmpty()) return false;
e= elements[front];
return true;
}
};
int main()
{
using namespace std;
CycleQueue queue;
printf("length is :%d\n",queue.length());
printf("is empty? :%d\n",queue.isEmpty());
puts("\n----------put 10 elements to the queue-----------");
for(int i=0;i<10;++i){
queue.enQueue(i);
}
printf("length is :%d\n",queue.length());
printf("is empty? :%d\n",queue.isEmpty());
printf("--------pop 5 elements from the queue-------------\n");
for(int i=0,e;i<5;++i){
queue.getFront(e);
if(queue.deQueue())
printf("%d ",e);
}
printf("\nlength is :%d\n",queue.length());
printf("is empty? :%d\n",queue.isEmpty());
puts("\n----------put 2 elements to the queue-----------");
for(int i=0,e;i<2;++i){
queue.enQueue(i+100);
}
printf("\nlength is :%d\n",queue.length());
printf("is empty? :%d\n",queue.isEmpty());
printf("--------pop all elements from the queue-------------\n");
while(!queue.isEmpty() ){
int e;
queue.getFront(e);
if(queue.deQueue())
printf("%d ",e);
}
printf("\nlength is :%d\n",queue.length());
printf("is empty? :%d\n",queue.isEmpty());
return 0;
}
双端队列
双端队列是一种两端都可以 删除元素和追加元素的线性结构。双端队列比普通的队列更加灵活。
如果我们在使用时,自己限制自己的操作行为,则可以将双端队列当成其它的数据结构来使用。
如果我们对一个双端队列只调用他的removeFirst() 和 addLast() ,那么就是当做一个队列使用。
如果我们对一个双端队列只调用他的addLast() 和 removeLast() 【或者只调用addFirst和removeFirst()】,那么就是当做一个栈使用。
双端队列可以用双向链表实现。