顺序队
队列
和栈一样,队列也是一种运算受限的线性表。队列只能选取一个端点进行插入操作,另一个端点进行删除操作。
- 进行插入操作的一端称为队尾
- 进行删除操作的一端称为队头
- 向队列中插入新元素的操作称为进队,新元素进队后成为新的队尾元素
- 从队列中删除元素的操作称为出队,元素出队后,其后继元素成为队首元素
队列的特点是先进先出 ( First In First Out ) ,因此又把队列称为先进先出表。
队列的顺序存储结构
typedef struct _sqQueue{
ElemType data[SIZE];
int front;
int rear;
}sqQueue;
rear
指向队尾元素,front
指向队头元素的前一个位置- 进队
rear++
,出队front++
- 当
front == rear
为true
时,队空 - 当
rear == SIZE - 1
为true
时,队满
顺序队的基本运算
初始化队
在堆中申请一块连续的内存空间用于存放数据,front
和rear
初始化为-1
void
initQueue( sqQueue **q )
{
*q = ( sqQueue* )malloc( sizeof( sqQueue ) );
(*q)->front = (*q)->rear = -1;
}
销毁队
调用free函数释放申请的内存空间
#define destroyQueue(q) free(q)
判断队是否为空
当front
和rear
指向同一个位置时,队列为空
#define queueEmpty(q) ( q->front == q->rear )
进队
当队列未满时,新元素从队尾进入队列。rear
加1指向下一个位置,新元素写入rear
指向的位置
int
enQueue( sqQueue *q, int e )
{
if( q->rear == SIZE - 1 ){
return FALSE;
}
q->data[ ++q->rear ] = e;
return TRUE;
}
出队
当队不为空时,元素从队头离开。front指向后继元素的前一个位置。
int
deQueue( sqQueue *q, int *e )
{
if( q->front == q->rear ){
return FALSE;
}
*e = q->data[ ++q->front ];
return TRUE;
}
循环队列
在上面的示例中,当rear
指向了数组最后一个位置,则判断队列为满。此时即使执行出队操作腾出空间,也没有破坏队满的条件,新的元素无法进队。
这种情况称之为假溢出。要把蓝色部分的空间利用起来,需要改进算法。
解决方案是:在逻辑上把队列看成是首尾相接的(在物理上并不相连),元素进队和出队看成是在一个环形结构中进行。
在循环队列中,用rear
和front
的相对距离来判断队空和队满
如图所示,front和rear的距离有6种情况:
0
,
1
,
2
,
3
,
4
,
5
0,1,2,3,4,5
0,1,2,3,4,5
即大小为n的队列,front
和rear
的距离有n种可能
队列元素个数有7种情况:
0
,
1
,
2
,
3
,
4
,
5
,
6
0,1,2,3,4,5,6
0,1,2,3,4,5,6
即大小为n的队列,元素个数有n+1种可能。
front
和rear
距离的n种可能不能完全表示队列n+1种状态。
解决这个问题有两种方案:
- 仅使用n-1个数组空间,让队列的元素个数只有n种可能,数组仅有一个空闲单元即为满。
- 使用额外标记
cnt
来记录当前队列的元素个数,cnt == SIZE
为满。
解决方案一
- 进队操作
rear = ( rear + 1 ) % SIZE
- 出队操作
front = ( front + 1 ) % SIZE
- 队空条件
front == rear
为true
- 队满条件
( rear + 1 ) % SIZE == front
为true
初始化队
front
和rear
初始化为0
void
initQueue( cyQueue **q )
{
*q = ( cyQueue* )malloc( sizeof( cyQueue ) );
(*q)->front = (*q)->rear = 0;
}
进队
int
enQueue( cyQueue *q, int e )
{
if( ( q->rear + 1 ) % SIZE == q->front ){
return FALSE;
}
q->rear = ( q->rear + 1 ) % SIZE;
q->data[ q->rear ] = e;
return TRUE;
}
出队
int
deQueue( cyQueue *q, int *e )
{
if( q->rear == q->front ){
return FALSE;
}
q->front = ( q->front + 1 ) % SIZE;
*e = q->data[ q->front ];
return TRUE;
}
解决方案二
对于循环队列来说,如果知道队头指针和队列中元素的个数,则可以计算出队尾指针。也就是说可以用队列中元素个数代替队尾指针。
当rear > front
时,元素个数等于rear - front
当rear < front
时,元素个数分布在两处,SIZE - front
和0 + rear
两种统一起来便是
- cnt =
( rear - front + SIZE ) % SIZE
- rear =
( front + cnt ) % SIZE
- front =
( rear - cnt + SIZE ) % SIZE
初始化队
void
initQueue( cyQueue **q )
{
*q = ( cyQueue* )malloc( sizeof( cyQueue ) );
(*q)->front = (*q)->cnt = 0;
}
进队
int
enQueue( cyQueue *q, int e )
{
int rear;
if( q->cnt == SIZE ){
return FALSE;
}else{
rear = ( q->front + q->cnt ) % SIZE;
rear = ( rear + 1 ) % SIZE;
q->data[ rear ] = e;
q->cnt++;
return TRUE;
}
}
出队
int
deQueue( cyQueue *q, int *e )
{
if( q->cnt == 0 ){
return FALSE;
}else{
q->front = ( q->front + 1 ) % SIZE;
*e = q->data[ q->front ];
q->cnt--;
return TRUE;
}
}
判断是否为空
#define queueEmpty(q) ( q->cnt == 0 )
Notice
- 环形队列比非循环队列的空间利用率更高,不会出现假溢出
- 并不是任何时候都要使用循环队列,进队的元素可能会被覆盖。如果算法需要使用所有进队的元素来进一步求解,应该使用非循环队列