Hello,各位小伙伴们上期我们学习了栈这样的数据结构,今天让我们一起学习一下它的孪生兄弟队列。
队列的基本概念和结构
概念:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
队列底层结构选型
队列也可以数组和链表的结构实现,使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低。
队列的实现
队列的基本结构:
cpp
typedef int QNDatatype;
typedef struct QueueNode
{
QNDatatype data;
struct QueueNode* next;
}QueueNode;//这里实在定义队列的节点
//同时要定义队列的节点指针----队列的结构
typedef struct Queue
{
QueueNode* phead;//队头
QueueNode* ptail;//队尾
}
队列的结点结构与链表类似,但队列要满足基本从一端入队列,从另一端出队列如果我们像链表那样在头节点进行结点的删除,尾节点进行节点的增加,我们需要遍历链表来找到尾结点这样时间复杂度就变为O(n),所以我们额外定义了一个结构体变量在链表的两端固定头结点和尾结点。
实现队列我们要实现一下基本功能:
cpp
//队列的初始化
void QueueInit(Queue* ps);
//判断队列是否为空
bool QueueEmpty(Queue* ps);
//判断队列有效数据个数
int QueueNumSize(Queue* ps);
//入队列
void QueuePush(Queue* ps, QNDatatype x);
//删除队列节点
void QueuePop(Queue* ps);
//取对头数据
QNDatatype QueueFront(Queue* ps);
//取队尾数据
QNDatatype QueueBack(Queue* ps);
//队列的销毁
void QueueDestory(Queue* ps);
1.队列的初始化
cpp
oid QueueInit(Queue* ps)
{
assert(ps);
ps->phead = ps->ptail = NULL;
ps->size = 0;
}
2.判断队列是否为空
cpp
bool QueueEmpty(Queue* ps)
{
assert(ps);
return ps->phead == NULL;//如果为空bool则会显示true
}
3.判断队列中有效数据的个数
cpp
int QueueNumSize(Queue* ps)
{
int size = 0;
QueueNode* pur = ps->phead;
while (pur)
{
size++;
pur = pur->next;
}
return size;
}
在这里我们可以了解到时间复杂度为O(n),为了降低 时间复杂度我们可以在队列的结构体中多设置一个参数,int size 在入队列和出队列的时候我们只需要对ps->size加加或者减减就可以了,这样我们在此函数中直接返回ps->size即可。这样时间复杂度就会降低为O(1)。
4.入队列
在入队列操作时我们要注意在进行新的内存空间申请时注意变量类型,因为队列结点结构,我们在队列结点结构上进行了重定义,这里一定要注意一下万万不可写成队列结构。
cpp
void QueuePush(Queue* ps, QNDatatype x)
{
assert(ps);
QueueNode* newnode = (QueueNode*)malloc(sizeof(QueueNode));
if (newnode == NULL)
{
perror("malloc fail!");
exit(1);
}
newnode->data = x;
newnode->next = NULL;//将申请好的节点空间赋值并间下一个节点指针赋值为NULL
//队列为空的情况下
if (ps->phead == NULL)
{
ps->phead = ps->ptail = newnode;
}
else {
//队列不为空的情况下
ps->ptail->next = newnode;//将newnode插入到尾节点的下一个节点
ps->ptail = ps->ptail->next;//同时将尾节点的下一个节点置为尾节点
}
}
5.删除队列结点
删除队列结点我们要注意结点个数,通过结点个数来分情况。但结点个数为1的时候队列的phead与ptail在同一个位置,直接free掉即可。
但结点个数有多个时,我们要在头结点的位置开始删除,但要创建一个新的变量next来记录头结点的下一个结点(否则如果直接删除了头结点就早不到下一个结点了)删除该结点后将next赋值给该队列的头结点。
cpp
void QueuePop(Queue* ps)
{
assert(ps);
assert(!QueueEmpty(&ps));
//只有一个节点
if (ps->phead->next == NULL)
{
free(ps->phead);
ps->phead = ps->ptail = NULL;
}//多个节点的情况
else {
Queue* next = ps->phead->next;
free(ps->phead);
ps->phead = next;
}
}
6.取对头数据
取对头数据我们要进行两次断言,一次为传递过来的队列的指针不能为空,第二个为传递过来的队列里面的结点不能为空!然后直接返回头结点的数据即可。
cpp
QNDatatype QueueFront(Queue* ps)
{
assert(ps);
assert(!QueueEmpty(&ps));
return ps->phead->data;
}
7.取队尾数据
cpp
QNDatatype QueueBack(Queue* ps)
{
assert(ps);
assert(!QueueEmpty(&ps));
return ps->ptail->data;
}
8.队列的销毁
队列的销毁与链表的销毁类似,我们要循环销毁,创建一个新的队列结点结构体变量来记录队列的头结点,通过判断该结点是否为NULL来循环销毁结点。最后要记得将队列的两个指针释放掉。
cpp
void QueueDestory(Queue* ps)
{
assert(ps);
QueueNode* pur = ps->phead;
while (pur)
{
QueueNode* next = pur->next;
free(pur);
pur = next;
}
ps->phead = ps->ptail = NULL;
}