2.队列
2.1队列的概念及结构
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 的特点。
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
2.2队列的实现
还是那个问题,用数组结构还是链式结构来实现尾删呢?
数组结构已经明显不适合了,每次出队列都要遍历一遍,而链式结构的则没有这个问题。而且单链表就可以。
如果你还记得,单链表尾插的结构也是O(N)的时间复杂度,为什么可以用链式结构?
原因在于:单链表不止存在尾插,还有尾删,需要尾结点前一个节点的位置,使用尾指针没有太大意义,而队列中的链式结构只存在一个尾插,用尾指针可以很好的将时间复杂度优化为O(1)。
大体逻辑和单链表一样,如果无法理解,不是队列的问题,而是之前单链表没有学好。
不同的地方在于,队列要有两个结构体,一个链式队列节点结构体,一个链式队列首节点尾节点结构体,其他的就是考虑一些特殊情况,有漏写的也能通过测试测出来。
下面给出队列的代码:
Queue.c中
#include"Queue.h"
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
void QueuePush(Queue* pq, QueueDateType x)
{
assert(pq);
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->val = x;
newNode->next = NULL;
if (pq->head == NULL)
{
pq->head = pq->tail = newNode;
}
else
{
pq->tail->next = newNode;
pq->tail = newNode;
}
}
void QueuePop(Queue* pq)
{
assert(pq);
if (pq->head == NULL)
return;
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
}
bool QueueEmpty(Queue* pq)
{
if (pq->head == NULL)
return true;
else
return false;
}
size_t QueueSize(Queue* pq)//如果需要频繁查看队列的元素个数,可以在QueueNode结构体中额外定义一个size变量。
{
assert(pq);
size_t size = 0;
QueueNode* cur = pq->head;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
QueueDateType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->head);
return pq->head->val;
}
QueueDateType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->tail);
return pq->tail->val;
}
Queue.h中
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<Stdbool.h>
#include<assert.h>
typedef int QueueDateType;
typedef struct QueueNode
{
QueueDateType val;
struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QueueDateType x);
void QueuePop(Queue* pq);
bool QueueEmpty(Queue* pq);
size_t QueueSize(Queue* pq);
QueueDateType QueueFront(Queue* pq);
QueueDateType QueueBack(Queue* pq);
test.c中
对队列进行测试
然后就是队列的OJ题了
1.用队列实现栈
基本思路:
入栈,push数据到不为空的队列中
出栈,将不为空队列的前n-1个数据放入到另一个队列中,剩下的pop。
逻辑很简单,复杂的是结构
顺便说一下,这个代码逻辑简单,结构复杂,最好自己写,出问题了再对照代码看是什么问题。
还是无法理解就复制到编译器中,然后根据测试用例来调试。
还有一种方法,在OJ题上来进行排除法锁定出错的函数。对测试用例进行删除。下图1为一个出错的测试用例,图2位排除法后的测试用例,直到出现符合期望的情况。在删除pop和empty后可以正常运行,说明,问题在pop上。当然,这是大部分情况。
代码如下:
//标注行以上为队列的实现
typedef int QueueDateType;
typedef struct QueueNode
{
QueueDateType val;
struct QueueNode* next;
}QueueNode;
typedef struct Queue
{
QueueNode* head;
QueueNode* tail;
}Queue;
void QueueInit(Queue* pq)
{
assert(pq);
pq->head = pq->tail = NULL;
}
void QueueDestroy(Queue* pq)
{
assert(pq);
QueueNode* cur = pq->head;
while (cur)
{
QueueNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
void QueuePush(Queue* pq, QueueDateType x)
{
assert(pq);
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->val = x;
newNode->next = NULL;
if (pq->head == NULL)
{
pq->head = pq->tail = newNode;
}
else
{
pq->tail->next = newNode;
pq->tail = newNode;
}
}
void QueuePop(Queue* pq)
{
assert(pq);
if (pq->head == NULL)
return;
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QueueNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
}
bool QueueEmpty(Queue* pq)
{
if (pq->head == NULL)
return true;
else
return false;
}
size_t QueueSize(Queue* pq)
{
assert(pq);
size_t size = 0;
QueueNode* cur = pq->head;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
QueueDateType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->head);
return pq->head->val;
}
QueueDateType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->tail);
return pq->tail->val;
}
//标注行
typedef struct
{
Queue q1;
Queue q2;
} MyStack;
MyStack* myStackCreate()
{
MyStack* obj = (MyStack*)malloc(sizeof(MyStack));//这里不能定义局部变量,出作用域会销毁。
QueueInit(&(obj->q1));
QueueInit(&(obj->q2));
return obj;
}
void myStackPush(MyStack* obj, int x)
{
if(QueueEmpty(&obj->q2))
{
QueuePush(&obj->q1, x);
}
else
{
QueuePush(&obj->q2, x);
}
}
int myStackPop(MyStack* obj)
{
if(QueueEmpty(&obj->q2))
{
while((&obj->q1)->head->next)
{
QueuePush(&obj->q2,(&obj->q1)->head->val);
QueuePop(&obj->q1);
}
int tmp = (&obj->q1)->head->val;
QueuePop(&obj->q1);
return tmp;
}
else
{
while((&obj->q2)->head->next)
{
QueuePush(&obj->q1,(&obj->q2)->head->val);
QueuePop(&obj->q2);
}
int tmp = (&obj->q2)->head->val;
QueuePop(&obj->q2);
return tmp;
}
}
int myStackTop(MyStack* obj)
{
if(QueueEmpty(&obj->q2))
{
int tmp = QueueBack(&obj->q1);
return tmp;
}
else
{
int tmp = QueueBack(&obj->q2);
return tmp;
}
}
bool myStackEmpty(MyStack* obj)
{
if(QueueEmpty(&obj->q2) && QueueEmpty(&obj->q1))
{
return true;
}
else
{
return false;
}
}
void myStackFree(MyStack* obj)
{
if(QueueEmpty(&obj->q2) && QueueEmpty(&obj->q1))
{
free(obj);
return ;
}
else if(QueueEmpty(&obj->q2))
{
free(obj);
QueueDestroy(&obj->q1);
}
else
{
free(obj);
QueueDestroy(&obj->q2);
}
}
2.用栈实现队列
本题思路:有两个栈,一个存储数据1,2,3,4,5,现在要实现队列,意味着要按1,2,3,4,5出,所以先将栈的所有值导出,另一个栈中存储5,4,3,2,1。正好符合栈出的顺序。所以需要出就是按另一个栈的顺序出,出完了就再次从第一个队列导入,所以可以将两个栈取名,一个入队列,一个出队列。
代码如下:
//以下到标注行是栈的实现
typedef int StackDateType;
typedef struct Stack
{
StackDateType* a;
int top;
int capacity;
}Stack;
void StackInit(Stack* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
void StackDestroy(Stack* ps)
{
assert(ps);
free(ps->a);
ps->a = NULL;
ps->capacity = 0;
ps->top = 0;
}
void StackPush(Stack* ps, StackDateType x)
{
assert(ps);
if (ps->top == ps->capacity)
{
int newCapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
StackDateType* tmp = (StackDateType*)realloc(ps->a, newCapacity * sizeof(StackDateType));
if (tmp == NULL)
{
printf("realloc fail\n");
return;
}
else
{
ps->a = tmp;
ps->capacity = newCapacity;
}
}
ps->a[ps->top] = x;
ps->top++;
}
void StackPop(Stack* ps)
{
assert(ps);
if (ps->top > 0)
ps->top--;
}
StackDateType StackTop(Stack* ps)
{
assert(ps);
assert(ps->top > 0);
return ps->a[ps->top-1];
}
bool StackEmpty(Stack* ps)
{
assert(ps);
if (ps->top > 0)
return false;
else
return true;
}
int StackSize(Stack* ps)
{
assert(ps);
return ps->top;
}
//标注行
typedef struct
{
Stack in;
Stack out;
} MyQueue;
MyQueue* myQueueCreate()
{
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
StackInit(&obj->in);
StackInit(&obj->out);
return obj;
}
void myQueuePush(MyQueue* obj, int x) {
StackPush(&obj->in,x);
}
int myQueuePop(MyQueue* obj)
{
if(StackEmpty(&obj->out))
{
while(!StackEmpty(&obj->in))
{
StackPush(&obj->out,StackTop(&obj->in));
StackPop(&obj->in);
}
int tmp = StackTop(&obj->out);
StackPop(&obj->out);
return tmp;
}
else
{
int tmp = StackTop(&obj->out);
StackPop(&obj->out);
return tmp;
}
}
int myQueuePeek(MyQueue* obj)
{
if(StackEmpty(&obj->out))
{
while(!StackEmpty(&obj->in))
{
StackPush(&obj->out,StackTop(&obj->in));
StackPop(&obj->in);
}
return StackTop(&obj->out);
}
else
{
return StackTop(&obj->out);
}
}
bool myQueueEmpty(MyQueue* obj)
{
return StackEmpty(&obj->in) && StackEmpty(&obj->out);
}
void myQueueFree(MyQueue* obj)
{
if(StackEmpty(&obj->in) && StackEmpty(&obj->out))
{
free(obj);
return;
}
else if(StackEmpty(&obj->in))
{
StackDestroy(&obj->out);
free(obj);
return ;
}
else if(StackEmpty(&obj->out))
{
StackDestroy(&obj->in);
free(obj);
return ;
}
else
{
StackDestroy(&obj->in);
StackDestroy(&obj->out);
free(obj);
return ;
}
}
3.设计循环队列(有点难度)
循环队列就是要形成一个环状的队列,逻辑上呈现的如下图所示,环状队列不增容,满足在任意时间点储存数据的量不超过队列容量的情况。
可以通过数组实现,也可以通过链表实现,数组实现就是在越过下标极限后队列尾置为0。队列尾始终指向将要输入数据的空间。链表更符合环形队列的逻辑,即使到最后一个节点也只需要tail = tail->next就可以了。但以优劣性来讲,数组实现比链表更优,因为数组是连续的空间,而链表唯一比数组优的就是队尾不需要额外的判断。链表的删除是不能free的,因为空间大小是固定的,头节点直接指向下一个就可以了。而且链表取最后一个数据不好取。(队列是可以知道尾节点数据的)
循环队列的缺陷:容量不是缺陷,循环队列就是解决容量有限的情况。缺陷在于如何判断队列满和队列空的情况。一开始是空,此时front和tail指向同一空间,所以front == tail时为空
那么这种情况呢?
tail在完成最后一个空间的赋值后又和front指向同一空间。为了解决这种情况,往往会多开辟一个空间,有4个数据要储存,就开辟5个空间。当tail+1 == front时即为空间满。空的空间位置不固定。
本题最好画图写。
代码如下://涉及if的,都是特殊情况
typedef struct
{
int* a;
int front;
int tail;
int k;//队列长度大小
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue* obj);//提前声明函数,因为判断空满的函数在需要使用空满函数的下面。
bool myCircularQueueIsFull(MyCircularQueue* obj);
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
obj->a = (int*)malloc(sizeof(int)*(k+1));//多开辟一个节点
obj->k = k;
obj->front = obj->tail = 0;
return obj;
}
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if(myCircularQueueIsFull(obj))
{
return false;
}
else
{
obj->a[obj->tail] = value;
if(obj->tail == obj->k)//也可以通过tail++后取k+1的模来控制位置
{
obj->tail = 0;
}
else
{
obj->tail++;
}
return true;
}
}
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
if(myCircularQueueIsEmpty(obj))
{
return false;
}
else
{
if(obj->front == obj->k)
{
obj->front = 0;
}
else
{
obj->front++;
}
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
{
if(obj->tail == 0)
{
return obj->a[obj->k];
}
else
{
return obj->a[obj->tail-1];
}
}
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->front == obj->tail;
}
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
if(obj->tail == obj->k && obj->front == 0)
{
return true;
}
else
{
return obj->tail+1 == obj->front;
}
}
void myCircularQueueFree(MyCircularQueue* obj)
{
free(obj->a);
free(obj);
}