数据结构学习笔记---006
数据结构之队列
前言:
前篇学习了 数据结构的栈,那么这篇继续学习队列的基础内容。
/知识点汇总/
1、队列的概念和结构
队列 :只允许在一端进行数据操作,在另一端进行删除数据操作的特殊线性表
队列具有先进先出(FIFO:First In First Out)的特点.
入队列操作 :进行插入操作的一端称为队尾。
出队列操作:进行删除操作的一端称为队头。
与栈不同,队列的出队列顺序与出队列顺序是唯一的。
比如入队顺序:1,2,3,4
则出队顺序:1,2,3,4
1.1、如何实现队列?
双向链表、数组、单链表?
排除数组,大量挪动数据
其次,能用单链表解决就暂时不用双链表。所以采用类似于单链表的形式实现。另外,由于队列的特性,一端进队列,另一端出队列,所以额外定义一个结构体类型更加便于操作。
队列的存储结构
c
typedef int QDataType;
//定义队列结构体成员和变量
typedef struct QueueNode
{
QDataType val;
struct QDataNode* next;
}QNode;
//由于队列的使用,存在多个值,采用结构体的指针,就不用二级指针了。
typedef struct Queue
{
QNode* phead;
QNode* ptail;
int size; //以空间换时间,解决求队内元素个数的时间复杂度问题
}Queue;
2、队列的实现
2.1、队列的Queue.h
c
//队列初始化
void QueueInit(Queue* pq);
//队列销毁
void QueueDestory(Queue* pq);
//队列入队
void QueuePush(Queue* pq, QDataType x);
//队列出队
void QueuePop(Queue* pq);
//取队头
QDataType QueueFront(Queue* pq);
//取队尾
QDataType QueueBack(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
//取队内元素大小
int QueueSize(Queue* pq);
2.2、队列的Queue.c
队列的操作算是比较简单,理解性掌握即可。只需要注意一下,操作空队列和一个元素的情况的处理即可。
2.2.1、队列的初始化
c
//队列初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
2.2.2、队列销毁
c
//队列销毁
void QueueDestory(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead;
while (cur)
{
QNode* next =cur->next;
free(cur);
cur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
2.2.3、队列入队、出队操作
c
//队列入队
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloc fail");
return;
}
newnode->next = NULL;
newnode->val = x;
if (pq->ptail == NULL)
{
pq->ptail = pq->phead = newnode;
}
else
{
pq->ptail->next = newnode;
pq->ptail = newnode;
}
pq->size++;
}
//队列出队
void QueuePop(Queue* pq)
{
assert(pq);
//判空
assert(pq->phead);
//一个节点的情况
QNode* del = pq->phead;
pq->phead = pq->phead->next;
free(del);
del = NULL;
if (pq->phead == NULL)
{
pq->ptail = NULL;
}
pq->size--;
}
2.2.4、队列取队头、队尾操作
c
//取队头
QDataType QueueFront(Queue* pq)
{
assert(pq);
//判空
assert(pq->phead);
return pq->phead->val;
}
//取队尾
QDataType QueueBack(Queue* pq)
{
assert(pq);
//判空
assert(pq->phead);
return pq->ptail->val;
}
2.2.5、队列的判空
c
//判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
//判空
return pq->phead == NULL;
}
2.2.5、取队内元素大小
c
//取队内元素大小
int QueueSize(Queue* pq)
{
assert(pq);
//判空
//assert(pq->phead);
return pq->size;
}
2.3、队列的main.c
c
#include "Queue.h"
//测试1:
void TestQ1()
{
Queue q;
QueueInit(&q);//初始化
QueuePush(&q, 1);//入队
QueuePush(&q, 2);//入队
QueuePush(&q, 3);//入队
QueuePush(&q, 4);//入队
QueuePush(&q, 5);//入队
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
QueueDestory(&q);//销毁
}
//测试2:
void TestQ2()
{
Queue q;
QueueInit(&q);//初始化
QueuePush(&q, 1);//入队
QueuePush(&q, 2);//入队
QueuePush(&q, 3);//入队
printf("%d ", QueueFront(&q));
QueuePop(&q);
QueuePush(&q, 4);//入队
printf("%d ", QueueFront(&q));
QueuePop(&q);
QueuePush(&q, 5);//入队
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
QueueDestory(&q);//销毁
}
int main()
{
//TestQ1();
TestQ2();
return 0;
}
小结 :
队列的元素入队出队顺序是固定的,属于一对一的关系
3、队列巩固练习
3.1、练习题1:用队列实现栈
练习题1:用队列实现栈 -- 实现两个队列实现先进后出栈的push、top、pop、empty
思路 :将第一个队列出队,到第二个队列入队,然后反复颠倒,直到按栈的方式完成所有操作。
核心:一个队列存储数据,另一个队列在出队列时,倒数据。
c
#include "Queue.h"
typedef struct
{
Queue q1;
Queue q2;
}MyStack;
MyStack* myStackCreat()
{
MyStack* pst = (MyStack*)malloc(sizeof(MyStack));
QueueInit(&pst->q1);//注意这里的传参 ,栈地址
QueueInit(&pst->q2);
return pst;
}
void myStackPush(MyStack* obj, int x)
{
if (!QueueEmpty(&obj->q1))//谁不为空,就向那个队列入队数据
{
QueuePush(&obj->q1, x);
}
else
{
QueuePush(&obj->q2, x);
}
}
int myStackPop(MyStack* obj)
{
Queue* emptyq = &obj->q1;
Queue* noemptyq = &obj->q2;
if (!QueueEmpty(&obj->q1))
{
emptyq = &obj->q2;
noemptyq = &obj->q1;
}
//将非空队列前N-1个导入空队列,最后一个就是要的栈顶元素。
while (QueueSize(noemptyq) > 1)
{
QueuePush(emptyq, QueueFront(noemptyq));
QueuePop(noemptyq);
}
//直到栈顶元素就是最后一个元素
int top = QueueFront(noemptyq);
QueuePop(noemptyq);
return top;
}
int myStackTop(MyStack* obj)
{
if (!QueueEmpty(&obj->q1))
{
return QueueBack(&obj->q1);
}
else
{
return QueueBack(&obj->q2);
}
}
bool msStackEmpty(MyStack* obj)
{
return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}
MyStack* myStackFree(MyStack* obj)
{
QueueDestory(&obj->q1);
QueueDestory(&obj->q2);
free(obj);
obj = NULL;
return NULL;
}
int main()
{
MyStack* pst = myStackCreat();
myStackPush(pst, 1);
myStackPush(pst, 2);
myStackPush(pst, 3);
myStackPush(pst, 4);
myStackPush(pst, 5);
while (!msStackEmpty(pst))
{
printf("%d ", myStackTop(pst));
myStackPop(pst);
}
myStackFree(pst);
return 0;
}
3.2、练习题2:用栈实现队列
练习题2:用栈实现队列 -- 实现两个栈实现先进先出队列的push、top、pop、empty
思路:两个栈,入数据都是入左边栈,出数据都是出右边栈,然后,右边不为空,则右边先出完,左边再倒数据。总之,右边栈没数据,左边栈再到数据。
c
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>
typedef int STDatatype;
//定义数组栈结构体成员及变量
typedef struct Stack
{
STDatatype* a;
int top; //标识栈顶位置
int capacity;
}ST;
//栈初始化
void STInit(ST* pst)
{
assert(pst);
pst->a = NULL;
pst->capacity = 0;
//注意栈顶给-1还是0,因为要明确top是栈顶元素还是栈顶元素的下一个元素。而且此时栈内只有一个元素时表示top=0,那么top无法区分top指向栈顶元素,还是指向栈顶元素的下一个位置,所以给-1还是0,写法不同
//pst->top = -1; //表示top指向栈顶元素的位置
pst->top = 0; //表示top指向栈顶元素的下一个元素位置。
//主要区别,先++top还是先赋值的区别,根据自己合理即可
}
//栈销毁
void STDestory(ST* pst)
{
assert(pst);
free(pst->a);
pst->a = NULL;
pst->capacity = pst->top = 0;
}
//入栈
void STPush(ST* pst, STDatatype x)
{
assert(pst);
//检查容量,扩容
if (pst->top == pst->capacity)
{
int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
STDatatype* tmp = (STDatatype*)realloc(pst->a, sizeof(STDatatype) * newcapacity);
if (tmp == NULL)
{
perror("realloc fail");
return;
}
pst->a = tmp;
pst->capacity = newcapacity;
}
//入栈
pst->a[pst->top] = x;
pst->top++;
}
//出栈
void STPop(ST* pst)
{
assert(pst);
//判空
assert(pst->top > 0);
pst->top--;
}
//获取栈顶元素
STDatatype STTop(ST* pst)
{
assert(pst);
//判空
assert(pst->top > 0);
return pst->a[pst->top - 1];//注意此时top表示栈顶元素的下一个,所以需要减1
}
//栈空判定
bool STEmpty(ST* pst)
{
assert(pst);
//直接以判断返回即可
return pst->top == 0;
}
//获取栈的大小
int STSize(ST* pst)
{
assert(pst);
return pst->top;
}
typedef struct
{
ST pushst;
ST popst;
}MyQueue;
MyQueue* myQueueCreate()
{
MyQueue* obj = (MyQueue*)malloc(sizeof(MyQueue));
//初始化栈
STInit(&obj->pushst);//注意传地址与二级指针的传参应用
STInit(&obj->popst);//类型与接口类型相同,所以这里传地址
return obj;
}
void myQueuePush(MyQueue* obj,int x)
{
//一个栈入栈,一个栈入出栈的元素再出栈
//所以这里栈只管入栈元素就行
STPush(&obj->pushst, x);
}
int myQueuePeek(MyQueue* obj);
int myQueuePop(MyQueue* obj)
{
int front = myQueuePeek(obj);//因为peek只返回栈顶元素,不会删除操作,所以直接利用返回值进行popQueue操作
STPop(&obj->popst);//取得栈顶元素就出元素(即删除),没有元素就返回的空
return front;
}
int myQueuePeek(MyQueue* obj)//返回对头的数据,不删除元素
{
//写法1:
//if (!STEmpty(&obj->popst))//如果popst不为空,那么直接出数据
//{
// return STTop(&obj->popst);
//}
//else//popst不为空,那么导数据
//{
// //倒数据
// while (!STEmpty(&obj->pushst))//当push不为空,就导数据
// {
// STPush(&obj->popst, STTop(&obj->pushst));
// STPop(&obj->pushst);
// }
// //否则,pushst为空就出popst
// return STTop(&obj->popst);
//}
//写法二:
if (STEmpty(&obj->popst))//如果popst为空,那么直接导数据
{
//倒数据
while (!STEmpty(&obj->pushst))//当push不为空,就导数据
{
STPush(&obj->popst, STTop(&obj->pushst));//将pushst的栈顶元素取出,入popst的栈。
STPop(&obj->pushst);//然后删除pushst元素
}
}
//否则,pushst为空就出popst
return STTop(&obj->popst);//反正出数据都是出的popst
}
bool myQueueEmpty(MyQueue* obj)
{
//两边栈都为空,队列才为空
return STEmpty(&obj->pushst) && STEmpty(&obj->popst);
}
void myQueueFree(MyQueue* obj)
{
//两个栈都销毁
STDestory(&obj->pushst);
STDestory(&obj->popst);
free(obj);
obj = NULL;
}
int main()
{
MyQueue* pst = myQueueCreate();
myQueuePush(pst, 1);
myQueuePush(pst, 2);
myQueuePush(pst, 3);
myQueuePush(pst, 4);
myQueuePush(pst, 5);
while (!myQueueEmpty(pst))
{
printf("%d ", myQueuePeek(pst));
myQueuePop(pst);
}
myQueueFree(pst);
return 0;
}
3.3、练习题3:设计循环队列
练习题3:设计循环队列 -- 满足队列的先进先出的原则,其次对尾与队头被连接后形成一个循环。
注意的是,循环队列满了,就不能再入队了,删除同样需要判断是否为空了。
主要考察队头队尾的关系运算。
怎么判空?
队头等于队尾为空
怎么判满?
1.增加一个size,front = back & & size=0为空,size!=0,不为空
2.对开一个位置(解决假溢出,或者说无法区分空和满的问题),始终保持一个位置空置,当k为队列长度(数据个数),k+1为空间个数,此时(back+1)%(k+1) == front就是满
总结 :front = back就是空 back的下一个 = front就是满
思路1 :以开辟多一个空间的数组实现,front指向头,back指向队尾的下一个(不建议指向队尾,不好操作),关键在于取模运算
思路2:链表实现,front==back就是空,back->next == front就是满,但是相比数组较为复杂(唯独就是队尾不好取,单链表无法回溯,要么增加一个指针,或者双向链表)
c
#include "Queue.h"
typedef struct
{
int* a; //数组
int front; //头
int back; //尾
int k; //队内元素个数或队列大小
}MyCircularQueue;
MyCircularQueue* myCircularQueueCreate(int k)
{
MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
//循环队列的初始化
obj->a = (int*)malloc(sizeof(int) * (k + 1));//开辟元素个数加1个空间
obj->front = 0;
obj->back = 0;
obj->k = k;
return obj;
}
bool myCircularQueueIsFull(MyCircularQueue* obj);
bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
if (myCircularQueueIsFull(obj))
{
return false;
}
obj->a[obj->back] = value;
obj->back++;
//循环处理
obj->back %= (obj->k + 1);
//obj->k++;//队列的大小是固定的,不是size
return true;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj);
bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
if (myCircularQueueIsEmpty(obj))
{
return false;
}
++obj->front;
//循环处理
obj->front %= (obj->k + 1);
return true;
}
bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
return obj->front == obj->back;//队头等于队尾就为空,要注意的是此时的队尾是指向多开的空间地址
}
bool myCircularQueueIsFull(MyCircularQueue* obj)
{
return (obj->back + 1) % (obj->k + 1) == obj->front;//队尾的下一个==front就是满
}
int myCircluarQueueFront(MyCircularQueue* obj)//取队头
{
if (myCircularQueueIsEmpty(obj))
return -1;
return obj->a[obj->front];
}
//写法1:
//int myCircularQueueRear(MyCircularQueue* obj)//取队尾
//{
// //return obj->a[obj->back - 1];//直接取出这种属于,情况没有考虑到位,会涉及到越界的问题
// //两种处理方法:单独判断一下即可
// if (obj->back == 0)
// return obj->a[obj->k];//back被循环到头的位置,back==0,那么取队尾就是返回的k
// else
// return obj->a[obj->back - 1];//否则,其它情况就是满足返回back-1的位置
//}
//写法二:
int myCircularQueueRear(MyCircularQueue* obj)//取队尾
{
if (myCircularQueueIsEmpty(obj))
return -1;
return obj->a[(obj->back - 1 + obj->k + 1) % (obj->k + 1)];
//back == 0 (0-1+4+1)%(4+1) = 4 处理back == 0 准备的 处理back>0准备的
//满足上述条件,简化得出:
//return obj->a[(obj->back + obj->k) % (obj->k + 1)];
}
//扩展:
//如何计算循环队列的数据个数:正常情况下,back-front
//满足特殊情况,考虑循环情况,back-front为负数,那么就是:(back+(k+1)-front)%(k+1)
void myCirCularQueueFree(MyCircularQueue* obj)
{
free(obj->a);
obj->a = NULL;
free(obj);
obj = NULL;
}
int main()
{
MyCircularQueue* pst = myCircularQueueCreate(5);
myCircularQueueEnQueue(pst, 1);
myCircularQueueEnQueue(pst, 2);
myCircularQueueEnQueue(pst, 3);
myCircularQueueEnQueue(pst, 4);
myCircularQueueEnQueue(pst, 5);
if (!myCircularQueueIsEmpty(pst))
{
printf("%d ", myCircluarQueueFront(pst));
printf("%d ", myCircularQueueRear(pst));
}
myCircularQueueDeQueue(pst);//头删
if (!myCircularQueueIsEmpty(pst))
{
printf("%d ", myCircluarQueueFront(pst));
printf("%d ", myCircularQueueRear(pst));
}
myCirCularQueueFree(pst);
return 0;
}