使用JavaScript的数据结构:堆栈和队列

最终产品图片
您将要创造的

Web开发中最常用的两个数据结构是堆栈和队列。 Internet的许多用户,包括Web开发人员,都没有意识到这一惊人的事实。 如果您是这些开发人员之一,那么请准备两个启发性的示例:文本编辑器的“撤消”操作使用堆栈来组织数据; Web浏览器的事件循环(用于处理事件(点击,悬停等))使用队列来处理数据。

现在暂停片刻,想象一下作为用户和开发人员,我们使用堆栈和队列的次数。 太神奇了吧? 由于它们在设计上无处不在且相似,因此我决定使用它们向您介绍数据结构。

一堆

在计算机科学中,堆栈是线性数据结构。 如果此语句像您最初对我一样对您具有边际价值,请考虑以下替代方法:堆栈按顺序组织数据。

此顺序通常被描述为自助餐厅的一堆菜。 将餐盘添加到一叠盘子时,餐盘会保持添加时的顺序。 此外,添加板时,将其推向堆栈的底部。 每次添加新的印版时,该印版都被推向堆叠的底部,但它也代表了印版堆叠的顶部。

这种添加板的过程将保留将每个板添加到堆栈中的顺序。 从堆叠中取出印版还将保留所有印版的顺序。 如果从堆叠的顶部卸下印版,则堆叠中的所有其他印版仍将在堆叠中保留正确的顺序。 我所描述的也许是用太多的词,是大多数食堂如何添加和移除餐盘!

为了提供堆栈的更多技术示例,让我们回顾一下文本编辑器的“撤消”操作。 每次将文本添加到文本编辑器时,该文本都会被推入堆栈。 文本编辑器的第一个添加项代表了堆栈的底部。 最近的更改代表堆栈的顶部。 如果用户要撤消最近的更改,则将删除堆栈顶部。 可以重复此过程,直到堆栈中没有更多添加内容为止,这是一个空白文件!

堆栈的操作

由于我们现在有了堆栈的概念模型,因此让我们定义堆栈的两个操作:

  • push(data)添加数据。
  • pop()删除最近添加的数据。

堆栈的实现

现在,让我们为堆栈编写代码!

堆栈的属性

对于我们的实现,我们将创建一个名为Stack的构造函数。 Stack每个实例将具有两个属性: _size_storage

function Stack() {
    this._size = 0;
    this._storage = {};
}

this._storage使Stack每个实例都有自己的容器来存储数据; this._size反映将数据推送到当前版本的Stack 。 如果创建了新的Stack实例并将数据推送到其存储中,则this._size将增加到1。如果再次将数据推送到栈中,则this._size将增加到2。堆栈,则this._size将减小为1。

堆栈方法

我们需要定义可以从堆栈中添加(推送)和删除(弹出)数据的方法。 让我们从推送数据开始。

方法1之2: push(data)

(此方法可以在Stack所有实例之间共享,因此我们将其添加到Stack的原型中。)

对于此方法,我们有两个要求:

  1. 每次添加数据时,我们都希望增加堆栈的大小。
  2. 每次添加数据时,我们都希望保留添加顺序。
Stack.prototype.push = function(data) {
    // increases the size of our storage
    var size = this._size++;

    // assigns size as a key of storage
    // assigns data as the value of this key
    this._storage[size] = data;
};

我们对push(data)包括以下逻辑。 声明一个名为size的变量,并为其分配this._size++的值。 分配size作为this._storage的键。 并分配data作为相应键的值。

如果我们的堆栈调用了push(data)五次,那么我们的堆栈大小将为5。第一次推送到堆栈将在this._storage为该数据分配键1。 第五次调用push(data)将在this._storage为该数据分配键5。 我们刚刚为数据分配了订单!

方法2之2: pop()

现在我们可以将数据推入堆栈; 下一步的逻辑步骤是从堆栈中弹出(删除)数据。 从堆栈中弹出数据并不仅仅是删除数据。 它仅删除最新添加的数据。

这是此方法的目标:

  1. 使用堆栈的当前大小来获取最新添加的数据。
  2. 删除最近添加的数据。
  3. _this._size减一。
  4. 返回最近删除的数据。
Stack.prototype.pop = function() {
    var size = this._size,
        deletedData;

    deletedData = this._storage[size];

    delete this._storage[size];
    this.size--;

    return deletedData;
};

pop()满足我们的四个目标。 首先,我们声明两个变量: size初始化为堆栈的大小; deletedData分配给最近添加到堆栈中的数据。 其次,我们删除最近添加的数据的键值对。 第三,我们将堆栈的大小减1。第四,我们返回从堆栈中删除的数据。

如果我们测试当前的pop()实现,则会发现它适用于以下用例。 如果push(data)堆栈,则堆栈的大小将增加一。 如果我们从堆栈中pop()数据,则堆栈的大小将减少1。

但是,当我们反转调用顺序时会出现问题。 考虑以下情形:我们调用pop() ,然后调用push(data) 。 我们的堆栈大小更改为-1,然后更改为0。但是我们堆栈的正确大小为1!

为了处理这个用例,我们将一个if语句添加到pop()

Stack.prototype.pop = function() {
    var size = this._size,
        deletedData;

    if (size) {
        deletedData = this._storage[size];

        delete this._storage[size];
        this._size--;

        return deletedData;
    }
};

加上我们的if语句,仅在存储中有数据时才执行代码主体。

堆栈的完整实现

我们对Stack实现已完成。 无论我们调用任何一种方法的顺序如何,我们的代码都可以工作! 这是我们代码的最终版本:

function Stack() {
    this._size = 0;
    this._storage = {};
}

Stack.prototype.push = function(data) {
    var size = ++this._size;
    this._storage[size] = data;
};

Stack.prototype.pop = function() {
    var size = this._size,
        deletedData;

    if (size) {
        deletedData = this._storage[size];

        delete this._storage[size];
        this._size--;

        return deletedData;
    }
};

从堆栈到队列

当我们要按顺序添加数据并删除数据时,堆栈很有用。 根据其定义,堆栈只能删除最近添加的数据。 如果我们想删除最旧的数据会怎样? 我们想使用一个名为queue的数据结构。

排队

类似于堆栈,队列是线性数据结构。 与堆栈不同,队列仅删除最早添加的数据。

为了帮助您概念化这将如何工作,让我们花点时间使用一个类比。 想象一下,队列与熟食店的售票系统非常相似。 每个客户都可以买票,并在拨打他们的电话号码时得到服务。 拿第一张票的顾客应该首先得到服务。

让我们进一步想象一下,该票证上显示的数字是“ one”。 下一张票证上显示数字“ 2”。 拿第二张票的顾客将获得第二位。 (如果我们的票务系统像堆叠一样运作,那么最先进入堆叠的客户将是最后一个获得服务的客户!)

队列的一个更实际的示例是Web浏览器的事件循环。 当触发不同的事件(例如单击按钮)时,会将它们添加到事件循环的队列中,并按照进入队列的顺序进行处理。

队列操作

由于我们现在有了队列的概念模型,因此让我们定义其操作。 您将注意到,队列的操作与堆栈非常相似。 区别在于删除数据的位置。

  • enqueue(data)将数据添加到队列。
  • dequeue将最早添加的数据删除到队列中。

队列的实现

现在让我们为队列编写代码!

队列的属性

对于我们的实现,我们将创建一个名为Queue的构造函数。 然后,我们将添加三个属性: _oldestIndex_newestIndex_storage 。 在_oldestIndex ,对_oldestIndex_newestIndex将更加清楚。

function Queue() {
    this._oldestIndex = 1;
    this._newestIndex = 1;
    this._storage = {};
}
队列方法

现在,我们将创建在队列的所有实例之间共享的三个方法: size()enqueue(data)dequeue(data) 。 我将概述每种方法的目标,揭示每种方法的代码,然后解释每种方法的代码。

方法1之3: size()

此方法有两个目标:

  1. 返回正确的队列大小。
  2. 保留队列的正确密钥范围。
Queue.prototype.size = function() {
    return this._newestIndex - this._oldestIndex;
};

实现size()看起来很琐碎,但是您很快就会发现这是不正确的。 要了解原因,我们必须快速回顾一下如何实现堆栈的size

使用堆栈的概念模型,让我们想象一下将五个板推到堆栈上。 我们的堆栈大小为五,每个板块都有一个与之相关的数字,从一个(第一个添加的板)到五个(最后添加的板)。 如果我们移除三个板,那么我们将拥有两个板。 我们可以简单地从5中减去3以获得正确的大小,即2。 关于堆栈大小,这是最重要的一点:当前大小表示与堆栈顶部的板(2)和堆栈中的另一个板(1)相关的正确密钥。 换句话说,键的范围始终是从当前大小到1。

现在,让我们将此堆栈size应用于我们的队列。 想象有五个客户从我们的票务系统中取票。 第一个客户的票证上显示数字1,第五个客户的票证上显示数字5。在排队的情况下,首先为拥有第一张票证的客户提供服务。

现在,让我们假设第一个客户已得到服务,并且该票证已从队列中删除。 与堆栈类似,我们可以通过从5中减去1来获得正确的队列大小。我们的队列当前有4个未提供的票证。 现在,这就是问题所在:大小不再代表正确的票号。 如果仅从5中减去1,则大小为4。我们无法使用4来确定队列中剩余票证的当前范围。 我们队列中的票号是1到4还是2到5? 答案尚不清楚。

在队列中具有以下两个属性的好处是_oldestIndex_newestIndex 。 所有这些似乎都令人困惑-我仍然偶尔会感到困惑。 以下是我开发的示例,可帮助我合理化所有内容。

想象一下,我们的熟食店有两个售票系统:

  1. _newestIndex表示来自客户票务系统的票证。
  2. _oldestIndex表示来自员工票务系统的票证。

关于拥有两个票务系统,这是最难把握的概念:当两个票务系统中的号码相同时,队列中的每个客户都已得到解决,并且队列为空。 我们将使用以下方案来加强此逻辑:

  1. 顾客取票。 从_newestIndex检索的客户的票号是1。客户票证系统中的下一张票是2。
  2. 员工不拿票,员工票证系统中的当前票是1。
  3. 我们将客户系统(2)中的当前票证编号减去雇员系统(1)中的票证编号以获得数字1。数字1表示仍在队列中但尚未除去的票证数量。
  4. 员工从售票系统中取票。 该票证代表正在提供的客户票证。 从_oldestIndex检索提供的票证,该票证显示数字1。
  5. 重复第4步,现在差为零-队列中不再有票证!

现在,我们有一个属性( _newestIndex )可以告诉我们队列中分配的最大数字(键),还有一个属性( _oldestIndex )可以告诉我们队列中最早的索引号(键)。

我们已经充分研究了size() ,所以现在让我们进入enqueue(data)

方法2之3:入 enqueue(data)

对于入enqueue ,我们有两个目标:

  1. 使用_newestIndex作为一个关键this._storage和使用任何数据被添加作为键的值。
  2. _newestIndex的值增加1。

基于这两个目标,我们将创建以下enqueue(data)

Queue.prototype.enqueue = function(data) {
    this._storage[this._newestIndex] = data;
    this._newestIndex++;
};

此方法的主体包含两行代码。 在第一行中,我们使用this._newestIndex创建一个新的密钥this._storage和分配data给它。 this._newestIndex始终从1开始。在第二行代码中,我们将this._newestIndex递增1,将其值更新为2。

这就是我们需要enqueue(data)的所有代码。 现在让我们实现dequeue()

方法3之3: dequeue()

这是此方法的目标:

  1. 删除队列中最旧的数据。
  2. _oldestIndex递增1。
Queue.prototype.dequeue = function() {
    var oldestIndex = this._oldestIndex,
        deletedData = this._storage[oldestIndex];

    delete this._storage[oldestIndex];
    this._oldestIndex++;

    return deletedData;
};

dequeue()的主体中,我们声明了两个变量。 第一个变量, oldestIndex ,被分配给一个队列的当前值this._oldestIndex 。 第二个变量deletedData被分配了this._storage[oldestIndex]包含的值。

接下来,我们删除队列中最早的索引。 删除后,我们将this._oldestIndex递增1。最后,我们返回刚删除的数据。

与我们的第一个带有堆栈的pop()实现类似,我们的dequeue()实现无法处理在添加任何数据之前删除数据的情况。 我们需要创建一个条件来处理此用例。

Queue.prototype.dequeue = function() {
    var oldestIndex = this._oldestIndex,
        newestIndex = this._newestIndex,
        deletedData;

    if (oldestIndex !== newestIndex) {
        deletedData = this._storage[oldestIndex];
        delete this._storage[oldestIndex];
        this._oldestIndex++;

        return deletedData;
    }
};

每当oldestIndexnewestIndex的值不相等时,我们就会执行之前的逻辑。

队列的完整实现

我们对队列的实现已完成。 让我们查看整个代码。

function Queue() {
    this._oldestIndex = 1;
    this._newestIndex = 1;
    this._storage = {};
}

Queue.prototype.size = function() {
    return this._newestIndex - this._oldestIndex;
};

Queue.prototype.enqueue = function(data) {
    this._storage[this._newestIndex] = data;
    this._newestIndex++;
};

Queue.prototype.dequeue = function() {
    var oldestIndex = this._oldestIndex,
        newestIndex = this._newestIndex,
        deletedData;

    if (oldestIndex !== newestIndex) {
        deletedData = this._storage[oldestIndex];
        delete this._storage[oldestIndex];
        this._oldestIndex++;

        return deletedData;
    }
};

结论

在本文中,我们探讨了两个线性数据结构:堆栈和队列。 堆栈按顺序存储数据并删除最近添加的数据; 队列按顺序存储数据,但删除最早添加的数据。

如果这些数据结构的实现似乎微不足道,请提醒自己数据结构的目的。 它们的设计并不是过于复杂。 它们旨在帮助我们组织数据。 在这种情况下,如果发现自己需要按顺序组织数据,请考虑使用堆栈或队列。

翻译自: https://code.tutsplus.com/articles/data-structures-with-javascript-stack-and-queue--cms-23348

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值