深入解析栈与队列:从基础操作到实际应用

97 阅读4分钟

栈与队列——这两种基础却强大的数据结构,是几乎每个程序员在编写代码时都会碰到的。它们不仅能帮助你解决各种问题,还能在实际应用中大大提高效率。不论你是刚入门编程的新手,还是正在寻求优化的开发者,这篇文章都将为你揭开栈与队列的神秘面纱,带你深入理解它们的工作原理和应用场景。

一、栈:先进后出的“魔法盒子”

栈是一种遵循“先进后出”原则(LIFO,Last In, First Out)的数据结构。可以将其想象成一个盛放书本的盒子。每次添加新书时,它都会放在最上面;而每次取书时,也只能取到最上面的那本书。这种结构适用于需要反向处理数据的场景,比如撤销操作、递归调用等。

栈的基本操作:

  • push(x) :将元素x推入栈中。
  • pop() :从栈中移除并返回最上面的元素。

栈的实现

我们可以通过数组来实现栈。以下是一个简单的栈操作示例:

const stack = []

// 压入元素
stack.push('a')
stack.push('b')
stack.push('c')

// 弹出元素
console.log(stack.pop()) // 输出 'c'
console.log(stack.pop()) // 输出 'b'

在这个例子中,栈遵循了“先进后出”的原则,最后加入的元素('c')最先被弹出。 stack-4.gif

栈的应用

栈广泛应用于以下几个场景:

  • 递归调用:函数的调用栈就是使用栈来实现的,每次函数调用都会被压入栈中,返回时则从栈中弹出。
  • 括号匹配:检查表达式中的括号是否正确匹配,可以利用栈来实现。
  • 撤销操作:在文本编辑器中,栈可以用于实现撤销功能。每次用户执行操作,都会将当前状态压入栈中,撤销时则弹出栈顶元素。

二、队列:先进先出的“传送带”

与栈不同,队列遵循“先进先出”原则。可以把队列比作一个排队等候的队伍,第一个排队的人会最先被服务,而后面的人则需要等待。

队列的基本操作

队列通常包含两种基本操作:

  • push(x) :将元素x添加到队列的末尾。
  • shift() :从队列的前端移除并返回最先进入队列的元素。

队列的实现

在JavaScript中,我们可以使用数组来模拟队列。以下是一个简单的队列操作示例:

const queue = []

// 入队
queue.push('1a')
queue.push('2b')
queue.push('3c')

// 出队
console.log(queue.shift()) // 输出 '1a'
console.log(queue.shift()) // 输出 '2b'

在这个例子中,队列遵循了“先进先出”的原则,第一个入队的元素('1a')最先被出队。

image.png

队列的应用

队列在计算机科学中有许多应用场景,最常见的包括:

  • 任务调度:操作系统中的进程调度往往使用队列来保证任务按顺序执行。
  • 宽度优先搜索:在图的遍历算法中,广度优先搜索(BFS)常用队列来存储待遍历的节点。
  • 缓存系统:比如消息队列,在分布式系统中,消息会按顺序进入队列,消费者按顺序处理。

三、栈与队列的进阶实现

用栈实现队列:

在某些情况下,我们需要将栈的特性与队列的特性结合起来。这时,我们可以使用两个栈来实现一个队列。

具体思路是:当队列为空时,将栈1中的元素全部转移到栈2中,这样栈2的顶部就成了队列的头部。

以下是用两个栈实现队列的代码:

var MyQueue = function() {
    this.stack1 = []
    this.stack2 = []
};

MyQueue.prototype.push = function(x) {
    this.stack1.push(x)
};

MyQueue.prototype.pop = function() {
    if (this.stack2.length === 0) {
        while (this.stack1.length > 0) {
            this.stack2.push(this.stack1.pop())
        }
    }
    return this.stack2.pop()
};

MyQueue.prototype.peek = function() {
    if (this.stack2.length === 0) {
        while (this.stack1.length > 0) {
            this.stack2.push(this.stack1.pop())
        }
    }
    return this.stack2[this.stack2.length - 1]
};

MyQueue.prototype.empty = function() {
    return this.stack1.length === 0 && this.stack2.length === 0
};

用队列实现滑动窗口最大值:LeetCode 239

这个问题考察了如何在队列中维护当前窗口的最大值。

var maxSlidingWindow = function (nums, k) {
    const result = []
    const deque = []
    for (let i = 0; i < nums.length; i++) {
        // 移除过期元素
        if (deque.length > 0 && deque[0] < i - k + 1) {
            deque.shift()
        }
        // 移除队列中小于当前元素的所有元素
        while (deque.length > 0 && nums[deque[deque.length - 1]] < nums[i]) {
            deque.pop()
        }
        deque.push(i)
        // 将当前窗口的最大值添加到结果中
        if (i >= k - 1) {
            result.push(nums[deque[0]])
        }
    }
    return result
};

最后

无论是实现撤销功能、管理任务队列,还是进行图算法遍历,栈和队列都在我们的程序中扮演着重要角色。理解并熟练掌握它们的应用,将使你在解决编程问题时更加得心应手。如果你遇到任何问题或有不同的见解,欢迎在评论区留言与大家分享!