本篇博客会讲解队列这种数据结构,并使用C语言实现。
概况
什么是队列呢?队列是一种先进先出的数据结构,即First In First Out,简称FIFO。队列有2端,分别是队头和队尾,规定只能在队尾插入数据(入队列),在队头删除数据(出队列)。比如:下图中,左边是队尾,右边是队头。
入队列:1
入队列:2
入队列:3
再依次入队列:[4 5 6]
出队列(会删除1)
出队列(会删除2)
出队列(会删除3)
结构的定义
要实现队列,如果使用数组,效率太低,因为头插、头删的时间复杂度是O(N)。所以建议用链表实现队列,并且建议使用带头+单向+不循环链表实现,入队列时尾插,出队列时头删。原因是:
- 单链表可以节省一些指针,结构更简单,但不影响效率。
- 带哨兵位的头结点,第一次插入数据,可以统一处理。
- 不使用循环链表,因为没必要。
结点的定义如下:
typedef int QDataType;
typedef struct QListNode
{
struct QListNode* next;
QDataType data;
}QNode;
为了省去尾插时找尾结点的过程,我们需要同时维护头结点和尾结点。同时,为了统计结点个数方便,需要记录有效数据的个数,这样就不用每次都遍历链表计数了。
// 队列的结构
typedef struct Queue
{
QNode* front; // 队头
QNode* tail; // 队尾
int size; // 有效数据个数
}Queue;
申请结点
申请结点的函数声明如下:
QNode* BuyQueueNode(QDataType data);
申请一个结点,初始化其数据域和指针域。
QNode* BuyQueueNode(QDataType data)
{
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return NULL;
}
newnode->data = data;
newnode->next = NULL;
return newnode;
}
初始化
初始化队列的函数声明如下:
void QueueInit(Queue* pq);
开辟一个哨兵位的头结点,并更新头尾指针。
void QueueInit(Queue* pq)
{
assert(pq);
// 定义哨兵位
QNode* head = BuyQueueNode(0);
pq->front = head;
pq->tail = head;
pq->size = 0;
}
入队列
入队列的函数声明如下:
void QueuePush(Queue* pq, QDataType data);
由于有哨兵位的头结点,不需要分类讨论,在尾结点后面直接插入即可。入队列只会影响尾结点,不会影响头结点。
void QueuePush(Queue* pq, QDataType data)
{
assert(pq);
// 创建新结点
QNode* newnode = BuyQueueNode(data);
// 链接在尾部
pq->tail->next = newnode;
pq->tail = newnode;
pq->size++;
}
出队列
出队列的函数声明如下:
void QueuePop(Queue* pq);
这里需要分类讨论:
- 队列为空,不能删除。
- 队列中除去哨兵位的头结点之外只有1个结点,删除后需要把尾结点重置为哨兵位。
- 其他情况,正常尾删即可。
void QueuePop(Queue* pq)
{
assert(pq);
// 判断非空
assert(!QueueEmpty(pq));
if (pq->size == 1)
{
// 删除数据
free(pq->tail);
pq->front->next = NULL;
pq->tail = pq->front;
}
else
{
// 至少2个数据
QNode* first = pq->front->next;
QNode* second = first->next;
// 删除数据
free(first);
first = NULL;
// 链接
pq->front->next = second;
}
pq->size--;
}
取队头、队尾数据
函数声明如下:
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
直接取对应的数据即可,注意要检查队列非空。
QDataType QueueFront(Queue* pq)
{
assert(pq);
// 判断非空
assert(!QueueEmpty(pq));
return pq->front->next->data;
}
QDataType QueueBack(Queue* pq)
{
assert(pq);
// 判断非空
assert(!QueueEmpty(pq));
return pq->tail->data;
}
统计有效数据个数
统计有效数据个数的函数声明如下:
int QueueSize(Queue* pq);
由于我们维护size,用来记录有效数据个数,这个函数的实现就非常方便。
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
判空
判断队列是否为空的函数声明如下:
bool QueueEmpty(Queue* pq);
当size为0时,队列为空。
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
销毁
销毁队列的函数声明如下:
void QueueDestroy(Queue* pq);
遍历链表,销毁结点,最后销毁哨兵位。
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* del = pq->front;
while (del)
{
QNode* next = del->next;
free(del);
del = next;
}
pq->front = NULL;
pq->tail = NULL;
pq->size = 0;
}
总结
使用单向带头不循环链表实现队列,满足“先进先出”的特性。
感谢大家的阅读!