什么是队列
队列只允许在一端进行插入数据这一端叫队尾,在另一端进行删除数据,这一端叫队头,的特殊线性表。
遵循先进先出的原则。
入队
出队
队列的实现
队列也可以用数组和链表实现,但是用链表更优,在对头的处理上效率更高。
标记一个头指针,一个尾指针
typedef int QueueDataType;
typedef struct QueueNode
{
struct QueueNode* next;
QueueDataType data;
}QN;
typedef struct Queue
{
QN* tail;
QN* head;
}Queue;
相关函数接口的声明
void QueueInit(Queue* pq);//初始化队列
void QueueDestroy(Queue* pq);//销毁队列
void QueuePush(Queue* pq, QueueDataType x);//入队
void QueuePop(Queue* pq);//出队
QueueDataType QueueFront(Queue* pq);//队头数据
QueueDataType QueueBack(Queue* pq);//队尾数据
bool QueueEmpty(Queue* pq);//判空
int QueueSize(Queue* pq);//队列数据数量
函数定义
void QueueInit(Queue* pq)
{
assert(pq);
pq->tail = pq->head = NULL;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QN* cur = pq->head;
while (cur)
{
QN* prev = cur->next;
free(cur);
cur = prev;
}
pq->head = pq->tail = NULL;
}
void QueuePush(Queue* pq, QueueDataType x)//入队列
{
assert(pq);
QN* new = (QN*)malloc(sizeof(QN));
if (new == NULL)
{
perror("malloc fail:");
exit(-1);
}
else
{
new->data = x;
new->next = NULL;
}//若开辟节点成功,就初始化节点
if (pq->tail == NULL && pq->head == NULL)
{
pq->head = pq->tail = new;
}
else
{
pq->tail->next = new;
pq->tail = new;
}
}
void QueuePop(Queue* pq)//出队列
{
assert(pq);
assert(!QueueEmpty(pq));
if (pq->head->next == NULL)//要分只有一个节点的情况
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else //如果不去分析,这样虽然head指向空,但是tail不指向空,会有内存泄露的风险
{
QN* del = pq->head;
pq->head = pq->head->next;
free(del);
}
}
QueueDataType QueueFront(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->head->data;//如果没有节点不不就存在对NULL的解引用了嘛?
}
QueueDataType QueueBack(Queue* pq)
{
assert(pq);
assert(!QueueEmpty(pq));
return pq->tail->data;
}
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->head==NULL && pq->tail == NULL;
}
int QueueSize(Queue* pq)
{
assert(pq);
QN* cur = pq->head;
int n = 0;
while (cur)
{
n++;
cur = cur->next;
}
return n;
}
循环队列
它的长度不变,逻辑上成环状。
循环队列可以用数组也可以用链表来实现。
用数组实现:让back 走到超出长度下标,就又要回到前面0下标
用队列实现:使用循环链表实现,即让尾的next指向头
但是无论哪个,判空和判满怎么区别呢?
空:front 与back 在一开始都初始化为0,
或者都指向NULL; 两者相等。
满:front 在头,back 在尾的下一个,即为头,
或者back的next 又指向了头。
解决方法:
1.加一个size
空:0
满:队列长度
2.增加一个空间,满的时候永远保留一个空位置
(比如,队列长度为4,就要开5个空间)
当back 的下一个位置是front 就为满了
对于链表,如果选择去多留一个空间的话,那么实现队列就要去实现一个队尾数据读取的功能,此时的back 是队尾数据的下一个,链表无法倒回前一个,要么去再有一个指针记录;但对于数组,读取队尾,只要让下标减一即可。
所以如果要用链表去实现的话,最后用size ,但是也很麻烦,要去遍历计数。
总之,对于实现循环队列,一般地,标准的去实现,应该用数组并且增加一个空位置的方法。
这里的N记录的是总共的空间
typedef struct {
int *a;
int front;
int back;
int N;
} MyCircularQueue;
创建一个队列,初始化 front 和 back
这里的参数k 是能存储数据的长度
MyCircularQueue* myCircularQueueCreate(int k) {
MyCircularQueue*obj=(MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a=(int *)malloc((k+1)*sizeof(int));
obj->front=obj->back=0;
obj->N=k+1;
return obj;
}
队列的判空判满
bool myCircularQueueIsEmpty(MyCircularQueue* obj) {//判空
return obj->front==obj->back;
}
bool myCircularQueueIsFull(MyCircularQueue* obj) {//判满
return obj->front==(obj->back+1)%obj->N;
}
插入删除数据
获取队首队尾数据
注意:走到末尾下标加一要回到头,
走到队前下标减一要回末尾,注意考虑!!!用%
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {//插入数据
//判满
if(myCircularQueueIsFull(obj))
{
return false;
}
else
{
obj->a[obj->back]=value;
obj->back++;
obj->back%=obj->N;
return true;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj) {//删除数据
//判空
if(myCircularQueueIsEmpty(obj))
{
return false;
}
else
{
obj->front++;
obj->front%=obj->N;
return true;
}
}
int myCircularQueueFront(MyCircularQueue* obj) {//队首获取数据
//判空
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
else
{
return obj->a[obj->front];
}
}
int myCircularQueueRear(MyCircularQueue* obj) {//队尾获取数据
//判空
if(myCircularQueueIsEmpty(obj))
{
return -1;
}
else
return obj->a[(obj->back-1+obj->N)%(obj->N)];
}
最后释放
malloc几次就释放几次
void myCircularQueueFree(MyCircularQueue* obj) {
free(obj->a);
free(obj);
}
一循环队列,队头指针为front,队尾指针为rear
队列的长度为N(假设该循环队列多给了一个空间,N表示实际空间大小)
那么,对内的有效长度为:
(rear-front+N)%N;