Skip to content

栈与队列

1. 栈

栈(stack)是一种遵循先入后出逻辑的线性数据结构。如图所示,我们把堆叠元素的顶部称为"栈顶",底部称为"栈底"。将把元素添加到栈顶的操作叫作"入栈",删除栈顶元素的操作叫作"出栈"。 Alt text

1.1 栈的常用操作

栈的常用操作如表所示,具体的方法名需要根据所使用的编程语言来确定。在此,我们以常见的push()pop()peek()命名为例:

方法描述时间复杂度
push()元素入栈(添加至栈顶)O(1)
pop()栈顶元素出栈O(1)
peek()访问栈顶元素O(1)

通常情况下,我们可以直接使用编程语言内置的栈类:

java
/* 初始化栈 */
Stack<Integer> stack = new Stack<>();

/* 元素入栈 */
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(5);
stack.push(4);

/* 访问栈顶元素 */
int peek = stack.peek();

/* 元素出栈 */
int pop = stack.pop();

/* 获取栈的长度 */
int size = stack.size();

/* 判断是否为空 */
boolean isEmpty = stack.isEmpty();

1.2 栈的典型应用

  1. 浏览器中的后退与前进、软件中的撤销与反撤销。每当我们打开新的网页,浏览器就会对上一个网页执行入栈,这样我们就可以通过后退操作回到上一个网页。后退操作实际上是在执行出栈。如果要同时支持后退和前进,那么需要两个栈来配合实现。
  2. 程序内存管理。每次调用函数时,系统都会在栈顶添加一个栈帧,用于记录函数的上下文信息。在递归函数中,向下递推阶段会不断执行入栈操作,而向上回溯阶段则会不断执行出栈操作。

2. 队列

队列(queue)是一种遵循先入先出规则的线性数据结构。如图所示,我们将队列头部称为"队首",尾部称为"队尾",将把元素加入队尾的操作称为"入队",删除队首元素的操作称为"出队"。 Alt text

2.1 队列常用操作

队列的常见操作如表所示:

方法名描述时间复杂度
push()元素入队,即将元素添加至队尾O(1)
pop()队首元素出队O(1)
peek()访问队首元素O(1)

我们可以直接使用编程语言中现成的队列类:

java
/* 初始化队列 */
Queue<Integer> queue = new LinkedList<>();

/* 元素入队 */
queue.offer(1);
queue.offer(3);
queue.offer(2);
queue.offer(5);
queue.offer(4);

/* 访问队首元素 */
int peek = queue.peek();

/* 元素出队 */
int pop = queue.poll();

/* 获取队列的长度 */
int size = queue.size();

/* 判断队列是否为空 */
boolean isEmpty = queue.isEmpty();

2.2 队列典型应用

  • 淘宝订单。购物者下单后,订单将加入队列中,系统随后会根据顺序处理队列中的订单。在双十一期间,短时间内会产生海量订单,高并发成为工程师们需要重点攻克的问题。
  • 各类待办事项。任何需要实现"先来后到"功能的场景,例如打印机的任务队列、餐厅的出餐队列等,队列在这些场景中可以有效地维护处理顺序。

3. 双向队列

在队列中,我们仅能删除头部元素或在尾部添加元素。如图所示,双向队列(double-ended queue)提供了更高的灵活性,允许在头部和尾部执行元素的添加或删除操作。 Alt text

3.1 双向队列常用操作

双向队列的常用操作如表所示,具体的方法名称需要根据所使用的编程语言来确定:

方法名描述时间复杂度
push_first()将元素添加至队首O(1)
push_last()将元素添加至队尾O(1)
pop_first()删除队首元素O(1)
pop_last()删除队尾元素O(1)
peek_first()访问队首元素O(1)
peek_last()访问队尾元素O(1)

同样地,我们可以直接使用编程语言中已实现的双向队列类:

java
/* 初始化双向队列 */
Deque<Integer> deque = new LinkedList<>();

/* 元素入队 */
deque.offerLast(2);   // 添加至队尾
deque.offerLast(5);
deque.offerLast(4);
deque.offerFirst(3);  // 添加至队首
deque.offerFirst(1);

/* 访问元素 */
int peekFirst = deque.peekFirst();  // 队首元素
int peekLast = deque.peekLast();    // 队尾元素

/* 元素出队 */
int popFirst = deque.pollFirst();  // 队首元素出队
int popLast = deque.pollLast();    // 队尾元素出队

/* 获取双向队列的长度 */
int size = deque.size();

/* 判断双向队列是否为空 */
boolean isEmpty = deque.isEmpty();

3.2 双向队列应用

双向队列兼具栈与队列的逻辑,因此它可以实现这两者的所有应用场景,同时提供更高的自由度。