关于队列的学习
原创博文,转载请注明出处。
队列也是一种线性表,但是只允许在表的一端进行插入,而在表的另一端进行删除。其操作特性是先进先出。队列常应用在在层次遍历中(如对二叉树的遍历),计算机系统中,也常用来解决如主机与外部设备之间速度不匹配的问题,和由多用户引起的资源竞争问题。
队头(front):允许删除的一端,又称队首。
队尾(rear): 允许插入的一端。
空队列:不含任何元素的空表。
队列的顺序存储 比较简单,下面我们学习一下队列的链式存储结构。
队列的链式存储类型可描述为
1 typedef struct{ //链式队列节点 2 ElemType data: 3 struct LinkNode *next; 4 }LinkNode; 5 typedef struct{ //链式队列 6 LinkNode *front, *rear; 7 }LinkQueue
当Q.front,Q.rear==NULL时,链式队列为空。为了方便插入和删除操作,通常将链式队列设计成一个带头结点的单链表。
链式队列的基本操作:
(1)初始化
void InitQueue(LinkQueue &Q){ Q.front = Q.rear = (LinkNode *)malloc(sizeof(LinkNode)); Q.front->next = NULL; }
(2)判队空
bool IsEmpty(LinkQueue Q){ if(Q.front = = Q.rear) return true; else return false; }
(3)入队
void EnQueue(LinkQueue &Q,ElemType x){ s=(LinkNode *)malloc(sizeof(LinkNode)); s->data=x;s->next=NULL; Q.rear->next=s; Q.rear=s; }
(4)出队
bool DeQueue(LinkQueue &Q,ElemType &x){ if(Q.front==Q.rear) return false;//空队 p=Q.front; //让头元素指向p x=p->data; Q.front->next=p->next; if(Q.rear==p) Q.rear==Q.front; //若原队列中只有一个结点,删除后变空 free(p); return true; }
1、如果希望循环队列中的元素都能得到利用,则需要设置一个标志域tag,并以tag的值为0或1来区分队头指针front和队尾指针rear相同时的队列状态是“空”还是“满”。使编写此结构相应的入队和出队算法。
在循环队列的类型结构中,增设一个tag的整型变量,进队时设置为1,出队时设置为0。初始时,设置为tag=0,front=0,rear=0.
队空条件:Q.front ==Q.rear 且tag==0。
队满条件: Q.front == Q.rear且tag==1。
入队操作:Q.data[Q.rear]=x;Q.rear=(Q.rear+1)/MaxSize;Q.tag=1。
出队操作: x=Q.data[Q.front];Q.front=(Q.front+1)/MaxSize;Q.tag=0。
2、Q是一个队列,S是一个空栈,实现将队列中的元素逆置的算法。
只是对队列的一系列操作是不可能将其中的元素逆置的,所以我们可以借助栈来存储队列中的元素。
void Inverser(Stack S, Queue Q){ while(Q.front!=Q.rear){ //将队列中的元素逐个地出队列,入栈 x=DeQueue(Q); Push(S,x); } while(S.top!=-1){ //逐个出栈,然后入队列 Pop(S,x); EnQueue(Q,x); } }
3、利用两个栈 S1 、S2 来模拟一个队列,已知栈的4个运算定义如下:
Push(S,x); //元素x入栈S Pop(S,x); //S出栈并将出栈的值赋给x StackEmpty(S); //判断栈是否为空 StackOverflow(S); //判断栈是否满
那么如何利用栈的运算来实现该队列的3个运算:
Enqueue; //将元素x入队 Dequeue; //出队,并将出队元素存储在x中 QueueEmpty; //判断队列是否为空
利用两个栈S1和S2 来模拟一个队列,当需要想队列中插入一个元素时,用S1存放已输入的元素,即S1 执行入栈操作。当需要出队时,则对S2执行出栈操作。由于从栈中取出元素的顺序是原顺序的逆序,所以,必须将S1 中所有元素全部出栈并入栈到S2中,再在S2 中执行出栈操作,即可实现出队操作。因此,在执行操作前必须判断S2 是否为空,否则会导致顺序混乱。当栈S1和S2都为空时,队列为空。
总结如下:
(1)对S2的出栈操作用出队,若S2 为空,则先将S1 中的所有元素送入S2 。
(2)对S1的入栈操作用入队,若S1满,必须先保证S2 为空,才能将S1中的元素全部插入到S2中去。
入队算法:
int EnQueue(Stack S1,Stack S2,ElemType e){ if(!StackOverflow(S1)){ Push(S1,x); return 1; } if(StackOverflow(S1)&&!StackEmpty(S2)){ return 0; } if(StackOverflow(S1)&&StackEmpty(S2)){ //S1满 S2空 while(!StackEmpty(S1)){ Pop(S1,x); Push(S2,x); } } Push(S1,x); return 1; }
出队算法:
void DeQueue(Stack S1,Stack S2,ElemType &x){ if(!StackEmpty(S2)){ Pop(S2,x); } else if(StackEmpty(S1){ printf("队列为空"); } else{ while(!StackEmpty(S1)){ Pop(S1,x); Push(S2,x); } Pop(S2,x); } }
判断列队为空的算法:
int QueueEmpty(Stack S1,Stack S2){ if(StackEmpty(S1)&&StackEmpty(S2)) return 1; else return 0; }
4、某汽车轮渡口,过江渡船每次能载10辆车过江。过江车辆分为客车类和货车类,上渡船有如下规定:同类车先到先上船;客车先于货车上渡船,且每上4辆客车,才允许放上一辆货车;若等待客车不足4辆,则以货车代替;若无货车等待,才允许客车都上船。设设计一个算法模拟渡口管理。
算法思想:我们可以设定三个队列,Q 是每次载渡的队列,长度为10。Q1 和Q2 分别是客车和货车的等待队列。若Q1>4,则每取4个Q1元素后再取一个Q2元素,直到Q长度为10。若Q1<4,则直接用Q2补齐。算法实现如下:
void manager(Queue q,Queue q1,Queue q2){ int i=0,j=0; //i代表渡船上客车的数量 j代表渡船上总车辆数 while(j<10){ if(!QueueEmpty(q1)&&i<4){ //客车队列非空,且未上满4辆 DeQueue(q1,x); EnQueue(q,x); j++; i++; } else if(i==4&&!QueueEmpty(q2)){ //客车已上满4辆 DeQueue(q2,x); EnQueue(q,x); j++; i=0; } else{ while(i<4&&!QueueEmpty(q2)){ //客车队列空,且未上满4辆 DeQueue(q2,x); EnQueue(q,x); j++; i++; } i=0; } if (QueueEmpty(q1)&&QueueEmpty(q1)) j=11; } }