# 队列 队列是限定在一端进行插入,另一端进行删除特殊线性表。 就像排队买东西,排在前面的人买完东西后离开队伍(删除),而后来的人总是排在队伍未尾(插入)。 通常把队列的删除和插入分别称为出队和入队。允许出队的一端称为队头,允许入队的一端称为队尾。所有需要进队的数据项,只能从队尾进入,队列中的数据项只能从队头离去。由于总是先入队的元素先出队(先排队的人先买完东西),这种表也称为先进先出(FIFO)表。 队列可以用数组Q[m+1]来存储,数组的上界m即是队列所容许的最大容量。在队列的运算中需设两个指针: head:队头指针,指向实际队头元素的前一个位置 tail:队尾指针,指向实际队尾元素所在的位置 一般情况下,两个指针的初值设为0,这时队列为空,没有元素。图1 (a)画出了一个由6个元素构成的队列,数组定义Q[11]。 Q[i] i=3,4,5,6,7,8 头指针head=2,尾指针tail=8。 队列中拥有的元素个数为:L=tail-head现要让排头的元素出队,则需将头指针加1。即head=head+1这时头指针向上移动一个位置,指向Q[3],表示Q[3]已出队。见图1 (b)。 如果想让一个新元素入队,则需尾指针向上移动一个位置。即tail=tail+1这时Q[9]入队,见图1 (c)。 当队尾已经处理在最上面时,即tail=10,见图1 (d),如果还要执行入队操作,则要发生“上溢”,但实际上队列中还有三个空位置,所以这种溢出称为“假溢出”。 ![截屏2020-12-26 下午10.38.23](https://picreso.oss-cn-beijing.aliyuncs.com/%E6%88%AA%E5%B1%8F2020-12-26%20%E4%B8%8B%E5%8D%8810.38.23.png) ​ 克服假溢出的方法有两种。 一种是将队列中的所有元素均向低地址区移动,显然这种方法是很浪费时间的; 另一种方法是将数组存储区看成是一个首尾相接的环形区域。当存放到n地址后,下一个地址就"翻转"为1。 在结构上采用这种技巧来存储的队列称为循环队列,见图2 ![截屏2020-12-26 下午10.39.08](https://picreso.oss-cn-beijing.aliyuncs.com/%E6%88%AA%E5%B1%8F2020-12-26%20%E4%B8%8B%E5%8D%8810.39.08.png) 循环队的入队算法如下: 1、tail=tail+1; 2、若tail=n+1,则tail=1; 3、若head=tail尾指针与头指针重合了,表示元素已装满队列, 则作上溢出错处理; 4、否则,Q[tail]=x,结束(x为新入出元素)。 队列和栈一样,有着非常广泛的应用。 考虑一个分时系统,如果一台计算机联有四个终端,即允许四个用户同时使用这一台计算机。 那么,计算机系统必须设立一个队列, 用以管理各终端用户使用CPU的请求。 当某个用户要求使用CPU时,相应的终端代号就入队(插入队尾),而队头的终端用户则是CPU当前服务的对象。 我们考虑最简单的情况, 对于当前用户(队头),系统每次分配一个为时间片的时间间隔,在一个时间片内,如果当前用户的作业没有结束,则该终端用户的代号出队后重新入队,插入队尾,等待下一次CPU服务。 如果某个用户的作业运行结束,则先退出,出队后不再入队,整个运行过程就是各终端代号不断地入队、出队,CPU 轮流地为n(n≤4)个终端用户服务。 由于计算机的运行速度极快,所以,对于每个终端用户来说,似乎计算机是单独在为其服务。 和线性表一样,栈和队可以采用链表存储结构,当要实现多个栈共享内存或多个队共享内存时,选择链式分配结构则更为合适。 ## 例题 【例1】假设在周末舞会上,男士们和女士们进入舞厅时,各自排成一队。跳舞开始时,依次从男队和女队的队头上各出一人配成舞伴。规定每个舞曲能有一对跳舞者。若两队初始人数不相同,则较长的那一队中未配对者等待下一轮舞曲。现要求写一个程序,模拟上述舞伴配对问题。 输入: 第一行两队的人数 第二行舞曲的数目 【分析】:设计两个队列分别存放男士和女士。每对跳舞的人一旦跳完后就回到队尾等待下次被选。如m=4 n=3 k=6 ![截屏2020-12-26 下午10.40.47](https://picreso.oss-cn-beijing.aliyuncs.com/%E6%88%AA%E5%B1%8F2020-12-26%20%E4%B8%8B%E5%8D%8810.40.47.png) ``` c 【参考程序】 #include #include using namespace std; int a[10001],b[10001],k1=1,k,i,f1=1,r1,f2=1,r2; main() { int m,n; cin>>m>>n; for (i=1;i<=m;i++) a[i]=i; for (i=1;i<=n;i++) b[i]=i; cin>>k; r1=m; r2=n; while (k1<=k) { printf("%d %d\n",a[f1],b[f2]); r1++; a[r1]=a[f1]; f1++; //第一次a[m+1]=a[1]=1,第二次a[m+2]=a[2]=2,如此循环 r2++; b[r2]=b[f2]; f2++; //第一次b[n+1]=b[1]=1,第二次b[n+2]=b[2]=2,如此循环。 k1++; } return 0; } ``` 【例2】集合的前N个元素:编一个程序,按递增次序生成集合M的最小的N个数,M的定义如下: ​ (1)数1属于M; ​ (2)如果X属于M,则Y=2*x+1和Z=3*x+1也属于M; ​ (3)此外再没有别的数属于M。 【分析】 可以用两个队列a和b来存放新产生的数,然后通过比较大小决定是否输出,具体方法如下: (1)令fa和fb分别为队列a和队列b的头指针,它们的尾指针分别为ra和rb。 初始时,X=1,fa=fb=ra=rb=1; (2)将2*x+1和3*x+1分别放入队列a和队列b的队尾,尾指针加1。 即:a[r]←2*x+1,b[r]←3*x+1,r←r+1; (3)将队列a和队列b的头结点进行比较,可能有三种情况:(A)a[ha]>b[hb] (B)a[ha]=b[hb] (C)a[ha] using namespace std; int a[1001],b[1001],x=1,fa=1,fb=1,ra=0,rb=0,total=1,n,i; main() { printf("N="); scanf("%d",&n); while (total<=n) { printf("%d ",x); a[++ra]=2*x+1; b[++rb]=3*x+1; if (a[fa]>b[fb]) x=b[fb++]; else if (a[fa] using namespace std; const int n=10,m=4; //设有10个人,报到4的人出列 int a[n+1],j=n,k=1,p=0; main() { for (int i=1;i using namespace std; char a[51][51]; bool b[51][51]; int n,m,i,j,s=0,c[5]={1,0,-1,0,1}; void f(int,int); main() { scanf("%d%d\n",&n,&m); for (i=0;i-1&&x+c[i]-1&&y+c[i+1] #include #include using namespace std; int dx[12]={-2,-2,-1,1,2,2,2,2,1,-1,-2,-2}, dy[12]={-1,-2,-2,-2,-2,-1,1,2,2,2,2,1}; int main() { int s[101][101],que[10000][4]={0},x1,y1,x2,y2; memset(s,0xff,sizeof(s)); //s数组的初始化 int head=1,tail=1; //初始位置入队 que[1][1]=1;que[1][2]=1;que[1][3]=0; cin>>x1>>y1>>x2>>y2; //读入黑马和白马的出发位置 while(head<=tail) //若队列非空,则扩展队首结点 { for(int d=0;d<=11;d++) //枚举12个扩展方向 { int x=que[head][1]+dx[d]; //计算马按d方向跳跃后的位置 int y=que[head][2]+dy[d]; if(x>0&&y>0) if(s[x][y]==-1) //若(x,y)满足约束条件 { s[x][y]=que[head][3]+1; //计算(1,1)到(x,y)的最少步数 tail++; //(1,1)至(x,y)的最少步数入队 que[tail][1]=x; que[tail][2]=y; que[tail][3]=s[x][y]; if(s[x1][y1]>0&&s[x2][y2]>0) //输出问题的解 { cout<