队列
参考资料,以下内容来自CSDN大佬:
https://blog.csdn.net/zhang21722668/article/details/82155301
https://blog.csdn.net/lin20044140410/article/details/79617397
... ...
定义
1. 只允许在一端进行插入数据操作,在另一端进行删除操作的特殊线性表;
2. 进行插入操作的一端称为队尾(入队列),进行删除操作的一端称为队头(出队列);
3. 队列遵循“先进先出”(FIFO)原则。
实现方式
顺序队列
数组实现,比如有一个n个元素的队列,数组下标0的一端是队头,入队操作就是通过数组下标一个个顺序追加,不需要移动元素,但是如果删除队头元素,后面的元素就要往前移动,对应的时间复杂度就是O(n),性能自然不高。 循环队列,两个指针,front指向队头,rear指向队尾元素的下一个位置,元素出队时front往后移动,如果到了队尾则转到头部,同理入队时rear后移,如果到了队尾则转到头部,这样通过下标front出队时,就不需要移动元素。
程序实现
#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 11 // 初始容量
typedef int Status;
typedef int QElemType; // 定义数据类型
// 循环队列的顺序存储结构
typedef struct {
QElemType data[MAXSIZE];
int front; //头指针
int rear; //尾指针,队列非空时,指向队尾元素的下一个位置
} SqQueue;
Status visit(QElemType item)
{
printf("%d", item);
return OK;
}
// 初始化空队列
Status InitQueue(SqQueue* sQ)
{
sQ->front = 0;
sQ->rear = 0;
return OK;
}
// 将队列清空
Status ClearQueue(SqQueue* Q)
{
Q->front = Q->rear = 0;
return OK;
}
// 判断队列是否为null
Status QueueEmpty(SqQueue Q)
{
if (Q.front == Q.rear) {
return TRUE;
} else {
return FALSE;
}
}
// 返回队列中的元素个数
int QueueLength(SqQueue Q)
{
return (Q.rear - Q.front + MAXSIZE) % MAXSIZE;
}
// 返回队头元素
Status GetHead(SqQueue Q, QElemType* e)
{
if (Q.front == Q.rear) { // 是否为空队列
return ERROR;
}
*e = Q.data[Q.front];
return OK;
}
// 在队尾插入元素
Status EnQueue(SqQueue* Q, QElemType e)
{
if ((Q->rear + 1) % MAXSIZE == Q->front) { // 队列已满
return ERROR;
}
Q->data[Q->rear] = e; // 插入队尾
Q->rear = (Q->rear + 1) % MAXSIZE; // 尾部指针后移,如果到最后则转到头部
return OK;
}
// 元素出队
Status DeQueue(SqQueue* Q, QElemType* e)
{
if (Q->front == Q->rear) { // 队列空
return ERROR;
}
*e = Q->data[Q->front]; // 返回队头元素
Q->front = (Q->front + 1) % MAXSIZE; // 队头指针后移,如到最后转到头部
return OK;
}
// 遍历队列元素
Status QueueTraverse(SqQueue Q)
{
int i = Q.front;
while ((i + Q.front) != Q.rear) {
visit(Q.data[i]);
i = (i + 1) % MAXSIZE;
}
printf("\n");
return OK;
}
int main()
{
QElemType d;
SqQueue Q;
InitQueue(&Q);
// 入队10个元素
for (int i = 0; i < MAXSIZE - 1; i++) {
EnQueue(&Q, i);
}
QueueTraverse(Q);
printf("依次出队:");
for (int j = 1; j < MAXSIZE; j++) {
DeQueue(&Q, &d);
printf("d = %d, ", d);
}
return 0;
}
链式队列
链式存储队列,即只能队尾进队头出的线性表单链表。且规定队头指针指向链队列的头节点,队尾指针指向终端节点,当队列为空时,front和rear都指向头节点。 入队操作,就是在链表尾部插入节点;出队操作就是头节点的后继节点出队,然后将头节点的后继后移。如果最后除了头节点外,只剩一个元素了,就把rear也指向头节点。
程序实现
#include "stdio.h"
#include "stdlib.h"
#include "io.h"
#include "math.h"
#include "time.h"
#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0
#define MAXSIZE 20
typedef int Status;
typedef int QElemType;
// 节点结构
typedef struct QNode {
QElemType data;
struct QNode* next;
} QNode, *QueuePtr;
// 队列的链表结构
typedef struct {
QueuePtr front; // 队头
QueuePtr rear; // 队尾
} LinkQueue;
Status visit(QElemType e)
{
printf("%d ", e);
return OK;
}
// 初始化空队列
Status InitQueue(LinkQueue* Q)
{
Q->front = Q->rear = (QueuePtr)malloc(sizeof(QNode));
if (!Q->front) {
exit(OVERFLOW);
}
Q->front->next = NULL;
return OK;
}
// 销毁队列
Status DestroyQueue(LinkQueue* Q)
{
while (Q->front) {
Q->rear = Q->front->next; // 从队头开始销毁
free(Q->front);
Q->front = Q->rear;
}
return OK;
}
// 清空队列,队头指针还在
Status ClearQueue(LinkQueue* Q)
{
QueuePtr p, q;
Q->rear = Q->front; // 跟初始状态相同,Q->rear指向头节点
p = Q->front->next; // 开始销毁队头元素,队头、队尾依然保留
Q->front->next = NULL;
while (p) {
q = p;
p = p->next;
free(q);
}
return OK;
}
// 队列是否为空
Status QueueEmpty(LinkQueue Q) {
if (Q.front == Q.rear) {
return TRUE;
} else {
return FALSE;
}
}
// 取队列长度
int QueueLength(LinkQueue Q)
{
int i = 0;
QueuePtr p = Q.front;
while (Q.rear != p) {
i++;
p = p->next;
}
return i;
}
// 获取队头元素
Status GetHead(LinkQueue Q, QElemType* e)
{
QueuePtr p;
if (Q.front == Q.rear) { // 队空
return ERROR;
}
p = Q.front->next;
*e = p->data;
return OK;
}
// 队尾插入元素
Status EnQueue(LinkQueue* Q, QElemType e)
{
QueuePtr s = (QueuePtr)malloc(sizeof(QNode));
if (!s) {
exit(OVERFLOW);
}
s->data = e;
s->next = NULL;
Q->rear->next = s; // 原来队的next指向新的元素
Q->rear = s; // 将新元素变为队尾
return OK;
}
// 队头元素出队
Status DeQueue(LinkQueue* Q, QElemType* e)
{
QueuePtr p;
if (Q->front == Q->rear) {
return ERROR;
}
p = Q->front->next; // p指向队头元素
*e = p->data;
Q->front->next = p->next; // 头节点的后继指向队头的下一个元素
if (Q->rear == p) { // 队头等于队尾
Q->rear = Q->front; // 队尾指向头节点
}
free(p);
return OK;
}
// 遍历元素
Status QueueTraverse(LinkQueue Q)
{
QueuePtr p;
p = Q.front->next;
while (p) {
visit(p->data);
p = p->next;
}
printf("\n");
return OK;
}
int main()
{
int i;
LinkQueue q;
i = InitQueue(&q);
// 入队10个元素
for (int index = 0; index < MAXSIZE; index++) {
EnQueue(&q, index);
}
QueueTraverse(q);
DestroyQueue(&q);
printf("队列已经销毁, q.front = %p q.rear = %p\n", q.front, q.rear);
return 0;
}
Python队列
以下内容来自CSDN大佬:
https://blog.csdn.net/brucewong0516/article/details/84025027
分类
python队列依赖模块queue,主要有四种:
队列方式 | 特点 |
---|---|
queue.Queue | 先进先出队列 |
queue.LifoQueue | 后进先出队列 |
queue.PriorityQueue | 优先级队列 |
queue.deque | 双线队列 |
方法
方法 | 用法说明 |
---|---|
put | 放数据,Queue.put( )默认有block=True和timeout两个参数。当block=True时,写入是阻塞式的,阻塞时间由timeout确定。当队列q被(其他线程)写满后,这段代码就会阻塞,直至其他线程取走数据。Queue.put()方法加上 block=False 的参数,即可解决这个隐蔽的问题。但要注意,非阻塞方式写队列,当队列满时会抛出 exception Queue.Full 的异常 |
get | 取数据(默认阻塞),Queue.get([block[, timeout]])获取队列,timeout等待时间 |
empty | 如果队列为空,返回True,反之False |
qsize | 显示队列中真实存在的元素长度 |
maxsize | 最大支持的队列长度,使用时无括号 |
join | 实际上意味着等到队列为空,再执行别的操作 |
task_done | 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号 |
full | 如果队列满了,返回True,反之False |
程序实现
# !/usr/bin/env python
# coding: utf-8
import queue
# 先进先出队列
def fifo():
q = queue.Queue(5) # 如果不设置长度,默认为无限长
print(q.maxsize) # 注意没有括号
q.put(123)
q.put(456)
q.put(789)
q.put(100)
q.put(200)
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
# 后进先出队列
def lifo():
q = queue.LifoQueue()
q.put(12)
q.put(34)
print(q.get())
print(q.get())
# 优先级队列
def priority():
"""
优先级队列put的是一个元组
(优先级, 数据)
优先级越小级别越高
"""
q = queue.PriorityQueue()
q.put((3, 'aaaaa'))
q.put((3, 'bbbbb'))
q.put((1, 'ccccc'))
q.put((3, 'ddddd'))
print(q.get())
print(q.get())
# 双边队列
def double():
from collections import deque
dq = deque(['a', 'b'])
dq.append('c')
print(dq)
print(dq.pop())
print(dq)
print(dq.popleft())
print(dq)
dq.appendleft('d')
print(dq)
print(len(dq))
if __name__ == '__main__':
print('-' * 18, '先进先出队列', '-' * 18)
fifo()
print('-' * 18, '后进先出队列', '-' * 18)
lifo()
print('-' * 18, '优先级队列', '-' * 18)
priority()
print('-' * 18, '双边队列', '-' * 18)
double()
LeetCode题目
621.任务调度器
https://leetcode-cn.com/problems/task-scheduler/
给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的 最短时间 。
解题思路
必须要待命:实际完成任务时间 = len(tasks) + 待命 > len(tasks) 不需要待命:实际完成任务时间 = len(tasks)
作者:MingZhang
Python3方法
# !/usr/bin/env python
# coding: utf-8
from collections import Counter
from typing import List
import unittest
class Solution:
def least_interval(self, tasks: List[str], n: int) -> int:
list_dict_count = list(Counter(tasks).values())
max_count = max(list_dict_count)
task_max = list_dict_count.count(max_count)
return max((max_count - 1) * (n + 1) + task_max, len(tasks))
class TestCase(unittest.TestCase):
def test_01(self):
tasks = ["A", "A", "A", "B", "B", "B"]
n = 2
expected_results = 8
self.result = Solution()
res = self.result.least_interval(tasks, n)
self.assertEqual(res, expected_results)
def test_02(self):
tasks = ["A", "A", "A", "B", "B", "B"]
n = 0
expected_results = 6
self.result = Solution()
res = self.result.least_interval(tasks, n)
self.assertEqual(res, expected_results)
def test_03(self):
tasks = ["A", "A", "A", "A", "A", "A", "B", "C", "D", "E", "F", "G"]
n = 2
expected_results = 16
self.result = Solution()
res = self.result.least_interval(tasks, n)
self.assertEqual(res, expected_results)
if __name__ == '__main__':
unittest.main()
C++方法
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
class Solution {
public:
int LeastInterval(vector<char>& tasks, int n) {
int len = tasks.size();
vector<int> vec(26);
for (char c : tasks) {
++vec[c - 'A'];
}
sort(vec.begin(), vec.end(), [](int& x, int& y) { return x > y; });
int cnt = 1;
while (cnt < (int)vec.size() && vec[cnt] == vec[0]) {
cnt++;
}
return max(len, cnt + (n + 1) * (vec[0] - 1));
}
};
int main()
{
Solution res;
vector<char> task = { 'A', 'A', 'A', 'B', 'B', 'B' };
int num = 2;
int result;
result = res.LeastInterval(task, num);
cout << result << endl;
}
622.设计循环队列
https://leetcode-cn.com/problems/design-circular-queue/
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
你的实现应该支持如下操作:
MyCircularQueue(k): 构造器,设置队列长度为 k 。 Front: 从队首获取元素。如果队列为空,返回 -1 。 Rear: 获取队尾元素。如果队列为空,返回 -1 。 enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。 deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。 isEmpty(): 检查循环队列是否为空。 isFull(): 检查循环队列是否已满。
解题思路
数组 + 双指针
作者:Alice_Time
Python3方法
# !/usr/bin/env python
# coding: utf-8
import unittest
class MyCircularQueue:
def __init__(self, k: int):
self.queue = [0] * k
self.length = k
self.head = self.cnt = 0
def en_queue(self, value: int) -> bool:
if self.is_full():
return False
self.queue[(self.head + self.cnt) % self.length] = value
self.cnt += 1
return True
def de_queue(self) -> bool:
if self.is_empty():
return False
self.head += 1
self.cnt -= 1
if self.cnt == 0:
self.head = 0
return True
def front(self) -> int:
if self.is_empty():
return -1
return self.queue[self.head % self.length]
def rear(self) -> int:
if self.is_empty():
return -1
return self.queue[(self.head + self.cnt) % self.length - 1]
def is_empty(self) -> bool:
return self.cnt == 0
def is_full(self) -> bool:
return self.cnt == self.length
class TestCase(unittest.TestCase):
def test_01(self):
res = MyCircularQueue(3)
self.assertTrue(res.en_queue(1))
self.assertTrue(res.en_queue(2))
self.assertTrue(res.en_queue(3))
self.assertFalse(res.en_queue(4))
self.assertEqual(3, res.rear())
self.assertTrue(res.is_full())
self.assertTrue(res.de_queue())
self.assertTrue(res.en_queue(4))
self.assertEqual(4, res.rear())
if __name__ == '__main__':
unittest.main()
# Your MyCircularQueue object will be instantiated and called as such:
# obj = MyCircularQueue(k)
# param_1 = obj.enQueue(value)
# param_2 = obj.deQueue()
# param_3 = obj.Front()
# param_4 = obj.Rear()
# param_5 = obj.isEmpty()
# param_6 = obj.isFull()
C++方法
#include <iostream>
#include <vector>
using namespace std;
class MyCircularQueue {
public:
int queueLen;
vector<int> circularQueue;
MyCircularQueue(int k) {
queueLen = k;
circularQueue.clear();
}
bool enQueue(int value) {
if (isFull()) {
return false;
}
else {
circularQueue.push_back(value);
return true;
}
}
bool deQueue() {
if (isEmpty()) {
return false;
}
else {
circularQueue.erase(circularQueue.begin());
return true;
}
}
int Front() {
if (isEmpty()) {
return -1;
}
else {
return circularQueue[0];
}
}
int Rear() {
if (isEmpty()) {
return -1;
}
else {
return circularQueue[circularQueue.size() - 1];
}
}
bool isEmpty() {
if (circularQueue.size() == 0) {
return true;
}
else {
return false;
}
}
bool isFull() {
if (circularQueue.size() == queueLen) {
return true;
}
else {
return false;
}
}
};
int main()
{
MyCircularQueue* obj = new MyCircularQueue(3); // 设置长度为 3
cout << obj->enQueue(1) << endl; // 返回 true
cout << obj->enQueue(2) << endl; // 返回 true
cout << obj->enQueue(3) << endl; // 返回 true
cout << obj->enQueue(4) << endl; // 返回 false,队列已满
cout << obj->Rear() << endl; // 返回 3
cout << obj->isFull() << endl; // 返回 true
cout << obj->deQueue() << endl; // 返回 true
cout << obj->enQueue(4) << endl; // 返回 true
cout << obj->Rear() << endl; // 返回 4
}
641.设计循环双端队列
https://leetcode-cn.com/problems/design-circular-deque/
设计实现双端队列。 你的实现需要支持以下操作:
MyCircularDeque(k):构造函数,双端队列的大小为k。 insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。 insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。 deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。 deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。 getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。 getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。 isEmpty():检查双端队列是否为空。 isFull():检查双端队列是否满了。
解题思路
数组 + 双指针
作者:Alice_Time
Python3方法
# !/usr/bin/env python
# coding: utf-8
import unittest
class MyCircularDeque:
def __init__(self, k: int):
"""
Initialize your data structure here. Set the size of the deque to be k.
"""
self.deque = [0] * k
self.k = k
self.start = self.end = 0
self.count = 0
def insert_front(self, value: int) -> bool:
"""
Adds an item at the front of Deque. Return true if the operation is successful.
"""
if self.count == self.k:
return False
self.start = (self.start - 1) % self.k
self.deque[self.start] = value
self.count += 1
return True
def insert_last(self, value: int) -> bool:
"""
Adds an item at the rear of Deque. Return true if the operation is successful.
"""
if self.count == self.k:
return False
self.deque[self.end] = value
self.end = (self.end + 1) % self.k
self.count += 1
return True
def delete_front(self) -> bool:
"""
Deletes an item from the front of Deque. Return true if the operation is successful.
"""
if self.count == 0:
return False
self.start = (self.start + 1) % self.k
self.count -= 1
return True
def delete_last(self) -> bool:
"""
Deletes an item from the rear of Deque. Return true if the operation is successful.
"""
if self.count == 0:
return False
self.end = (self.end - 1) % self.k
self.count -= 1
return True
def get_front(self) -> int:
"""
Get the front item from the deque.
"""
if self.count == 0:
return -1
return self.deque[self.start]
def get_rear(self) -> int:
"""
Get the last item from the deque.
"""
if self.count == 0:
return -1
return self.deque[self.end - 1]
def is_empty(self) -> bool:
"""
Checks whether the circular deque is empty or not.
"""
return not self.count
def is_full(self) -> bool:
"""
Checks whether the circular deque is full or not.
"""
return self.count == self.k
class TestCase(unittest.TestCase):
def test_01(self):
res = MyCircularDeque(3)
self.assertTrue(res.insert_last(1))
self.assertTrue(res.insert_last(2))
self.assertTrue(res.insert_front(3))
self.assertFalse(res.insert_front(4))
self.assertEqual(2, res.get_rear())
self.assertTrue(res.is_full())
self.assertTrue(res.delete_last())
self.assertTrue(res.insert_front(4))
self.assertEqual(4, res.get_front())
if __name__ == '__main__':
unittest.main()
# Your MyCircularDeque object will be instantiated and called as such:
# obj = MyCircularDeque(k)
# param_1 = obj.insertFront(value)
# param_2 = obj.insertLast(value)
# param_3 = obj.deleteFront()
# param_4 = obj.deleteLast()
# param_5 = obj.getFront()
# param_6 = obj.getRear()
# param_7 = obj.isEmpty()
# param_8 = obj.isFull()
C++方法
#include <iostream>
#include <vector>
using namespace std;
class MyCircularDeque {
private:
vector<int> q;
public:
int K;
/** Initialize your data structure here. Set the size of the deque to be k. */
MyCircularDeque(int k) {
q.reserve(k);
K = k;
}
/** Adds an item at the front of Deque. Return true if the operation is successful. */
bool insertFront(int value) {
if (!isFull()) {
q.insert(q.begin(), value);
return true;
}
return false;
}
/** Adds an item at the rear of Deque. Return true if the operation is successful. */
bool insertLast(int value) {
if (!isFull()) {
q.push_back(value);
return true;
}
return false;
}
/** Deletes an item from the front of Deque. Return true if the operation is successful. */
bool deleteFront() {
if (!isEmpty()) {
q.erase(q.begin());
return true;
}
return false;
}
/** Deletes an item from the rear of Deque. Return true if the operation is successful. */
bool deleteLast() {
if (!isEmpty()) {
q.pop_back();
return true;
}
return false;
}
/** Get the front item from the deque. */
int getFront() {
if (isEmpty())return -1;
return q.front();
}
/** Get the last item from the deque. */
int getRear() {
if (isEmpty())return -1;
return q.back();
}
/** Checks whether the circular deque is empty or not. */
bool isEmpty() {
return q.size() == 0;
}
/** Checks whether the circular deque is full or not. */
bool isFull() {
return q.size() >= K;
}
};
int main()
{
MyCircularDeque* obj = new MyCircularDeque(3); // 设置长度为 3
cout << obj->insertLast(1) << endl; // 返回 true
cout << obj->insertLast(2) << endl; // 返回 true
cout << obj->insertFront(3) << endl; // 返回 true
cout << obj->insertFront(4) << endl; // 返回 false,队列已满
cout << obj->getRear() << endl; // 返回 3
cout << obj->isFull() << endl; // 返回 true
cout << obj->deleteLast() << endl; // 返回 true
cout << obj->insertFront(4) << endl; // 返回 true
cout << obj->getFront() << endl; // 返回 4
}
总结
九月的算法训练,题目实则当月已经完成了,总结延后了大半个月。原因呢,上个月工作和生活均发生了一些事情,并非借口,是实实在在会打乱计划的事情。也不多说,赶紧补齐九月,并保证十月算法训练不会延期。
本次三道题为训练要求中“队列”的题目,首先整理了队列的知识点,分别从C++和Python两个方面单独展示代码实现方式。同样,从三道leetcode题目练手,基本都是大佬高分答案,本地编译调试通过。