目录
队列(Queue)
1.队列概念:
2.代码实现:
① 队列的主体选择:
②基本结构:(结构体 QueueNode)
③头尾标点初始化(QueueInit)
④元素入列(QueuePush)(尾插)
⑤元素出列(QueuePop)(头删)
⑥判断队列是否为空(QueueEmpty)
⑦队列长度(QueueSize)
⑧取队列首位(QueueFront)
⑨取队列末位(QueueBack)
⑩打印队列(Queue print)
销毁队列(QueueDestory)
完整代码:
Queue.h
Queue.c
队列(Queue) 1.队列概念:
队列(queue)是只允许在一端进行插入 *** 作,而在另一端进行删除 *** 作的线性表。
队列是一种先进先出的(First In First Out)的线性表,简称FIFO。
允许插入(Push)的一端为队尾,允许删除(Pop)的一端为队头。队列不允许在中间部位进行 *** 作!假设队列是Q=(a1,a2,……,an),那么a1就是队头元素,而an是队尾元素。
所以我们进行插入数据时,新插入的数据总是位于队列尾端;进行删除时,删除的数据总是位于队列开头的数据。这跟我们排队打饭的道理有点类似:队伍最前的人先打到饭,并且离开队伍。
———————————————————————————————————————————
队列可以选择用顺序表或链表进行实现,但是如果使用顺序表实现队列的情况下,对队列首位元素进行删除时,可能需要对整体向前一位迁移,这样的时间复杂度是O(N);或者不进行整体迁移,而采用扩容及改变下标的方法实现队列的头删功能,有点繁杂,所以这里我采用单链表实现队列。
②基本结构:(结构体 QueueNode)QueueNode:队列结点
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
这也是链表的基本结构,到这里我们可以考虑到:队列两个主要功能:头删和尾插。
!如果用链表实现队列的话,队列就可以看成一个只能头删和尾插的链表!
在头插方面,链表的效率比比顺序表要好;
在尾插方面,顺序表的效率常规情况下会比链表要好,因为链表进行尾删时,要先进行一次遍历找到尾结点,时间复杂度来到了O(N),顺序表只需要通过记录的下标找到尾端下标,进行删除。
有没有一种办法使得链表的尾删变得有效率?
标记尾结点。(或改写双链表实现队列)
(!双链表的哨兵结点前一位就是链表尾结点,哨兵结点后一位就是链表头结点!)
typedef struct Queue
{
QNode* head;
QNode* tail;
}Queue;
我们再加一个结构体Queue,负责记录队列中的头结点和尾结点:
——————————————————————————————————————————
③头尾标点初始化(QueueInit)void QueueInit(Queue* pq)//队列初始化
{
assert(pq);
pq->head = pq->tail = NULL;
}
首先检查传入用于保存head和tail指针的Queue结构体地址是否为空,避免错误使用。
其次,初始化时,队列没有元素,所以Queue结构体中的head和tail都应指为NULL。
———————————————————————————————————————————
④元素入列(QueuePush)(尾插)void QueuePush(Queue* pq, QDataType x);//入列
稳妥点,检查传入用于保存head和tail指针的Queue结构体地址是否为空,避免错误使用。
assert(pq);
然后就是为队列中结点开辟空间,再然后就是传入数据。
关于入列的结点,需要注意的点:
1. 第一次入列时,即队列只有一个元素,head还处于指向NULL的状态,需要将入列的结点地址 给予 head,且tail此时与head同位置。
2.不是第一次入列,即队列中已经有其他元素时,此时head已经具体,就指向队列的首位元素,不需要进行改动,此时需要改动的是tail,每有一个元素入列,tail位置就需要更新。
完整函数:
void QueuePush(Queue* pq, QDataType x)//入列
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
assert(newnode);
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL)
{
assert(pq->head == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
———————————————————————————————————————————
⑤元素出列(QueuePop)(头删)本质是链表尾删,细节是关注head与tail位置的更新,小细节是每次断尾结点后,要更新tail的位置,且将断出去的原来的尾结点释放内存。
实现出列的大概逻辑:
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
我们要考虑的情况是:
当我们一直使队列元素出列,tail的位置一直在更新,直到删到只剩一个元素,此时head与tail的位置是一样的:
继续删除,只剩一个元素:
此前的出列 *** 作是不需要对tail位置进行更新的,但是目前的这个情况,如果我们继续出列,到队列置空时,head与tail指向同一个位置,这时再 free(head),则tail跟head指向同一个指针的内存被释放,如果不及时更新tail位置,使得tail置空,就会出现tail变成野指针的情况,因此进行判断,当head的下一个结点为NULL时,即队列只有一个元素时,释放head指向的结点,head与tail同时指向NULL。
完整函数:
void QueuePop(Queue* pq)//出列
{
assert(pq);
assert(pq->head && pq->tail);
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
}
———————————————————————————————————————————
⑥判断队列是否为空(QueueEmpty)如果队列为空,pq->head==NULL
bool QueueEmpty(Queue* pq)//判断是否为空
{
assert(pq);
return pq->head == NULL;
}
———————————————————————————————————————————
⑦队列长度(QueueSize)实现一个简单的count,对队列进行遍历。
size_t QueueSize(Queue* pq)//队列长度
{
assert(pq);
QNode* cur = pq->head;
size_t size = 0;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
———————————————————————————————————————————
⑧取队列首位(QueueFront)QDataType QueueFront(Queue* pq)//取队列首位
{
assert(pq);
assert(pq->head);
return pq->head->data;
}
———————————————————————————————————————————
⑨取队列末位(QueueBack)QDataType QueueBack(Queue* pq)//取队列末位
{
assert(pq);
assert(pq->tail);
return pq->tail->data;
}
———————————————————————————————————————————
⑩打印队列(Queue print)void QueuePrint(Queue* pq)//打印队列
{
assert(pq);
if (pq->head != NULL)
{
QNode* cur = pq->head;
while (cur)
{
printf("%d ", cur->data);
cur = cur->next;
}
}
}
———————————————————————————————————————————
销毁队列(QueueDestory)就是销毁链表的道理:
void QueueDestory(Queue* pq)//销毁队列
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
完整代码:
Queue.h
#pragma once
#include
#include
#include
#include
typedef int QDataType;
typedef struct QueueNode
{
QDataType data;
struct QueueNode* next;
}QNode;
typedef struct Queue
{
QNode* head;
QNode* tail;
//size_t size;
}Queue;
void QueueInit(Queue* pq);//队列初始化
void QueueDestory(Queue* pq);//销毁队列
void QueuePush(Queue* pq, QDataType x);//入列
void QueuePop(Queue* pq);//出列
bool QueueEmpty(Queue* pq);//判断是否为空
size_t QueueSize(Queue* pq);//队列长度
QDataType QueueFront(Queue* pq);//取队列首位
QDataType QueueBack(Queue* pq);//取队列末位
void QueuePrint(Queue* pq);//打印队列
Queue.c
#include "Queue.h"
void QueueInit(Queue* pq)//队列初始化
{
assert(pq);
pq->head = pq->tail = NULL;
}
void QueueDestory(Queue* pq)//销毁队列
{
assert(pq);
QNode* cur = pq->head;
while (cur)
{
QNode* next = cur->next;
free(cur);
cur = next;
}
pq->head = pq->tail = NULL;
}
void QueuePush(Queue* pq, QDataType x)//入列
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
assert(newnode);
newnode->data = x;
newnode->next = NULL;
if (pq->tail == NULL)
{
assert(pq->head == NULL);
pq->head = pq->tail = newnode;
}
else
{
pq->tail->next = newnode;
pq->tail = newnode;
}
}
void QueuePop(Queue* pq)//出列
{
assert(pq);
assert(pq->head && pq->tail);
if (pq->head->next == NULL)
{
free(pq->head);
pq->head = pq->tail = NULL;
}
else
{
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
}
}
bool QueueEmpty(Queue* pq)//判断是否为空
{
assert(pq);
return pq->head == NULL;
}
size_t QueueSize(Queue* pq)//队列长度
{
assert(pq);
QNode* cur = pq->head;
size_t size = 0;
while (cur)
{
size++;
cur = cur->next;
}
return size;
}
QDataType QueueFront(Queue* pq)//取队列首位
{
assert(pq);
assert(pq->head);
return pq->head->data;
}
QDataType QueueBack(Queue* pq)//取队列末位
{
assert(pq);
assert(pq->tail);
return pq->tail->data;
}
void QueuePrint(Queue* pq)//打印队列
{
assert(pq);
if (pq->head != NULL)
{
QNode* cur = pq->head;
while (cur)
{
printf("%d ", cur->data);
cur = cur->next;
}
}
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)