队列
队列的基本概念
队列(Queue) 也是运算受限的线性表
- 只允许在表的一端进行插入,而在另一端进行删除→先进先出(First In First Out ,FIFO),先进入队列的成员总是先离开队列
- 队头(front) :允许进行删除的一端
- 队尾(rear) :允许进行插入的一端
- 空队列:队列中没有元素
队列的设计
定义:
- 队头(front):不存数据,只是指向队列队头
- 队尾(rear)
操作:
- 创建一个空队列
- 销毁已存在的队列
- 将队列清为空队列
- 判断是否为空队列
- 返回队列的长度
- 返回队列的队头元素
- 向队列的队尾插入元素e
- 删除队头元素,并返回其值
- 从队头到队尾依次对队列的每个数据元素调用函数visit()
队列的具体实现
链式表示
链队列:用链表表示的队列
- 数据元素结点,设有头结点
- 队列的队头指针和队尾指针
typedef struct Node {
ElemType data;
struct Node *next;
}QNode;
typedef struct {
QNode *front;
QNode *rear;
}LinkedQueue;
// 1. 链队列的初始化,构造一个空队列
bool InitQueue(LinkedQueue *lq){
lq->front=lq->rear=(QNode *)malloc(sizeof(QNode));
if(!lq->front) return false;
lq->front->next=NULL;
return true;
}
// 2. 取队列的长度
int GetLen(LinkedQueue *lq){
int count=0;
QNode *p;
if(IsQueueEmpty(lq)==0){
p=lq->front->next;
while(p!=lq->rear){
count++;
p=p->next;
}
count++;
}
return count;
}
// 3. 判断队列是否为空
int IsQueueEmpty(LinkedQueue *lq){
if(lq->front == lq->rear) return 1;
else return 0;
}
// 4. 查看队头元素
bool GetFront(LinkedQueue *lq,ElemType *e){
if(lq->front == lq->rear) return false;
*e=lq->front->next->data;
return ture;
}
// 5. 元素入队(尾)
bool Enqueue(LinkedQueue *lq,ElemType e){
QNode *p;
p=(QNode *)malloc(sizeof(QNode));
if(!p) return false;
p->data =e; p->next=NULL;
lq->rear->next=p; //修改尾指针
lq->rear=p;
return true;
}
//6. (队头)元素出队
bool Dequeue(LinkedQueue *lq,ElemType *e){
QNode *p;
if (lq->front == lq->rear) return false;
//空队列的话,则出错
p = lq->front->next; //p指向第一个结点
*e = p->data;
lq->front->next =p->next; //修改头结点的指针
if(lq->rear==p) //删仅有的一个元素时,需修改尾指针
lq->rear=lq->front;
free(p);
return true;
}
顺序表示
顺序队列:利用一组连续的存储单元(一维数组) 依次存放从队头到队尾的各个元素
在非空队列里,队头指针始终指向队头元素,而队尾指针始终指向队尾元素的下一位置
- 初始化:front=rear=0
- 判队列为空:front==rear
- 判队列满:rear==MAXQUEUESIZE
- 入队:将新元素插入rear所指的位置,然后rear加1
- 出队:删去front所指的元素,然后front加1并返回被删元素
#define MAXQUEUESIZE 100
typedef struct queue {
ElemType Queue_array[MAXQUEUESIZE] ;
int front; // 队头指针
int rear; // 队尾指针
int queueSize; //队列空间的大小
}SqQueue;
PS:存在假溢出问题!
在入队和出队操作中,头、尾指针只增加不减小,致使被删除元素的空间永远无法重新利用
循环队列
循环队列:将为队列分配的向量空间看成为一个首尾相接的圆环
- 在循环队列中进行出队、入队操作时,队头、队尾指针仍要加1
- 但当队头、队尾指针到达
MAXQUEUESIZE-1
时,其加1操作的结果是指向0 - 进队:在队尾加元素,然后
rear = (rear+1) % MAXQUEUESIZE
- 出队:取队头元素,然后
front = (front+1) % MAXQUEUESIZE
- 判断队空的条件
(rear+1) % MAXQUEUESIZE == front
(少用一个元素空间,让rear所指的单元始终为空。约定:以“队列头指针在队尾指针的下一个位置上”为队满的标志)←这只是一种判断队列满的方法,也可以增加队列计数器或标志位
#define MAXQUEUESIZE 100
typedef struct queue {
ElemType *base; // 动态分配的存储空间
int front; // 队头指针,若队列不空,指向队头元素
int rear; // 队尾指针,若队列不空,指向队列尾元素的下一个位置
} CircularQueue;
// 队列初始化,构造一个空循环队列
bool InitQueue(CircularQueue *cq){
cq->base = (ElemType *)malloc (MAXQUEUESIZE*sizeof(ElemType));
if(!cq->base) return false;
cq->front=0;cq->rear=0;
return true;
}
// 返回队列的元素个数,即队列的长度
int GetLen(CircularQueue *cq) {
return ((cq->rear - cq->front + MAXQUEUESIZE)%MAXQUEUESIZE);
}
// 插入元素e为Q的新的队尾元素
bool Enqueue(CircularQueue *cq,ElemType e) {
if((cq->rear+1)%MAXQUEUESIZE == cq->front)
return false; //队列满
cq->base[cq->rear]=e;
cq->rear=(cq->rear+1) % MAXQUEUESIZE;
return true;
}
// 若队列不空,则删除Q的队头元素,
// 用e返回其值,并返回true; 否则返回false
bool Dequeue(CircularQueue *cq,ElemType *e) {
if(cq->front == cq->rear) return false;
*e=cq->base[cq->front];
cq->front=(cq->front +1) % MAXQUEUESIZE;
return true;
}
队列的应用举例
杨辉三角/二项式系数生成
杨辉三角:
C
(
n
,
i
)
=
C
(
n
−
1
,
i
−
1
)
+
C
(
n
−
1
,
i
)
C(n,i)= C(n-1,i-1) + C(n-1,i)
C(n,i)=C(n−1,i−1)+C(n−1,i)
=>
𝑁
𝑒
𝑥
𝑡
𝑅
𝑜
𝑤
𝑖
=
𝐶
𝑢
𝑟
𝑅
𝑜
𝑤
i
−
1
+
𝐶
𝑢
𝑟
𝑅
𝑜
𝑤
𝑖
𝑁𝑒𝑥𝑡𝑅𝑜𝑤_𝑖=𝐶𝑢𝑟𝑅𝑜𝑤_{i-1}+ 𝐶𝑢𝑟𝑅𝑜𝑤_𝑖
NextRowi=CurRowi−1+CurRowi
想法:用变量存
𝐶
𝑢
𝑟
𝑅
𝑜
𝑤
i
−
1
𝐶𝑢𝑟𝑅𝑜𝑤_{i-1}
CurRowi−1,队头为
𝐶
𝑢
𝑟
𝑅
𝑜
𝑤
𝑖
𝐶𝑢𝑟𝑅𝑜𝑤_𝑖
CurRowi
int main(){
//生成总共total_row行的杨辉三角值
CircularQueue q; ElemType e;
int r,total_row, cur_row_i_1=0, cur_row_i=0,next_row_i;
InitQueue(&q); Enqueue(&q,1); Enqueue(&q,1);
for(r=1; r<total_row;r++){
Enqueue(&q, 0);
//用0来作为行的分隔
for(int c=1;c<=r+2;c++) {
Dequeue(&q,&e);
cur_row_i = e;
if (e) printf("%d ", cur_row_i);
next_row_i = cur_row_i + cur_row_i_1;
Enqueue(&q,next_row_i);
cur_row_i_1=cur_row_i;
}
printf("\n");
}
return 0;
}
迷宫寻路-搜索问题,寻找从入口到出口的最短路径
题目见这篇文章中的迷宫问题:https://blog.youkuaiyun.com/weixin_45770491/article/details/125271327?spm=1001.2014.3001.5501
寻找从入口到出口的最短路径,修改栈版迷宫算法,利用队列针对迷宫进行广度优先搜索
迷宫:用2维数组表示
有待搜索的通道块:用队列表示
轨迹(搜索过的块):trajectories
起始方块入队列Q;
while (队列Q非空) {
取Q的队头e;
若e是终点,则 {
从轨迹中回溯路径,
打印路径,
返回True
}
否则,将该方块记录到轨迹;
依次取e的下一步可以走到的方块s;
如果s是可通的方块 ,则 {
则在该方块中记录该方块的前一个位置,
将该方块入队,
将该点设置为走过// FootPrint(s);
}
}
返回False
/********************************************/
typedef struct Node {
ElemType data;
struct Node *next;
}QNode;
typedef struct {
QNode *front;
QNode *rear;
}LinkedQueue;
char maze[10][10];
//迷宫的墙:X;
//没有走过的通道块:空格;
//走过标记:*;路径:+
typedef struct{ //迷宫的坐标
int r,c; //r 表示行,c表示列
} PosType;
//表示路径中的一通道块
typedef struct{
PosType seat;
//通道块在迷宫中的坐标位置
PosType former; //从哪个块走过来的
int di; //从此通道块走向下一个通道块的方向
} ElemType;
ElemType trajectories[1000];
int traP=0;
运动会日程安排
即——子集划分问题:将n个元素组成的集合A划分成k个互不相交的子集A1,A2,…,Ak (k≤n),使同一子集中的元素均无冲突关系
有同一运动员参加的项目抽象为**“冲突”关系**
项目之间的冲突关系为:
R = {(1,4),(4,8),(1,8),(1,7),(8,3),(1,0),(0,5),(1,5),(3,4),(5,6),(5,2),(6,2),(6,4)}
数据结构:
- 设集合共有n个元素
- 用队列sq存放集合元素
- 用矩阵conflictMatrix[n][n]表示元素之间的冲突关系:
conflictMatrix[i][j]=1, 如果i,j有冲突
conflictMatrix[i][j]=0, 如果i,j无冲突 - 用数组result[n]存放每个元素的子集/分组编号
- 用工作数组clash[n]记录与第k组已入组元素有冲突的元素情况(当组号为k时)
-每次新开辟一组时,令clash数组各分量的值均为0,当序号为 head 的元素入组时,将和该元素发生冲突的信息记入clash 数组
-clash的引入可以减少重复察看conflictMatrix数组的时间
算法思想:
利用循环筛选
从第一个元素开始,无冲突的元素划归为一个子集;
再将剩下的元素重新找出互不冲突的元素,划归第二个子集
依次类推,直到所有元素都进入某个子集为止
初始化队列;全体集合元素入队列;
取队头元素,组号为1,clash的值被设置为队头元素在矩阵中的行值;
While(队列不空) {
取队头元素x;
若它与当前组的元素没有冲突,即Clash[x]==0 {
在result中设置该元素对应的分组号;
修改clash记录 (即,在原clash记录之上叠加上该元素的
冲突情况)
}
否则,将x再次入队
判断是否走完一轮 (即,若当前队列的队头的值小于前次
取的队头的值,那么 走完一轮)
若走完一轮,则:
取队头元素x,组号加1,数组clash初始化为全0
}
演示: