目录
一、队列基本概念
像栈一样,队列(queue)也是一种线性表,它的特性是先进先出,插入在一端,删除在另一端。就像排队一样,刚来的人入队(push)要排在队尾(rear),每次出队(pop)的都是队首(front)的人。
- 队头:允许删除的一端
- 队尾:允许插入的一端
二、顺序队列
1.队列的顺序存储类型:
#define MaxSize 50
typedef struct{
ElemType data[MaxSize];
int front, rear;
}SqQueue;
初始状态(队空条件):
Q.front == Q.rear == 0;
2.front 和 rear分别指示队头和队尾元素的位置:
front指向队头,rear指向队尾的后一位置(一般默认情况),或者front指向队头的前一位置,rear指向队尾。
- 进队操作:队不满时,先送值到队尾元素,再将队尾指针加一
- 出队操作:队不空时,先取队头元素值,再将队头指针加一
注意:Q.rear == MaxSize不能作为队列满的条件,因为有可能存在假溢出(上溢出):
比如说5个元素入队,出来4个元素,这时队列里面仅剩下一个元素,但是由于整体上元素向前移动了4个格,最后一个元素就顶在了最上面一个格,队列其实好几个格都是空的,但是另一个层面来讲却已经满了,所以称作这种情况为假溢出。
为了避免这种假溢出,我们下面来看循环队列↓↓↓
三、循环队列
将顺序队列臆造为一个环装的空间,即把存储队列元素的表从逻辑上视为一个环。当队首指针Q.front = Maxsize - 1后,再前进一个位置就自动到0,这可以用除余来实现。
- 初始时:Q.front = Q.rear = 0
- 队首指针进1:Q.front = (Q.front+1) % Maxsize;
- 队尾指针进1:Q.rear = (Q.rear+1) % MaxSize
- 队列长度:(Q.rear - Q.front + MaxSize)% MaxSize
出队入队时,指针都按照顺时针方向进一。
队空和队满都会Q.front ==Q.rear,为了区分队空和队满,有以下三种方式:
1.牺牲一个单元来区分队空和队满:队头指针在队尾指针的下一位置作为队满的标志
- 队满条件:(Q.rear + 1)% MaxSize == Q.front
- 队空条件仍:Q.front == Q.rear
- 队列中元素的个数:(Q.rear - Q.front + MaxSize)% MaxSize
2.类型中添加:
- 元素个数:比如说结构体中添加size:size == 8个元素,size == 0空。
- 是否满了:比如说结构体中添加flag:flag == 0空,flag == 1满。
循环队列的操作
(1)循环队列初始化
//循环队列初始化
void InitQueue(SqQueue &Q){
Q.rear = Q.front = 0;
}
(2)循环队列判队空
//循环队列判队空
bool isEmpty(SqQueue Q){
if(Q.rear == Q.front) return true;
else return false;
}
(3)循环队列入队
//循环队列入队
bool EnQueue(SqQueue &Q, ElemType x){
if((Q.rear + 1) % MaxSize == Q.front) return false; //队满
Q.data[Q.rear] = x;
Q.rear = (Q.rear + 1) % MaxSize; //队尾指针加1取模
return true;
}
(4)循环队列出队
//循环队列出队
bool DeQueue(SqQueue &Q, ElemType &x){
if(Q.rear == Q.front) return false; //队空
x = Q.data[Q.front];
Q.front = (Q.front + 1) % MaxSize; //队头指针加1取模
return true;
}
四、链队列
队列的链式存储称为链队列,实际上是同时带有头指针和尾指针的单链表。头指针指向队头结点,尾指针指向队尾结点,即单链表的最后一个结点。
//队列的链式存储类型
typedef struct{
ElemType data; //结点数据
struct LinkNode *next; //结点指针
}LinkNode;
typedef struct{
LinkNode *front, *rear; //队列包含front结点和rear结点
}LinkQueue;
- 当Q.rear == NULL且Q.rear == NULL时,链式队列为空。
- 不带头结点的链式队列在操作上往往比较麻烦,因此通常将链式队列设计成带(头结点的单链表),这样插入和删除操作就统一了。
- 用单链表表示的链式队列特别适合于数据元素变动比较大的情形,而且不存在队列满而且溢出的问题。假如程序中需要多个队列,与多个栈的情况一样,最好使用链式队列,这样就不会出现存储分配不合理和“溢出”的问题。
链式队列的基本操作
(1)链式队列初始化:新建一个头结点,两指针均指向此结点,结点后指空。
//链式队列初始化
void InitQueue(LinkQueue &Q){
Q.front = Q.rear = (LinkNode*)malloc(sizeof(LinkNode));
Q.front->next = NULL;//只有一个头结点
}
(2)链式队列判空:Q.rear == Q.front就是空
//循环队列判队空
bool isEmpty(SqQueue Q){
if(Q.rear == Q.front) return true;
else return false;
}
(3)链式队列入队:
- 创建新结点,将结点进行赋值:值为x,指针指空
- 将新结点链接到链表尾,将表尾后移
//链式队列入队
void EnQueue(LinkQueue &Q, ElemType x){
LinkNode *s = (ListNode *)malloc(sizeof(LinkNode));//创建新结点
s->data = x; s->next = NULL;
Q.rear->next = s;
Q.rear = s;
}
(4)链式队列出队
- 判空,待删除结点p在头结点后,将p值赋给x(如果链表里只有一个结点,即p是尾结点,那么将尾结点指向头结点,否则尾结点不用动),然后删除掉p就完成啦!
//链式队列出队
bool DeQueue(LinkQueue &Q, ElemType &x){
if(Q.rear == Q.front) return false;
LinkNode *p = Q.front->next;
x = p->data;
Q.front->next = p->next;
if(Q.rear == p) Q.rear = Q.front;
free(p);
return true;
}
五、双端队列
双端队列是指允许两端都可以入队和出队操作的队列。其元素的逻辑结构还是线性结构,将队列的两端分别称为前端和后端,两端都可以入队和出队。
- 输出受限的双端对列:允许在一端进行插入和删除,另一端只允许插入
- 输入受限的双端队列:允许在一端进行插入和删除,另一端只允许删除
这里需要做习题哦~明天继续更新习题 [狗头]
六、题目
(1)循环队列存储在数组A[0...n]中,入队时的操作是
rear = (rear + 1) mod (n + 1);
队满条件是:
Q.front == (Q.rear + 1)% maxSize;
(2)已知循环队列的存储空间为数组A[21],front指向队头元素的前一位置,rear指向队尾元素,假设当前front和rear的值分别为8和3,则该队列的长度为( )
队列的长度为(rear - front + maxSize)% maxSize = (3 - 8 + 21)% maxSize = 16;
所以答案 16
(3)若用数组A[0...5]来实现循环队列,且当前rear和front的值分别为1和5,当从队列中删除一个元素,再加入两个元素后,rear和front的值为( )
循环队列中,每删除一个元素,队首指针front = (front + 1) % 6,每插入一个元素,队尾指针rear = (rear + 1)% 6
上述操作后,front = 0, rear = 3。所以答案 3 和 0
(4)已知循环队列存储在一维数组A[0...n-1],且队列非空时front和rear分别指向队头元素和队尾元素。若初始时队列为空,且要求第一个进入队列的元素存储在A[0]处,则初始时 front 和 rear的值分别是( )
注意:一定要注意front和rear指向哪里!到底是队头,队尾?还是队头减一,队尾?还是队头,队尾加一?
根据题意,第一个元素进入队列后存储在A[0]处,此时 front 和 rear值都为0。由于要执行(rear + 1)% n操作,所以如果入队后指针指向0,则rear初值为n-1,而由于第一个元素在 A[0]中,插入操作只改变rear指针,所以front为0不变。所以答案 0,n-1;
(5)最适合用做链队的链表是(),最不适合用做链队的链表是( )
答案:带队首指针和队尾指针的非循环链表、只带队首指针的非循环双链表
原因:
- 如果用循环链表,因为不涉及删除最后一个结点,所以不需要循环,是多余的!而在队尾添加元素时还要修改成循环的,就更多余了!
- 而下一个问题不适合做链队的链表当然是这个没有尾结点的啦,因为每次删除尾巴头指针都要走好远~
下面这种链表最适合做链队啦嘻嘻↓↓↓
做这种题建议自己模拟一下~
(6)用链式存储方式的队列进行删除操作时需要( )
答案:头尾指针可能都要修改
哇!当队列用链表形式存储时,删除元素时从队头删除,一般情况下仅需要修改头指针,但若此时队列中仅有一个元素,队尾指针也要被修改,因为仅有一个元素时,删除后队列为空,需修改尾指针为rear = front;
(7)在一个链队列中,假设队头指针为 front,队尾指针为rear,x所指向的元素需要入队,则需要执行的操作为( )
答案:rear->next = x; x->next = null; rear = x;
唉当时错选了rear->next = x; rear = x;属实不小心,一定要小心!记住!!
明日继续更~