前言
队列和栈的定义和操作实在是太相似了,直接把栈那一篇的内容复制过来做个修改,学习的重点应该在后面的循环队列。
一、定义
队列(Queue)是一种只允许在一端进行操作的线性表,不仅如此,队列还是一种先进先出(first in first off,FIFO)的数据结构。
队列可以顺序存储也可以链式存储,顺序队和顺序表的定义几乎没有区别,只是在处理数据时加了一条先进先出的限制,链队也是同样的道理。
二、顺序队的基本操作
1.定义
#define MaxSize 10 //定义队列中元素的最大个数
typedef struct
{
ElemType data[MaxSize]; //静态数组存放栈中元素
int front, rear; //队头指针和队尾指针
}SqQueue;
注意:定义栈的时候我们只定义了一个栈顶指针,而定义队列时我们定义了两个,一个指向队头一个指向队尾,这也很好理解,栈中的数据只能在栈顶进行插入和删除等操作,但队列需要在队头出队,队尾入队,所以需要两个指针。
2.初始化
bool InitStack(SqQueue& S)
{
Q.rear = 0;
Q.front = 0;
}
注意:队尾指针指向队尾元素的下一个位置(也就是接下来要插入元素的位置)。
3.入队
bool Push(SqQueue& Q, ElemType x)
{
if ((Q.rear + 1) % MaxSize == Q.front) //队列满,报错
return false;
Q.data[Q.rear] = x; //新元素入队
Q.rear = (Q.rear + 1) % MaxSize; //队尾指针加一取模
return true;
}
注意:
①取模运算a%b,如果a<=b的话,结果其实还是a自己;如果a>b的话结果就是a%b后的余数,且不论a与b是什么关系,最后的结果都小于b。这种取模运算保证了队尾指针始终在MaxSize范围内循环变化,也正因此实现了队列在逻辑上的“环状”。
②由于此时队列已经成环(循环队列),所以在判断队满的时候需要牺牲一个空间(比如有10个空间,存满9个元素时就认为队满),这样做是为了区别与队空(Q.rear == Q.front)的判断。
③循环队列中元素的个数是(rear+MaxSize-front)%MaxSize(记住即可)
此外,如果不想像上述这样,通过牺牲一个空间的方式来区别队空和队满,也可以咋定义队列的时候在结构体中加上一个size变量,用来表示队列当前有几个元素。
4.出队
bool Pop(SqQueue& Q, ElemType& x)
{
if (Q.rear == Q.front) //队空,报错
return false;
x = Q.data[Q.front]; //元素出队
Q.front = (Q.front + 1) % MaxSize //队头指针成环性加一
return true;
}
5.读取队头元素
bool GetHead(SqQueue S, ElemType& x)
{
if (Q.rear == Q.front) //队空,报错
return false;
x = Q.data[Q.front]; //读出元素
return true;
}
6.判空
bool QueueEmpty(SqQueue Q)
{
if (Q.rear == Q.front) //队空
return true;
else
return false;
}
三、链式队的基本操作
1.定义
typedef strunct LinkNode
{//队列中结点的定义
ElemType data;
struct LinkNode* next;
}LinkNode;
typedef strunct
{//链式队列的定义
LinkNode *front, *rear; //队列的队头指针和队尾指针
}LinkQueue;
注意:因为在定义上,链队和单链表没有什么区别,链队列只是在单链表的使用上加了一些限制(阉割版),所以链队的实现也有带头结点和不带头结点两种方式。
2.初始化
bool InitQueue(LinkQueue& Q)
{//带头结点的链队
Q.front = Q.rear = new LinkNode; //创建一个头结点,并使头尾指针都指向它
Q.front->next = NULL; //让这个头结点指向NULL
return true;
}
3.入队
bool Push(LinkQueue& Q, ElemType x)
{
LinkNode* s = new LinkNode; //创建一个新的结点
s->data = x;
s->next = NULL;
Q.rear->next = s; //让队尾指针当前指向的结点(刚开始是头结点)的next指向新结点
Q.rear = s; //让队尾指针指向新结点
return true;
}
注意:队列的元素入队操作类似于单链表的尾插法,也就是越晚插入的元素越远离头结点
4.出队
bool Pop(LinkQueue Q, ElemType &x)
{
if (Q.) //此时队空,无法出队
return false;
LinkNode* p = Q.front->next; //临时指针存放待出队的结点
x = p->data; //元素出队,由x带回
Q.front->next = p->next; //修改头结点的next指针
if (Q.rear = p) //如果此次出队的已经是最后一个元素
Q.rear = Q.front; //重置队尾指针,指向头结点
delete p; //释放出队结点的内存
p = NULL;
return true;
}
注意:队列的出队操作每次都是出队最靠近头结点的那一个元素
5.读出队头元素
bool GetTop(LinkQueue Q, ElemType& x)
{
if (Q.rear == Q.front) //队空,报错
return false;
x = Q.rear->data; //读出元素
return true;
}
6.判空
bool Empty(LinkQueue Q)
{
if(Q.front == Q.rear) //也可以判断头结点的next是否指向NULL
return true;
else
return false;
}