队列的概念、C语言实现及应用

一、队列的概念

链表是一种只允许在一段插入数据,再另一端删除数据的特殊线性表。插入数据的一段称为队尾,删除数据的一段称为队头。

队列的使用场景如饭店排号、"好友推荐"机制给用户推荐关系从近到远的好友等。

二、队列的代码实现

我们用链表来实现栈。

2.1 定义节点结构体和队列结构体

因为每次插入数据都是从队尾插入,如果每次都要通过遍历一遍链表的方式来找到队尾,就太麻烦了。所以我们用一个结构体记录一下链表的头和尾,这样也可以避免使实现功能的时候传二级指针参数。此外我们在栈这个结构体中加上一个整形成员size来记录队列中数据的数量,这样可以很方便地获取队列中有多少个数据。

cpp 复制代码
typedef struct QueueNode
{
	QDataType val;
	QDataType* next;
}QNode;

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

2.1 初始化

cpp 复制代码
QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

2.2 插入数据

cpp 复制代码
void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	
	newnode->val = x;
	newnode->next = NULL;
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = pq->ptail->next;
	}
        pq->size++;
}

2.3 删除数据

cpp 复制代码
void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->size);

	//一个节点
	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--;
}

注意,如果链表中只有一个节点,删除后ptail就变成了野指针,所以要分类讨论。

2.4 获取队列中的数据个数

cpp 复制代码
int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

2.5 其他功能

获取队头、队尾数据,判空,都很简单,一两行代码。

cpp 复制代码
QDataType QueueFront(Queue* pq)
{
	return pq->phead->val;
}

QDataType QueueBack(Queue* pq)
{
	return pq->ptail->val;
}

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

三、例题

3.1 用队列实现栈

解题思路:栈是后进先出,而队列是先进先出。用队列实现栈时,我们要让栈顶(队尾)的元素出栈,就把除了栈顶(队尾)元素的其他元素复制到另一个队列里,再取出栈顶元素即可。

我们总是让一个队列负责存储数据,另一个队列为空,出数据的时候通过空白的队列倒一下。

我们只需要把刚才写的的队列的代码全部复制一遍,然后在此基础上按上述思路完成栈的各种功能即可。

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

typedef int QDataType;

typedef struct QueueNode
{
	QDataType val;
	struct QueueNode* next;
}QNode;

typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;
}Queue;

void QueueInit(Queue* pq)
{
	assert(pq);
	pq->phead = pq->ptail = NULL;
	pq->size = 0;
}

void QueuePush(Queue* pq, QDataType x)
{
	assert(pq);
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}

	newnode->val = x;
	newnode->next = NULL;
	if (pq->phead == NULL)
	{
		pq->phead = pq->ptail = newnode;
	}
	else
	{
		pq->ptail->next = newnode;
		pq->ptail = pq->ptail->next;
	}
	pq->size++;
}

void QueuePop(Queue* pq)
{
	assert(pq);
	assert(pq->size);

	//一个节点
	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--;
}

int QueueSize(Queue* pq)
{
	assert(pq);

	return pq->size;
}

QDataType QueueFront(Queue* pq)
{
	return pq->phead->val;
}

QDataType QueueBack(Queue* pq)
{
	return pq->ptail->val;
}

bool QueueEmpty(Queue* pq)
{
	assert(pq);
	return pq->size == 0;
}

void QueueDestroy(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;
}

typedef struct
{
	Queue q1;
	Queue q2;
} MyStack;


MyStack* myStackCreate()
{
	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* empty = &obj->q1;
	Queue* nonEmpty = &obj->q2;
	if (!QueueEmpty(&obj->q1))
	{
		empty = &obj->q2;
		nonEmpty = &obj->q1;
	}
	while (QueueSize(nonEmpty) > 1)
	{
		QueuePush(empty, QueueFront(nonEmpty));
		QueuePop(nonEmpty);
	}
	int top = QueueFront(nonEmpty);
	QueuePop(nonEmpty);
	return top;
}

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));
}

void myStackFree(MyStack* obj)
{
	QueueDestroy(&obj->q1);
	QueueDestroy(&obj->q2);

	free(obj);
}


/**
 * Your MyStack struct will be instantiated and called as such:
 * MyStack* obj = myStackCreate();
 * myStackPush(obj, x);
 
 * int param_2 = myStackPop(obj);
 
 * int param_3 = myStackTop(obj);
 
 * bool param_4 = myStackEmpty(obj);
 
 * myStackFree(obj);
*/

3.2 实现循环队列

这道题的核心就是,有限的空间,保证先进先出,重复使用。

可以用数组来解决这个问题。创建一个数组,再用两个整型变量,head指向队列的头,tail指向队列的尾的下一个元素。但是这样会产生一个"假溢出"问题,即无法判断head == tail的时候队列是满的还是空的。解决办法之一是再定义一个整型变量size来记录队列中元素的个数;另一种解决办法是额外多开一个空间,这样只有空的时候是head == tail,满的时候是(tail + 1)%(k + 1) == head。

cpp 复制代码
typedef struct
{
    int* a;
    int head;
    int tail;
    int k;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k)
{
    MyCircularQueue* obj = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));

    obj->a = malloc(sizeof(int)*(k+1));
    obj->head = 0;
    obj->tail = 0;
    obj->k = k;

    return obj;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj)
{
    return obj->head == obj->tail;
}

bool myCircularQueueIsFull(MyCircularQueue* obj)
{
    return (obj->tail+1) % (obj->k+1) == obj->head;
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value)
{
    if(myCircularQueueIsFull(obj))
        return false;
    else
    {
        obj->a[obj->tail] = value;
        obj->tail++;

        obj->tail %= (obj->k+1);
        return true;
    }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj)
{
    if(myCircularQueueIsEmpty(obj))
        return false;
    else
    {
        obj->head++;
        obj->head %= (obj->k+1);

        return true;
    }
}

int myCircularQueueFront(MyCircularQueue* obj)
{
    if(myCircularQueueIsEmpty(obj))
    {
        return -1;
    }
    else
    {
        return obj->a[obj->head];
    }
}

int myCircularQueueRear(MyCircularQueue* obj)
{
    if(myCircularQueueIsEmpty(obj))
        return -1;
    else
    {
        return obj->a[(obj->tail - 1 + obj->k + 1) % (obj->k + 1)];
    }
}

void myCircularQueueFree(MyCircularQueue* obj)
{
    free(obj->a);
    free(obj);    
}

/**
 * Your MyCircularQueue struct will be instantiated and called as such:
 * MyCircularQueue* obj = myCircularQueueCreate(k);
 * bool param_1 = myCircularQueueEnQueue(obj, value);
 
 * bool param_2 = myCircularQueueDeQueue(obj);
 
 * int param_3 = myCircularQueueFront(obj);
 
 * int param_4 = myCircularQueueRear(obj);
 
 * bool param_5 = myCircularQueueIsEmpty(obj);
 
 * bool param_6 = myCircularQueueIsFull(obj);
 
 * myCircularQueueFree(obj);
*/

其中,"到达数组最后一个位置,需要循环到第一个位置"的逻辑,用%(k+1)来实现;"到达数组第一个位置,需要循环到最后一个位置"的逻辑,用+(k+1)再%(k+1)的方式来实现。

这道题也可以用链表来实现,只不过队尾数据的访问会比较麻烦。

3.3 用栈实现队列

解题思路:两个栈分工合作,一个负责入数据(pushst),一个负责出数据(popst)。所有数据都从pushst中进入,要出数据的时候就从popst的顶端出。当popst为空的时候,就把数据从pushst中"倒"一下,也就是一个一个复制过来。这样,原本在pushst中正序排列的数据到了popst中变为倒序排列,此时从popst的顶端出数据正好符合队列"先进先出"的规则。

cpp 复制代码
typedef int STDataType;

typedef struct Stack
{
	STDataType* a;
	int top;
	int capacity;
}ST;

//初始化 销毁
void STInit(ST* pst)
{
	assert(pst);
	pst->a = NULL;
	pst->top = 0;
	pst->capacity = 0;
}


void STDestroy(ST* pst)
{
	assert(pst);

	free(pst->a);
	pst->a = NULL;
	pst->top = pst->capacity = 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 = realloc(pst->a, newcapacity * (sizeof(STDataType)));
		if (tmp == NULL)
		{
			perror("realloc");
			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--;
}

//判空
bool STEmpty(ST* pst)
{
	assert(pst);

	return(pst->top == 0);
}

//取栈顶元素
STDataType STTop(ST* pst)
{
	assert(pst);
	assert(pst->top);

	return pst->a[pst->top - 1];

} 

//获取数据个数
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)
{
    if(STEmpty(&(obj->popst)))
    {
        while(!STEmpty(&(obj->pushst)))
        {
            int top = STTop(&(obj->pushst));
            STPush(&(obj->popst),top);
            STPop(&(obj->pushst));
        }
        return STTop(&(obj->popst));
    }
    else
    {
        return STTop(&(obj->popst));
    }

}

int myQueuePop(MyQueue* obj)
{
    int front = myQueuePeek(obj);
    STPop(&(obj->popst));
    return front;
}

bool myQueueEmpty(MyQueue* obj)
{
    return STEmpty(&(obj->pushst)) && STEmpty(&(obj->popst));
}

void myQueueFree(MyQueue* obj)
{
    STDestroy(&(obj->popst));
    STDestroy(&(obj->popst));

    free(obj);
}

/**
 * Your MyQueue struct will be instantiated and called as such:
 * MyQueue* obj = myQueueCreate();
 * myQueuePush(obj, x);
 
 * int param_2 = myQueuePop(obj);
 
 * int param_3 = myQueuePeek(obj);
 
 * bool param_4 = myQueueEmpty(obj);
 
 * myQueueFree(obj);
*/

在myQueuePeek中我们实现了"倒数据"的逻辑,在myQueuePeek中只需调用即可。

END

相关推荐
玖釉-2 小时前
下一个排列:从字典序到原地算法的完整推导
数据结构·c++·windows·算法
枕星而眠3 小时前
数据结构八大排序详解(一):四大简单排序
c语言·数据结构·c++·后端
过期动态3 小时前
【LeetCode 热题 100】移动零
java·数据结构·算法·leetcode·职场和发展·rabbitmq
努力努力再努力wz3 小时前
【Qt入门系列】:按钮组件全解析:从 QAbstractButton 到快捷键事件、单选与复选机制
c语言·开发语言·数据结构·c++·git·qt·github
Dlrb12114 小时前
数据结构-栈
数据结构··内核栈·满栈空栈·增栈减栈
菜菜的顾清寒5 小时前
力扣HOT100(32)二叉树的中序遍历
数据结构·算法·leetcode
Shan12056 小时前
线段树入门:更新数组后处理求和查询
数据结构·算法
老花眼猫7 小时前
数学艺术图案画-曼陀罗单色版(4)
c语言·经验分享·青少年编程·课程设计
我命由我123457 小时前
C++ - 面向对象 - 常成员函数
android·java·linux·c语言·开发语言·c++·算法
徐安安ye7 小时前
FlashAttention自定义算子开发:Ascend C Tiling模板与调试技巧
c语言·开发语言·人工智能