数据结构:队列
队列的概念:
队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。队列中没有元素时,称为空队列。
队列的数据元素又称为队列元素。在队列中插入一个队列元素称为入队,从队列中删除一个队列元素称为出队。因为队列只允许在一端插入,在另一端删除,所以只有最早进入队列的元素才能最先从队列中删除,故队列又称为先进先出(FIFO—first in first out)线性表
图片所展示的内容为队列:
顺序队列:
建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置,如图所示
每次在队尾插入一个元素是,rear增1;每次在队头删除一个元素时,front增1。随着插入和删除操作的进行,队列元素的个数不断变化,队列所占的存储空间也在为队列结构所分配的连续空间中移动。当front=rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是已经出队的队列元素曾经占用过得存储单元。
顺序队列中的溢出现象:
(1) "下溢"现象:当队列为空时,做出队运算产生的溢出现象。“下溢”是正常现象,常用作程序控制转移的条件。
(2)"真上溢"现象:当队列满时,做进栈运算产生空间溢出的现象。“真上溢”是一种出错状态,应设法避免。
(3)"假上溢"现象:由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。该现象称为"假上溢"现象。
代码实现:
顺序队列:设计成环形队列
queue.h:
#pragma once
//顺序队列:设计成环形队列
#define SIZE 10
typedef struct Queue
{
int elem[SIZE];//数据
int front;//队头指针:队列中第一个数据的下标
int rear;//队尾指针:队尾数据的下一个数据下标即当前可以存放数据的下标
}Queue,*PQueue;
//初始化
void InitQueue(PQueue pq);
//入队
bool Push(PQueue pq,int val);
//出队
bool Pop(PQueue pq,int *rtval);
//判空
bool IsEmpty(PQueue pq);
//获得队头
bool GetTop(PQueue pq,int *rtval);
//摧毁
void Destroy(PQueue pq);
queue.cpp:
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "queue.h"
//初始化
void InitQueue(PQueue pq)
{
assert(pq != NULL);
pq->front = 0;
pq->rear = 0;
}
//判满:内部函数
//浪费一个单元格用于区分满和空
static bool IsFull(PQueue pq)
{
return (pq->rear+1) % SIZE == pq->front;
}
//入队
bool Push(PQueue pq,int val)//O(1)
{
if(IsFull(pq))
{
return false;
}
pq->elem[pq->rear] = val;
pq->rear = (pq->rear+1)%SIZE;
return true;
}
//出队(获得队头并删除)
bool Pop(PQueue pq,int *rtval)//O(1)
{
assert(pq != NULL);
if(IsEmpty(pq))
{
return false;
}
*rtval = pq->elem[pq->front];
pq->front = (pq->front+1)%SIZE;
return true;
}
//判空
bool IsEmpty(PQueue pq)
{
return pq->front == pq->rear;
}
//获得队头但不删除
bool GetTop(PQueue pq,int *rtval)
{
assert(pq != NULL);
if(IsEmpty(pq))
{
return false;
}
*rtval = pq->elem[pq->front];
return true;
}
//摧毁
void Destroy(PQueue pq)
{
pq->front = pq->rear;
}
主函数:
#include <stdio.h>
#include "queue.h"
int main()
{
int i;
Queue p;
InitQueue(&p);
for(i = 0; i < 10;i++)
{
Push(&p,i);//入队
}
int tmp;
while(!IsEmpty(&p))
{
Pop(&p,&tmp);//出队
printf("%d\n",tmp);
}
return 0;
}
循环队列:
在实际使用队列时,为了使队列空间能重复使用,往往对队列的使用方法稍加改进:无论插入或删除,一旦rear指针增1或front指针增1 时超出了所分配的队列空间,就让它指向这片连续空间的起始位置。自己真从MaxSize-1增1变到0,可用取余运算rear%MaxSize和front%MaxSize来实现。这实际上是把队列空间想象成一个环形空间,环形空间中的存储单元循环使用,用这种方法管理的队列也就称为循环队列。除了一些简单应用之外,真正实用的队列是循环队列。
SeqQueue.h
#pragma once
typedef int ElemType;
#define SIZE 10
typedef struct _SeqQue
{
ElemType *data;
int head;
int tail;
}SeqQue, *pSeqQue;
//初始化
bool InitQueue(pSeqQue sq);
//入队
bool EnQueue(pSeqQue sq, ElemType val);
bool EnQueue2(pSeqQue sq, ElemType val);
//出队
bool DeQueue(pSeqQue sq);
bool DeQueue2(pSeqQue sq);
//获取队头元素
bool GetHead(pSeqQue que, ElemType *val);
//清空
void ClearQue(pSeqQue que);
//销毁
void Destroy(pSeqQue que);
SeqQueue.cpp
#include "SeqQueue.h"
#include <malloc.h>
#include <string.h>
#include <assert.h>
static bool IsFull(pSeqQue sq)
{
return sq->tail == SIZE;
}
static bool IsEmpty(pSeqQue sq)
{
return sq->tail == 0;
}
static bool IsFull2(pSeqQue sq)
{
return (sq->tail+1) % SIZE == sq->head;
}
static bool IsEmpty2(pSeqQue sq)
{
return sq->tail == sq->head;
}
bool InitQueue(pSeqQue sq)
{
assert(sq != NULL);
if (sq == NULL) return false;
sq->data = (ElemType*)malloc(sizeof(ElemType)*SIZE);
assert(sq->data != NULL);
if (sq->data == NULL) return false;
sq->head = sq->tail = 0;
return true;
}
// O(1)
bool EnQueue(pSeqQue sq, ElemType val)
{
if (IsFull(sq) || sq->data == NULL) return false;
sq->data[sq->tail] = val;
sq->tail++;
return true;
}
bool EnQueue2(pSeqQue sq, ElemType val)
{
if (IsFull2(sq) || sq->data == NULL) return false;
sq->data[sq->tail] = val;
sq->tail = (sq->tail + 1) % SIZE;
return true;
}
// O(n)
bool DeQueue(pSeqQue sq)
{
if (IsEmpty(sq) || sq->data == NULL) return false;
for (int i = 0; i < sq->tail - 1; ++i)
{
sq->data[i] = sq->data[i + 1];
}
sq->tail--;
return true;
}
bool DeQueue2(pSeqQue sq)
{
if (IsEmpty2(sq) || sq->data == NULL) return false;
sq->head = (sq->head + 1) % SIZE;
return true;
}
bool GetHead(pSeqQue que, ElemType *val)
{
if (IsEmpty(que) || que->data == NULL ) return false;
*val = que->data[que->head];
return true;
}
void ClearQue(pSeqQue que)
{
que->tail = que->head = 0;
}
void Destroy(pSeqQue que)
{
free(que->data);
que->data = NULL;
que->tail = 0;
}
链式队列(带有尾指针的单链表实现):
队列的链式存储结构简称为链式队列,它是限制仅在表头进行删除操作和表尾进行插入操作的单链表。链队的操作实际上是单链表的操作,只不过是出队在表头进行,入队在表尾进行。入队、出队时分别修改不同的指针。链式队列的出队和入队的操作可参考下图:
lqueue.h
#pragma once//防止头文件重复引用
typedef struct QNode
{
int data;
struct QNode *next;
}QNode;
typedef struct HNode
{
QNode *front;//队头指针
QNode *rear;//队尾指针
}HNode,*PLQueue;
//初始化
void InitQueue(PLQueue pq);
//入队
bool Push(PLQueue pq,int val);
//出队
bool Pop(PLQueue pq,int *rtval);
//判空
bool IsEmpty(PLQueue pq);
//获得队头
bool GetTop(PLQueue pq,int *rtval);
//摧毁
void Destroy(PLQueue pq);
lqueue.cpp
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "lqueue.h"
//初始化
void InitQueue(PLQueue pq)
{
assert(pq != NULL);
pq->front = NULL;
pq->rear = NULL;
}
//入队
bool Push(PLQueue pq,int val)//O(1)
{
QNode *p = (QNode *)malloc(sizeof(QNode));
p->data = val;
p->next = NULL;
if(pq->front == NULL)//判断队空if(IsEmpty(pq))
{
pq->front = p;
pq->rear = p;
}
else
{
pq->rear->next = p;
pq->rear = p;
}
return true;
}
//出队(获得队头并删除)
bool Pop(PLQueue pq,int *rtval)//O(1)
{
if(IsEmpty(pq))
{
return false;
}
QNode *p = pq->front->next;
*rtval = p->data;
pq->front = p->next;
free(p);
return true;
}
//判空
bool IsEmpty(PLQueue pq)
{
return pq->front == NULL;
}
//获得队头但不删除
bool GetTop(PLQueue pq,int *rtval)
{
if(IsEmpty(pq))
{
return false;
}
QNode *p = pq->front->next;
*rtval = p->data;
return true;
}
//摧毁
void Destroy(PLQueue pq)
{
while(pq->front)
{
pq->rear = pq->front->next;
free(pq->front);
pq->front = pq->rear;
}
}
主函数:
#include <stdio.h>
#include <vld.h>
#include "lqueue.h"
int main()
{
HNode p;
InitQueue(&p);
for(int i = 0; i < 10;i++)
{
Push(&p,i);//入队
}
int tmp;
while(!IsEmpty(&p))
{
Pop(&p,&tmp);//出队
printf("%d\n",tmp);
}
Destroy(&p);//摧毁
Destroy(&p);
return 0;
}
上一篇: Java内存模型与线程
下一篇: CAS原理