一:什么是队列:
队列和栈一样也是一种特殊的数据结构。和名称一样,队列的特性是先存储的数据会放在队列的开头,之后存储的数据会跟在前一个数据的后面,像排队一样。在输出数据的时候从队列的开头开始输出,然后按顺序依次输出。
(图片来自Jacky_Feng )
顺序队列
队列的输入和输出的实现也需要定义一个指针来实现。由于队列的入队和出队与栈不同,并不是在同一个位置进行操作,所以需要两个指针,一个指向队首,用于输出。一个指向队尾,用于输入。
const int M = 200005;
typedef struct{
int data[M];
int front,end;
}Queue,*PQueue;
int main()
{
PQueue Q;
Q = (PQueue)malloc(sizeof(Queue));
}
队列的头指针:Q->front
队列的尾指针:Q->end
入队操作是在队尾指针指向的位置加入元素,当队尾指针的大小等于队列的大小M时队满。
出队操作是在队头删除一个元素,不需要移动任何元素,而是移动队头指针指向的位置。
假溢出
在线性表和栈中会出现表中元素个数达到内存空间的最大值导致的空间溢出现象,这是正常的情况。但是队列中存在假溢出的情况。
由于在出队的过程中采取不移动元素而是移动指针的方式,会导致随着入队的进行,整个队列会往后移动。直到队尾元素到达分配空间的最大值时就会出现假溢出现象。
在队列中解决假溢出的方法之一是采用循环队列,将一个队列的头尾相接。
循环队列
循环队列是一种头尾相接的队列,可以解决顺序队列存在的假溢出的情况。由于是头尾相接的循环结构,顺序队列中入队加一和出队减一的操作在循环队列中就不再适用。
Q->end = (Q->end+1) % M;
Q->front = (Q->end+1) % M;
在顺序队列中,当队尾指针指向0时即为队空,队尾指针指向M使即为队满。在循环队列中由于没有特定的0和M,队空和队满的判断条件也不同。
一种方法是添加一个计数器,当计数器为0时队空,计数器为M时队满。
另一种方法是不使用Q->front所指向的位置,使Q->end永远追不上Q->front。当队尾指针加一等于队头指针时即为队满。而为了与队空做区分让队尾指针指向的值对M取模即可。
end == front //说明队空
(end+1) % M == front; //说明队满
二:队列的基本操作
队列初始化:
PQueue Init_Queue()
{
PQueue Q;
Q = (PQueue)malloc(sizeof(Queue)); //分配队列空间
if(Q)
{
Q->front = 0; //初始化队头指针和队尾指针
Q->end = 0;
}
return Q;
}
判断队空:
int Empty(PQueue Q)
{
if(Q && Q->front == Q->end) //队头指针和队尾指针指向相同的位置
return 1; //队列为空
else
if(!Q)
return -1;
else
return 0;
}
数据入队:
int Push(PQueue Q,int x)
{
if((Q->end+1) % M == Q->front) //队满不能继续入队
{
printf("队满\n");
return -1;
}
else
{
Q->end = (Q->end+1) % M; //更新队尾指针
Q->data[Q->end] = x;
return 1;
}
}
数据出队:
int Pop(PQueue Q,int *x)
{
if(Empty(Q)) //队空没东西删
{
printf("队空\n");
return -1;
}
else
{
Q->front = (Q->front+1) % M; //更新队头指针
*x = Q->data[Q->front];
return 1;
}
}
输出队头元素:
int Front(PQueue Q,int *x)
{
if(Q->front == Q->end) //队空没有数据可以输出
{
printf("队空\n");
return -1;
}
else
{
*x = Q->data[(Q->front+1) % M]; //更新x的取值
return 1;
}
}
销毁队列:
void Destroy(PQueue *Q)
{
if(*Q)
free(*Q);
*Q == NULL;
}