队列的概念
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作,具有先进先出的效果。入数据的一段称为队尾,出数据的一段称为队头。队列的应用是:1.维持公平性(抽号)、2.广度优先遍历。
队列的应用
1.抽号
先排队的人先入队列,这样可以保证先进先出,从而保证公平性
2.广度优先遍历(好友推荐算法)
将用户的好友入队列,在知道用户好友数量的前提下,将好友出队列。
队列的实现
如果要想实现队列,我们发现数组无法实现,原因是每次出入队列都要进行数据的挪动,这样会消耗很多空间,故我们用链表实现。
1.队列创建及目录
typedef int QDataType;
typedef struct QueneNode
{
struct QueneNode* next;
QDataType val;
}QNode;
typedef struct Quene//避免用到二级指针与多指针,将其封装到结构体里
{
QNode* phead;
QNode* ptail;
QDataType size;
}Queue;
//初始化
void QueueInit(Queue* pq);
//入队列
void QueuePush(Queue* pq, QDataType x);
//出队列
void QueuePop(Queue* pq);
//求数据个数
int QueueSize(Queue* pq);
//取队头数据
QDataType QueueFront(Queue* pq);
//取队头数据
QDataType QueueBack(Queue* pq);
//判空
bool QueueEmpty(Queue* pq);
//队列的销毁
void QueueDestroy(Queue* pq);
在这里,我们为了防止函数中创建二级指针,于是选择再单独创建一个结构体Quene来存放一级指针;除此之外,在传递指针时我们也可以只传递一个结构体变量。
2.队列的初始化
void QueueInit(Queue* pq)
{
assert(pq);
pq->phead = NULL;
pq->ptail = NULL;
pq->size = 0;
}
3.入队列
我们仍然选择在入队列时创建新节点,最后一定不要忘了size++.
void QueuePush(Queue* pq, QDataType x)
{
assert(pq);
QNode* newnode = (QNode*)malloc(sizeof(QNode));
if (newnode == NULL)
{
perror("malloce 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++;
}
4.出队列
在出队列函数中,我们要注意节点为0和节点为1时要单独进行考虑。
void QueuePop(Queue* pq)
{
assert(pq);
assert(pq->size != 0);//0个节点
if (pq->phead->next == NULL)//一个节点
{
free(pq->phead);
pq->phead = pq->ptail = NULL;
}
else//多个节点
{
QNode* next = pq->phead->next;
free(pq->phead);
pq->phead = next;
}
pq->size--;//队列减少直接size--即可
}
5.求数据个数
int QueueSize(Queue* pq)
{
assert(pq);
return pq->size;
}
由于我们在创建结构体时已经为size单独创建了一个变量size,所以这里只需将其返回即可.
6.取队头数据
QDataType QueueFront(Queue* pq)
{
assert(pq);
assert(pq->phead);
}
取队头数据时就要注意对队头判空.
7.取队尾数据
QDataType QueueBack(Queue* pq)
{
assert(pq);
assert(pq->ptail);
return pq->ptail->val;
}
取队尾数据仍然不要忘记对队尾判空
8.队列判空
bool QueueEmpty(Queue* pq)
{
assert(pq);
return pq->size == 0;
}
如果pq的size等于0,则返回true,否则返回false.
9.队列的销毁
void QueueDestroy(Queue* pq)
{
assert(pq);
QNode* cur = pq->phead;
while (cur)
{
QNode* next = cur->next;
free(cur);
//先释放,再移动cur从而实现队列的销毁
cur = next;
}
pq->phead = pq->ptail = NULL;
pq->size = 0;
}
10.队列的打印
与栈类似,如果我们要打印队列,同样是要每打印一个数,就要对其进行一次删除操作,代码如下:
int main()
{
Queue q;
QueueInit(&q);
QueuePush(&q, 1);
QueuePush(&q, 2);
QueuePush(&q, 3);
QuenuePush(&q, 4);
while (!QueueEmpty(&q))
{
printf("%d ", QueueFront(&q));
QueuePop(&q);
}
printf("\n");
return 0;
}
队列的应用
用队列实现栈
在这里我们用两个队列实现栈,保持一个存数据,一个为空,入数据入不为空的队列,出数据就通过空队列.
通过结构体创建两队列
typedef struct {
Queue q1;
Queue q2;
} MyStack;
栈的初始化
对于栈的初始化,我们首先仍然要创建内存(malloc).
然后对q1q2分别初始化:
MyStack* myStackCreate() //初始化Init
{
MyStack* obj = (MyStack*)malloc(sizeof(MyStack));//malloc了内存可以一直存在
QueueInit(&obj->q1);
QueueInit(&obj->q2);
return obj;
}
栈的插入
由于队列的特点是先进后出,所以对于栈的插入来说可以直接将q1q2里的值插入即可。要记得判空。
void myStackPush(MyStack* obj, int x) {
if(!QueueEmpty(&obj->q1))
{
QueuePush(&obj->q1, x);
}
else
{
QueuePush(&obj->q2, x);
}
}
栈的删除
对于代码的if else语句,我们还可以用假设法对其进行优化,假设q1为空,q2不为空,然后将队列中的元素导走。
int myStackPop(MyStack* obj) {
//假设法
Queue* empty = &(obj->q1);
//不为空的前size-1个导走
Queue* nonempty = &(obj->q2);
if(!QueueEmpty(&obj->q1))
{
nonempty = &obj->q1;
empty = &obj->q2;
}
while(QueueSize(nonempty) > 1)
{
QueuePush(empty,QueueFront(nonempty));
QueuePop(nonempty);
}
int top = QueueFront(nonempty);
QueuePop(nonempty);
return top;
}
取栈顶元素
若要取栈顶元素,则直接将队列尾return 即可
int myStackTop(MyStack* obj) {
if(!QueueEmpty(&obj->q1))//队尾直接返回即可
{
return QueueBack(&(obj->q1));
}
else
{
return QueueBack(&(obj->q2));
}
}
判空
使用"&&"操作符,两队列都为空则返回空,有一个不为空则不返回空。
bool myStackEmpty(MyStack* obj) {
return QueueEmpty(&(obj->q1))&&QueueEmpty(&(obj->q2));
}
栈的释放
对于栈的释放,要先对两队列依次进行释放,再将obj直接释放
void myStackFree(MyStack* obj) {
//不能直接释放obj
QueueDestroy(&obj->q1);
QueueDestroy(&obj->q2);
free(obj);
}
如果觉得我写的不错,别忘了点赞收藏+评论!