队列是一种尾部进头部出的数据结构,顺口溜就是先进先出。
队列的结构用链式存储方式最合适的,这不仅仅是结构上的优势,更重要在于顺序式存储结构会造成“假溢出”现象。
假溢出现象数队头由于出队操作,还有剩余空间,但队尾指针已达到数组的尾部,继续插入元素,队尾指针就会越出数组的上界,进而造成“溢出”,此溢出不是因为存储空间不够而产生的溢出,而是经过反复插入删除操作引起的,这中有存储空间但不能插入元素的操作称为“假溢出“。幸好我们学习了循环队列可以完美解决这个问题!
这篇博客我们只讨论顺序队列与循环队列!至于链式队列,后面会开新的博客进行讨论。
由于顺序队列的相关接口是链式队列的基础,只有全面了解写熟顺序队列后,才能学习循环队列
从顺序队列结构体定义开始
#define MAXSIZE 10
typedef int DataType;
typedef struct Queue{
DataType Queue[MAXSIZE];
size_t fornt; //队头指针
size_t rear; //队尾指针
}SeqQueue;
在这里为什么结构体里定义的队头队尾指针不加*,这是因为我们是用数组的形式来实现这个顺序队列的。数组就是指针。
我们想要 哪个元素,只需要知道这个元素在本数组中的位置即可.
先来看队列的初始化操作
只需要操纵队头与队尾指针指向NULL就行了。
InitQueue(SeqQueue *s)
{
s->fornt = s->rear = NULL;
}
队列判空操作(这个操作是将来删除与取首元素的基础操作,所以 书写时要小心)
IsEmpty(SeqQueue *s)
{
if (s->fornt == s->rear){
printf("队列是空的!!");
return 1; //这里的返回值要考虑到后面操作会在if与while语句中运用
}
else
{
printf("队列不为空!!");
return 0;
}
}
队列的判满操作
int IsFull(SeqQueue *s)
{
if (s->rear == MAXSIZE){
printf("队列满了");
return 1;
}
else
{
printf("队列未满");
return 0;
}
}
队列的插入操作(此操作在尾部进行)
void PushQueue(SeqQueue *s, DataType data)
{
while(IsFull(s))
{
printf("队列已满,无法进行插入!!");
return;
}
s->Queue[s->rear] = data;
s->rear = s->rear+1;
}
队列的删除操作(此操作在队列的头部进行)
void PopQueue(SeqQueue *s)
{
while (IsEmpty(s)){
printf("队列为空,无法完成出队操作!!");
return;
}
s->fornt = s->fornt + 1;//仅仅需要将队头的位置向后诺一位就行了 最简单
}
队列的取头部操作
int GetFornt(SeqQueue *s)
{
while (IsEmpty(s)){
printf("队列为空,无法获取队首元素!!");
return;
}
return s->Queue[s->fornt];
}
队列的清空操作,和判空 操作相似,条件一模一样都是判断队列为空队头等于队尾
void ClearQueue(SeqQueue *s)
{
s->fornt = s->rear;
}
最后一个是队列的打印函数
void PrintQueue(SeqQueue *s)
{
int cur = s->fornt;
while (cur != s->rear)
{
printf("%d--", s->Queue[cur]);
++cur;
}
}
以上就是 顺序队列的全部接口
下面来谈循环队列!!
循环队列与顺序队列最大的区别就在于空与满的条件发生了变换,通过上面代码可见队列的难点就在于判空与判满函数的书写,其他的都很简单。
循环队列在队空和队满时,都是队头指针和队尾指针指向同一个位置,front==rear 为了区分这两种情况,可以少用一个存储空间,队空的判断条件不变,以队尾指针rear加1等于队头指针为队列的判满条件。即:front = rear 表示队空,(rear + 1) % MaxSize == fornt 表示队满。这种方式通过一个空间巧妙地区别了