数据结构——队列
目录
- 队列的概念
- 队列的实现
-
- 初始化
- 销毁
- 入队
- 出队
- 判空
- 队列长度
- 返回队头元素
- 返回队尾元素
队列的概念
队列是一种特殊的线性结构
只允许在一端进行插入操作,在另一端进行删除操作,队列具有先进先出FIFO(First In First Out)
入队:进行插入操作,进行插入的一端称为队尾
出队:进行删除操作,进行删除的一端称为队头
队列的实现
因为队列在尾进在头出,如果用顺序表实现,那么出队就需要挪动顺序表的数据,效率低
所以队列用链表实现更好
我们先写一个链表的结构体:
typedef int QueueDataType;
typedef struct QueueNode
{struct QueueNode* next;QueueDataType data;
}QNode;
在主函数中,为了方便,我们定义一个头指针
head
指向队头和尾指针tail
指向队尾QNode* head = NULL; QNode* tail = NULL;
如果这么写的话,在设计函数时,就要至少传2个指针,并且为二级指针,这是否的麻烦
所以我们可以再定义一个结构体,将head
和tail
都放到这个结构体中,那么设计函数时只需要传一个指针就可以了
typedef struct Queue
{QNode* head;QNode* tail;int size;
}Queue;
这样设计的话,函数中传这个结构体的参数,通过结构体指针就可以改变结构体了,所以也不用传二级指针了
这里在结构体内还定义了一个int size
,这是表示队列的长度,这个设计并非多余,在后面的实现中就会发现这个size
能提高效率
这里的队列实现需要定义2个结构体,这是在之前的数据结构中没有出现的
初始化
void QueueInit(Queue* pq)
{assert(pq);pq->head = pq->tail = NULL;pq->size = 0;
}
将head
和tail
都初始化为空,size
为0
销毁
void QueueDestroy(Queue* pq)
{assert(pq);QNode* cur = pq->head;while (cur){QNode* nex = cur->next;free(cur);cur = nex;}pq->head = NULL;pq->tail = NULL;pq->size = 0;
}
入队
入队,首先就是开辟一个新节点
QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;
然后就是入队了,在队尾入队
这里如果head==NULL
,就说明当前队列还没有元素head
和tail
都为空
所以让head
指向这个第一个节点,同时这个节点也是自己的尾,tail
也应指向这个节点
pq->head = pq->tail = newnode;
如果head
不为空,就说明当前队列中有元素,直接在tail
后面插入新节点即可
void QueuePush(Queue* pq, QueueDataType x)
{assert(pq);QNode* newnode = (QNode*)malloc(sizeof(QNode));if (newnode == NULL){perror("malloc fail");return;}newnode->data = x;newnode->next = NULL;if (pq->head == NULL){pq->head = pq->tail = newnode;}else{pq->tail->next = newnode;pq->tail = pq->tail->next;}pq->size++;}
出队
出队,首先应用断言判断一下队列是否为空
assert(!QueueEmpty(pq));
在队头出队
首先用next
指针保存head->next
,然后就可以free
掉head
了,然后再将head
指向新的头next
QNode* next = pq->head->next;
free(pq->head);
pq->head = next;
在这里会发现,如果队列只要一个节点,next
为空,然后虽然能够让head
为空,但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;}
所以最终代码:
void QueuePop(Queue* pq)
{assert(pq);assert(!QueueEmpty(pq));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;}pq->size--;
}
判空
bool QueueEmpty(Queue* pq)
{assert(pq);return pq->size == 0;
}
队列长度
int QueueSize(Queue* pq)
{assert(pq);return pq->size;
}
这里就最能看出在结构体中定义size
的好处了
如果不去定义,那么在这里求长度就会遍历一遍队列,导致效率低
而之前定义了size
,这里直接返回size
即可,效率高
返回队头元素
QueueDataType QueueFront(Queue* pq)
{assert(pq);return pq->head->data;
}
一般通过QueueFront
和QueuePop
2个函数来得到队列中的数据
打印队列中数据:
while (!QueueEmpty(&q)){printf("%d ", QueueFront(&q));QueuePop(&q);}
返回队尾元素
QueueDataType QueueBack(Queue* pq)
{assert(pq);return pq->tail->data;
}