1.4 数据结构之 队列

编程总结

在刷题之前需要反复练习的编程技巧,尤其是手写各类数据结构实现,它们好比就是全真教的上乘武功
学习参考:
https://leetcode.cn/leetbook/read/queue-stack/kkqf1/

为了实现队列,我们可以使用动态数组和指向队列头部的索引。

如上所述,队列应支持两种操作:入队和出队。入队会向队列追加一个新元素,而出队会删除第一个元素。 所以我们需要一个索引来指出起点。
在这里插入图片描述
上面的实现很简单,但在某些情况下效率很低。 随着起始指针的移动,浪费了越来越多的空间。 当我们有空间限制时,这将是难以接受的
让我们考虑一种情况,即我们只能分配一个最大长度为 5 的数组。当我们只添加少于 5 个元素时,我们的解决方案很有效。 例如,如果我们只调用入队函数四次后还想要将元素 10 入队,那么我们可以成功。

但是我们不能接受更多的入队请求,这是合理的,因为现在队列已经满了。但是如果我们将一个元素出队呢?
实际上,在这种情况下,我们应该能够再接受一个元素。
在这里插入图片描述
更有效的方法是使用循环队列。 具体来说,我们可以使用固定大小的数组和两个指针来指示起始位置和结束位置。 目的是重用我们之前提到的被浪费的存储

/-------------------------------------------分割线--------------------------------------/
队头统一叫: head.
队尾统一叫: tail.

一. 循环队列的实现

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

1. 判断队列是否为空的条件:

在这里插入图片描述

/* 检查循环队列是否为空 */
bool myCircularQueueIsEmpty(MyCircularQueue *obj) 
{
	return (obj->head == obj->tail);
}

手法1:检查循环队列是否为空,只看 head == tail. 如下特殊情况下,检查队列为空,也是看 head == tail.
在这里插入图片描述

2. 判断队列是否为满的条件:

在这里插入图片描述

/* 检查循环队列是否已满 */
bool myCircularQueueIsFull(MyCircularQueue *obj) 
{
	return ((obj->tail + 1) % obj->MaxSize == obj->head);
}

通常判断队列是否满,直接判断
(obj->tail + 1) == obj->head 即可。

手法2:但是循环队列,队列一直进,最后head为0,tail+1指向队列最后一个元素时;
obj->tail + 1== obj->MaxSize. 此时也是队列满.
所以需要以下条件来判断:
((obj->tail + 1) % obj->MaxSize == obj->head);

3. 进队列的条件:

在这里插入图片描述
进队列,队尾动,队尾插入:
obj->base[obj->tail] = value;
如果tail到了队尾要处理下,循环队列到队首.
obj->tail = (obj->tail + 1) % obj->MaxSize;
在这里插入图片描述

/* 在循环队列中插入元素, 如果操作成功, 则返回true */
bool myCircularQueueEnQueue(MyCircularQueue *obj, int value) {
	// 插入之前首先判断队列是否满
	if (myCircularQueueIsFull(obj)) {
		return false;
	}
	obj->base[obj->tail] = value;
	obj->tail = (obj->tail + 1) % obj->MaxSize;

	return true;
}

4. 出队列的条件:

队列为空,直接返回0;
队列非空,直接移动 Head++, 队尾进,队头出.
obj->head = (obj->head + 1) % obj->MaxSize;
在这里插入图片描述

/* 从循环队列中删除元素。如果操作成功,则返回true */
bool myCircularQueueDeQueue(MyCircularQueue *obj) {
	// 删除之前先判断队列是否为空
	if (myCircularQueueIsEmpty(obj)) {
		return false;
	}
	obj->head = (obj->head + 1) % obj->MaxSize;

	return true;
}

5. 获取队首尾元素:

手法1:队尾元素为 obj->base[obj->tail - 1]. 但有个特殊情况:tail为0时,tail-1小于0. tail为0时,其队尾元素为队列的最后一个元素 obj->base[obj->Maxsize-1].

/*从队列中获取 Front 项。*/
int myCircularQueueFront(MyCircularQueue *obj) 
{
	if (myCircularQueueIsEmpty(obj)) {
		return -1;
	}

	return obj->base[obj->head];
}

/*从队列中获取最后一项。*/
int myCircularQueueTail(MyCircularQueue *obj) 
{
	if (myCircularQueueIsEmpty(obj)) {
		return -1;
	}
	int i = (obj->tail - 1 + obj->MaxSize) % obj->MaxSize; // obj->tail -1 可能为0

	return obj->base[i];
}

6. 释放队列:

void myCircularQueueFree(MyCircularQueue *obj) {
	if (obj->base) {
		free(obj->base);
	}
	obj->base = NULL;     // 先释放指针,并赋值NULL
	obj->front = 0;
	obj->rear = 0;
	free(obj);
}

循环队列实现完整代码

typedef struct {
	int *base;     // 开始地址
	int head;
	int tail;
	int MaxSize;
} MyCircularQueue;
bool myCircularQueueIsEmpty(MyCircularQueue *obj);
bool myCircularQueueIsFull(MyCircularQueue *obj);
/** Initialize your data structure here. Set the size of the queue to be k. */
MyCircularQueue* myCircularQueueCreate(int k) {
	MyCircularQueue *obj = (MyCircularQueue *)malloc(sizeof(MyCircularQueue));

	obj->base = (int *)malloc((k + 1) * sizeof(int));
	if (obj->base == NULL) {
		return false;
	}
	obj->head = 0;
	obj->tail = 0;
	obj->MaxSize = k + 1;                        // 需要维护一个 size+1 的队列.

	return obj;
}

/** Insert an element into the circular queue. Return true if the operation is successful. 插入元素,成功返回true*/
bool myCircularQueueEnQueue(MyCircularQueue *obj, int value) {
	// 插入之前首先判断队列是否满
	if (myCircularQueueIsFull(obj)) {
		return false;
	}
	obj->base[obj->tail] = value;                // 先入队
	obj->tail = (obj->tail + 1) % obj->MaxSize;  // tail再移动,队尾元素为tail-1.

	return true;
}

/* Delete an element from the circular queue. Return true if the operation is successful. */
bool myCircularQueueDeQueue(MyCircularQueue *obj) {
	// 删除之前先判断队列是否为空
	if (myCircularQueueIsEmpty(obj)) {
		return false;
	}
	obj->head = (obj->head + 1) % obj->MaxSize;

	return true;
}

/* Get the front item from the queue. */
int myCircularQueueFront(MyCircularQueue *obj)
{
	if (myCircularQueueIsEmpty(obj)) {
		return -1;
	}

	return obj->base[obj->head];
}

/* Get the last item from the queue. */
int myCircularQueueTail(MyCircularQueue *obj)
{
	if (myCircularQueueIsEmpty(obj)) {
		return -1;
	}
	int i = (obj->tail - 1 + obj->MaxSize) % obj->MaxSize; // 队尾元素为 obj->base[obj->tail - 1].
														   // 但有个特殊情况:tail为0时,tail-1小于0.
														   // tail为0时,其队尾元素为队列的最后一个元素 obj->base[obj->Maxsize-1].
	return obj->base[i];
}
/* Checks whether the circular queue is empty or not. */
bool myCircularQueueIsEmpty(MyCircularQueue *obj)
{
	return (obj->head == obj->tail);
}
/* Checks whether the circular queue is full or not. */
bool myCircularQueueIsFull(MyCircularQueue *obj)
{
	return ((obj->tail + 1) % obj->MaxSize == obj->head);
}
void myCircularQueueFree(MyCircularQueue *obj) {
	if (obj->base) {
		free(obj->base);
	}
	obj->base = NULL;     // 先释放指针,并赋值NULL
	obj->head = 0;
	obj->tail = 0;
	free(obj);
}

int main(void)
{
	MyCircularQueue *obj = myCircularQueueCreate(3);
	int val = 1;
	int index = 0;
	myCircularQueueEnQueue(obj, 1);
	myCircularQueueEnQueue(obj, 2);
	myCircularQueueEnQueue(obj, 3);
	myCircularQueueEnQueue(obj, 4);
	val = myCircularQueueRear(obj);
	myCircularQueueDeQueue(obj);
	myCircularQueueEnQueue(obj, 4);
	myCircularQueueDeQueue(obj);
	myCircularQueueEnQueue(obj, 24);
	myCircularQueueEnQueue(obj, 25);
	val = myCircularQueueRear(obj);
	myCircularQueueDeQueue(obj);
	myCircularQueueDeQueue(obj);
	myCircularQueueDeQueue(obj);
	return 0;
}

二. 队列的实现模板

#define MAX_NUN     1024
typedef struct {
	int head;
	int tail;
	int size; // 计算队列元素个数
	int data[MAX_NUN];
} Queue_t;
Queue_t *QueueCreate(int size)
{
	Queue_t *queue = (Queue_t *)malloc(sizeof(Queue_t));
	queue->tail = 0;
	queue->head = 0;
	queue->size = size;
	memset(queue->data, 0, sizeof(int) * (MAX_NUN));
	return queue;
}
bool QueuePush(Queue_t *queue, int value)
{
	if (QueueIsFull(queue)) {
		return false;
	}
	queue->data[queue->tail] = value;  // 先入队
	queue->tail = (queue->tail + 1) % queue->size; // tail再移动,队尾元素为tail-1.
	return true;
}
bool QueuePop(Queue_t *queue, int *value)
{
	if (QueueIsEmpty(queue)) {
		return false;
	}
	*value = queue->data[queue->head];
	queue->head = (queue->head + 1) % queue->size;
	return true;
}
bool QueueIsEmpty(Queue_t *queue)
{
	return (queue->head == queue->tail);
}
bool QueueIsFull(Queue_t *queue)
{
	return (((queue->tail + 1) % queue->size) == queue->head);
}
int QueueFront(Queue_t *queue)
{
	if (QueueIsEmpty(queue)) {
		return -1;
	}
	return queue->data[queue->head];
}
int QueueTail(Queue_t *queue)
{
	if (QueueIsEmpty(queue)) {
		return -1;
	}
	return queue->data[(queue->tail - 1 + queue->size) % queue->size];
}
void QueueFree(Queue_t *queue)
{
	free(queue);
	queue = NULL;
}

225. 用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
注意:
你只能使用队列的标准操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 这些操作。
你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
[“MyStack”, “push”, “push”, “top”, “pop”, “empty”]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
提示:
1 <= x <= 9
最多调用100 次 push、pop、top 和 empty
每次调用 pop 和 top 都保证栈不为空

#define MAX_NUN     1024
typedef struct {
	int head;
	int tail;
	int size; // 计算队列元素个数
	int data[MAX_NUN];
} Queue_t;
Queue_t *QueueCreate(int size)
{
	Queue_t *queue = (Queue_t *)malloc(sizeof(Queue_t));
	queue->tail = 0;
	queue->head = 0;
	queue->size = size;
	memset(queue->data, 0, sizeof(int) * (MAX_NUN));
	return queue;
}
bool QueuePush(Queue_t *queue, int value)
{
	if (QueueIsFull(queue)) {
		return false;
	}
	queue->data[queue->tail] = value;  // 先入队
	queue->tail = (queue->tail + 1) % queue->size; // tail再移动,队尾元素为tail-1.
	return true;
}
bool QueuePop(Queue_t *queue, int *value)
{
	if (QueueIsEmpty(queue)) {
		return false;
	}
	*value = queue->data[queue->head];
	queue->head = (queue->head + 1) % queue->size;
	return true;
}
bool QueueIsEmpty(Queue_t *queue)
{
	return (queue->head == queue->tail);
}
bool QueueIsFull(Queue_t *queue)
{
	return (((queue->tail + 1) % queue->size) == queue->head);
}
int QueueFront(Queue_t *queue)
{
	if (QueueIsEmpty(queue)) {
		return -1;
	}
	return queue->data[queue->head];
}
int QueueTail(Queue_t *queue)
{
	if (QueueIsEmpty(queue)) {
		return -1;
	}
	return queue->data[(queue->tail - 1 + queue->size) % queue->size];
}
void QueueFree(Queue_t *queue)
{
	free(queue);
	queue = NULL;
}

/*---------------------两个队列实现栈---------------------*/
typedef struct {
	Queue_t *queueA;
	Queue_t *queueB;
} MyStack;

// Initialize your data structure here.
MyStack *myStackCreate() {
	MyStack *stack = (MyStack *)malloc(sizeof(MyStack));
	stack->queueA = QueueCreate(20);
	stack->queueB = QueueCreate(20);
	return stack;
}
// Returns whether the stack is empty.
bool myStackEmpty(MyStack *queue) {
	return (QueueIsEmpty(queue->queueA) && QueueIsEmpty(queue->queueB));
}
// Returns whether the stack is empty.
bool myStackFull(MyStack *queue) {
	return (QueueIsFull(queue->queueA) || QueueIsFull(queue->queueB));
}
// Push element x onto stack. 
void myStackPush(MyStack *queue, int x)
{
	if (myStackFull(queue))
		return;
	Queue_t *enQueue = NULL;
	if (!QueueIsEmpty(queue->queueA)) {
		enQueue = queue->queueA;
	} else {
		enQueue = queue->queueB;
	}
	QueuePush(enQueue, x);
}
// Removes the element on top of the stack and returns that element. 
int myStackPop(MyStack *queue)
{
	if (myStackEmpty(queue)) {
		return -1;
	}
	int data;
	Queue_t *enQueue = NULL;
	Queue_t *deQueue = NULL;
	Queue_t *tmpQueue = NULL;
	if (!QueueIsEmpty(queue->queueA)) {
		deQueue = queue->queueA;  // queueA A队列出数据
		enQueue = queue->queueB;  // queueB B队列收数据
	} else {
		deQueue = queue->queueB;
		enQueue = queue->queueA;
	}
	while (deQueue->tail != deQueue->head) {
		printf("\n");
		QueuePop(deQueue, &data);   // 队列1出数据,队列2收数据
		if (!QueueIsEmpty(deQueue)) { // 队列1出完最后一个元素,队列2不接收,直接作为最后一个输出,它就是栈的Top元素
			QueuePush(enQueue, data);
		}
	}
	printf("tail is %d, head is %d\n", deQueue->tail, deQueue->head);
	return data;
}
// Get the top element. 
int myStackTop(MyStack *queue)
{
	if (myStackEmpty(queue)) {
		return -1;
	}
	Queue_t *enQueue = NULL;
	Queue_t *deQueue = NULL;
	int data;
	if (!QueueIsEmpty(queue->queueA)) {
		deQueue = queue->queueA;
		enQueue = queue->queueB;
	} else {
		deQueue = queue->queueB;
		enQueue = queue->queueA;
	}
	while (deQueue->tail != deQueue->head) {
		QueuePop(deQueue, &data);
		QueuePush(enQueue, data);
	}
	return data;
}
void myStackFree(MyStack *queue) {
	QueueFree(queue->queueA);
	QueueFree(queue->queueB);
	free(queue);
}

379. 电话目录管理系统

设计一个电话目录管理系统,一开始有 maxNumbers 个位置能够储存号码。系统应该存储号码,检查某个位置是否为空,并清空给定的位置。
实现 PhoneDirectory 类:
PhoneDirectory(int maxNumbers) 电话目录初始有 maxNumbers 个可用位置。
int get() 提供一个未分配给任何人的号码。如果没有可用号码则返回 -1。
bool check(int number) 如果位置 number 可用返回 true 否则返回 false。
void release(int number) 回收或释放位置 number。

示例 1:
输入:
[“PhoneDirectory”, “get”, “get”, “check”, “get”, “check”, “release”, “check”]
[[3], [], [], [2], [], [2], [2], [2]]
输出:
[null, 0, 1, true, 2, false, null, true]
解释:
PhoneDirectory phoneDirectory = new PhoneDirectory(3);
phoneDirectory.get(); // 它可以返回任意可用的数字。这里我们假设它返回 0。
phoneDirectory.get(); // 假设它返回 1。
phoneDirectory.check(2); // 数字 2 可用,所以返回 true。
phoneDirectory.get(); // 返回剩下的唯一一个数字 2。
phoneDirectory.check(2); // 数字 2 不再可用,所以返回 false。
phoneDirectory.release(2); // 将数字 2 释放回号码池。
phoneDirectory.check(2); // 数字 2 重新可用,返回 true。
提示:
1 <= maxNumbers <= 10^4
0 <= number < maxNumbers
get,check 和 release 最多被调用 2 * 10^4 次。

手法1:用队列 Or 用数组解题?

这题想用队列来处理则陷入了麻烦
关键在这里:void release(int number) 回收或释放位置 number。

队列的操作并不合适从中间的位置来释放,需要额外处理;
本题是使用大数组,Hash的方法遍历解决;

#define MAX_NUN     10001
typedef struct {
	int cntNum;
	Queue_t queue;
} PhoneDirectory;
PhoneDirectory *phoneDirectoryCreate(int maxNumbers)
{
	PhoneDirectory *obj = (PhoneDirectory *)malloc(sizeof(PhoneDirectory));
	memset(obj, 0, sizeof(PhoneDirectory));
	obj->queue.size = maxNumbers;
	return obj;
}
int phoneDirectoryGet(PhoneDirectory *obj)
{
	for (int i = 0; i < obj->queue.size; i++) {
		if (obj->queue.data[i] == 0) {
			obj->queue.data[i] = 1;
			return i;
		}
	}
	return -1;
}
bool phoneDirectoryCheck(PhoneDirectory *obj, int number)
{
	if (obj->queue.data[number] == 0) {
		return true;
	} else {
		return false;
	}
}
void phoneDirectoryRelease(PhoneDirectory *obj, int number)
{
	obj->queue.data[number] = 0;
}
void phoneDirectoryFree(PhoneDirectory *obj)
{
	free(obj);
}

641. 设计双端队列

设计实现双端队列。
实现 MyCircularDeque 类:
MyCircularDeque(int k) :构造函数,双端队列最大为 k 。
boolean insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true ,否则返回 false 。
boolean insertLast() :将一个元素添加到双端队列尾部。如果操作成功返回 true ,否则返回 false 。
boolean deleteFront() :从双端队列头部删除一个元素。 如果操作成功返回 true ,否则返回 false 。
boolean deleteLast() :从双端队列尾部删除一个元素。如果操作成功返回 true ,否则返回 false 。
int getFront() ):从双端队列头部获得一个元素。如果双端队列为空,返回 -1 。
int getRear() :获得双端队列的最后一个元素。 如果双端队列为空,返回 -1 。
boolean isEmpty() :若双端队列为空,则返回 true ,否则返回 false 。
boolean isFull() :若双端队列满了,则返回 true ,否则返回 false 。

示例 1:
输入
[“MyCircularDeque”, “insertLast”, “insertLast”, “insertFront”, “insertFront”, “getRear”, “isFull”, “deleteLast”, “insertFront”, “getFront”]
[[3], [1], [2], [3], [4], [], [], [], [4], []]
输出
[null, true, true, true, false, 2, true, true, true, 4]
解释
MyCircularDeque circularDeque = new MycircularDeque(3); // 设置容量大小为3
circularDeque.insertLast(1); // 返回 true
circularDeque.insertLast(2); // 返回 true
circularDeque.insertFront(3); // 返回 true
circularDeque.insertFront(4); // 已经满了,返回 false
circularDeque.getRear(); // 返回 2
circularDeque.isFull(); // 返回 true
circularDeque.deleteLast(); // 返回 true
circularDeque.insertFront(4); // 返回 true
circularDeque.getFront(); // 返回 4
提示:
1 <= k <= 1000
0 <= value <= 1000
insertFront, insertLast, deleteFront, deleteLast, getFront, getRear, isEmpty, isFull 调用次数不大于 2000 次

手法1:设计双端队列

和普通队列的区别在于和head插入节点,其他都没有变


MyCircularDeque(int k):初始化队列,同时 base 数组的空间初始化大小为 k + 1。head, tail 全部初始化为 0。

insertFront(int value):队列未满时,在队首插入一个元素。我们首先将队首 head 移动一个位置,更新队首索引为 head 更新为 (head − 1 + capacity) mod capacity。

insertLast(int value):队列未满时,在队列的尾部插入一个元素,并同时将队尾的索引 tail 更新为 (tail + 1) mod capacity。


#define MAX_NUN     1024
typedef struct {
	int head;
	int tail;
	int size; // 计算队列元素个数
	int data[MAX_NUN];
} MyCircularDeque;

MyCircularDeque *myCircularDequeCreate(int k)
{
	MyCircularDeque *obj = (MyCircularDeque *)malloc(sizeof(MyCircularDeque));
	obj->size = k + 1;
	obj->tail = obj->head = 0;
	return obj;
}
bool myCircularDequeInsertFront(MyCircularDeque *obj, int value)
{
	if ((obj->tail + 1) % obj->size == obj->head) {
		return false;
	}
	// 头插入元素,head - 1 的位置插入
	obj->head = (obj->head - 1 + obj->size) % obj->size;
	obj->data[obj->head] = value;
	return true;
}
bool myCircularDequeInsertLast(MyCircularDeque *obj, int value)
{
	if ((obj->tail + 1) % obj->size == obj->head) {
		return false;
	}
	// 尾插入元素,tail + 1 的位置插入
	obj->data[obj->tail] = value;
	obj->tail = (obj->tail + 1) % obj->size;
	return true;
}
bool myCircularDequeDeleteFront(MyCircularDeque *obj)
{
	if (obj->tail == obj->head) { // isEmpty();
		return false;
	}
	obj->head = (obj->head + 1) % obj->size;
	return true;
}
bool myCircularDequeDeleteLast(MyCircularDeque *obj) {
	if (obj->tail == obj->head) { // isEmpty();
		return false;
	}
	obj->tail = (obj->tail - 1 + obj->size) % obj->size;
	return true;
}
int myCircularDequeGetFront(MyCircularDeque *obj) {
	if (obj->tail == obj->head) {
		return -1;
	}
	return obj->data[obj->head];
}
int myCircularDequeGetRear(MyCircularDeque *obj) {
	if (obj->tail == obj->head) {
		return -1;
	}
	return obj->data[(obj->tail - 1 + obj->size) % obj->size];
}
bool myCircularDequeIsEmpty(MyCircularDeque *obj) {
	return obj->tail == obj->head;
}
bool myCircularDequeIsFull(MyCircularDeque *obj) {
	return (obj->tail + 1) % obj->size == obj->head;
}
void myCircularDequeFree(MyCircularDeque *obj) {
	free(obj);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值