1.基本概念:
队列(Queue):队列和栈一样,也是一种操作受限的线性表,它只允许在一端进行插入,而在另一端进行删除。
重要术语:队头、队尾、空队列
队头:允许删除的一端
队尾:允许插入的一端
队空条件:Q.front==Q.rear
队列已满的条件:队尾指针的下一个位置是队头,即(Q.rear+1)%MaxSize==Q.front,这样会牺牲掉一个存储单元
循环队列:用模运算将存储空间在逻辑上变为环状

提问:为什么队列已满的条件不是Q.rear==MAXSIZE呢?
答:如果通过队头指针释放了一些数据元素,那么队头指针会向后移动,而队尾指针不变,这时,虽然满足Q.rear==MAXSIZE,但队列未满
队列的特点:先进先出(FIFO -- first in first out)
我们规定让队头指针指向队头元素,让队尾指针指向队尾元素的后一个位置(即接下来要插入数据元素的位置)
2.队列的顺序实现:
(1)定义一个顺序队列:
typedef struct {
int a;
double b;
}*ElemType;
typedef struct {
ElemType data[MAXSIZE]; //用静态数组存放队列元素
int front, rear; //队头指针和队尾指针
}SqQueue; //Sq - sequence 顺序
(2)队列的初始化和判空:
void InitQueue(SqQueue& Q) { //初始时,根据规定,队头指针和队尾指针都指向0位置
Q.front = Q.rear = 0;
}
bool Empty(SqQueue Q) {
if (Q.front == Q.rear)
return true;
return false;
}
(3)入队:
bool EnQueue(SqQueue& Q, ElemType e) {
if ((Q.rear + 1) % MAXSIZE == Q.front)
return false; //队列已满则报错
Q.data[Q.rear] = e; //将元素e插入队尾
Q.rear = (Q.rear + 1) % MAXSIZE; //使队尾指针后移一位
return true;
}
(4)出队:
bool DeQueue(SqQueue& Q, ElemType& x) {
if (Q.front == Q.rear) //空队列
return false;
x = Q.data[Q.front]; //将队头元素赋给x
Q.front = (Q.front + 1) % MAXSIZE; //队头指针后移一位
return true;
}
(5)获取队头元素:
bool GetHead(SqQueue& Q, ElemType& x) { //获取队头元素
if (Q.front == Q.rear)
return false; //队空则报错
x = Q.data[Q.front]; //将队头元素赋给x
return true;
}
附:
注意:这样的实现方式会造成一个ElemType空间的浪费,但是要想不浪费这个空间,又要达到能够正确给出队列已满和空队列的条件,我们应该如何做到呢?
这个问题的解题思路一共有两种:
a.在原来的队列定义中增加一个int 类型的size变量并赋初值为0,用来记录链表的长度;
每插入一个元素,就让size++;每删除一个元素,就让size--;
这样,我们就可以根据size的大小来判断链表是否已满。此时队列已满的条件是size==MAXSIZE,队列为空的条件是size==0。
定义如下:
typedef struct {
ElemType data[MAXSIZE];
int front, rear; //队头指针和队尾指针
int size; //用来记录队列的长度
}SqQueue;
void InitQueue(SqQueue& Q) {
Q.front = Q.rear = 0;
Q.size = 0;
}
b.在原来的队列定义中增加一个int类型的tag变量,用来记录最近的一次操作是插入元素还是删除元素;
规定若最近的一次操作是插入,则tag=1;若最近的一次操作是删除,则tag=0;
由于只有插入操作才能使得队列变满,而只有删除操作才能使队列变空;
这样,我们就可以根据tag以及rear和front指针来判断队列是否已满:若rear和front相等,且tag==1,即最近的一次操作是插入,那么队列已满;反之,若tag==0,那么最近的一次操作是删除,那么队列为空;
即此时队列已满的条件是:Q.rear==Q.front&&tag==1
此时队列为空的条件是:Q.rear==Q.front&&tag==0
定义如下:
typedef struct {
ElemType data[MAXSIZE];
int front, rear; //队头指针和队尾指针
int tag; //用来记录最近的一次操作是插入还是删除
}SqQueue;
3.队列的链式实现:
(1)链式队列的定义:
typedef struct LinkNode { //链式队列的结点
ElemType data;
struct LinkNode* next;
}LinkNode;
typedef struct { //链式队列
LinkNode* front, * rear; //队头指针和队尾指针
}LinkQueue;
(2)链式队列的初始化:
//带头结点
void InitQueue(LinkQueue& Q) {
Q.front = Q.rear = new LinkNode; //开辟一个动态空间用于存放头结点
Q.front->next = NULL; //头结点的下一个结点为空
}
//不带头结点
void InitQueue(LinkQueue& Q){
Q.front = Q.rear = NULL; //将队头指针和队尾指针都置空
}
(3)链式队列的判空:
bool Empty(LinkQueue Q) {
if (Q.front == Q.rear) //队列为空的条件,也可改为Q.front->next==NULL
return true;
return false;
}
(4)队尾元素的入队:

//带头结点
void EnQueue(LinkQueue& Q, ElemType e) {
LinkNode* s = new LinkNode;
s->data = e; //将数据元素e放入结点s中
s->next = NULL; //因为插入的结点e是最后一个结点,因此其下一个结点为NULL
Q.rear->next = s; //新结点插入Q.rear之后
Q.rear = s; //队尾指针指向最后一个结点
}
//不带头结点
void EnQueue(LinkQueue& Q, ElemType e) {
LinkNode* s = new LinkNode;
s->data = e;
s->next = NULL;
if (Q.front == NULL) { //在空队列中插入第一个元素
Q.front = s;
Q.rear = s; //让队头指针和队尾指针都指向第一个结点
}
else {
Q.rear->next = s; //新结点插入到rear结点之后
Q.rear = s; //修改队尾指针
}
}
(5)队头元素出队:
//带头结点
bool DeQueue(LinkQueue& Q, ElemType& x) {
if (Q.front == Q.rear) //队列为空,报错
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; //释放结点空间
return true;
}
//不带头结点
bool DeQueue(LinkQueue& Q, ElemType& x) {
if (Q.front == NULL)
return false; //空队列
LinkNode* p = Q.front; //p指向第一个结点
x = p->data;
Q.rear = p->next;
if (Q.rear == p) //最后一个元素出队
Q.front = Q.rear = NULL;
delete p;
return true;
}
本文介绍了队列的基本概念,包括队头、队尾、空队列的定义,以及队列的先进先出特点。文章详细阐述了队列已满和空队列的条件,并讨论了循环队列的概念。此外,还探讨了队列的顺序实现和链式实现,包括入队、出队操作,以及如何避免空间浪费和正确判断队列状态的两种方法。
961

被折叠的 条评论
为什么被折叠?



