【问题标题】:How to implement deque data structure in javascript?如何在javascript中实现双端队列数据结构?
【发布时间】:2020-05-20 00:21:00
【问题描述】:

我正在用 javascript 学习数据结构

我现在的重点是如何实现双端队列?

Edite:从下面的 cmets 中,我获得了有关如何实现 deque based array 的有用指导。有没有方向如何使用类实现deque based object

我明白了一些我需要的点:

  • addFront()
  • removeFront()
  • peekFront()

  • addBack()
  • removeBack()
  • peekBack()

但我对某些观点感到困惑:

  • 我需要多少个指针? 至少我从 queue 知道我需要两个(头尾)指针,但不确定在 deque

  • 中是否需要更多
  • 在这种情况下,javascript 中哪种数据类型方便作为基础?我在 youtube 上看到一些导师在谈论循环数组,例如在 JS 中对我来说是未知的。

edit2:

我正在关注一本书,名为:学习 javascript 数据结构和算法第三版

在本书的第 5 章中,作者开始实现仅基于对象和一些变量的 Deque

但我不明白他是怎么做到的,因为代码已加密,但我仍然可以从 github repository 访问他的文件并测试他的方法

我可以说@trincot 非常接近图书作者的方法

但是当我比较结果时,我得到了这个 [1 = author - 2 = @trincot] :

根据关于链表的书籍索引出现在第6章,所以我没想到他的解决方案会基于他之前没有提到的东西

如果我错过任何一点,我会很感激告诉我......谢谢

【问题讨论】:

  • 默认的 JS 数组已经是这样的结构:push()pop()shift()unshift() 以及标准索引访问应该为您提供所需的所有工具。跨度>
  • @Sirko 你的意思是我不需要deque 而只需要default JS array 来完成这项工作吗?
  • @Sirko 我明白default JS array 允许随机访问数据,而deque 不允许从末尾添加、修改或删除
  • @AymanMorsy,如果这是您关心的问题,那么只需使可以在您的阵列上使用的所有其他方法无效。
  • 一个JS数组支持你需要的所有方法。如果要抑制任何其他方法,那么您将需要一个包装器或类似的东西来限制访问(也可能是map(),...)。

标签: javascript data-structures deque


【解决方案1】:

如 cmets 中所述,JavaScript 通过其 Array 类/原型对双端队列操作提供原生支持:push、pop、shift、unshift。

如果您仍然想编写自己的实现,那么您可以使用双向链表,您只需要两个“指针”。应该说,在 JavaScript 中,我们真正谈论的不是指针,而是对象。以对象为值的变量或属性实际上是 JavaScript 中的引用

或者,您可以使用圆形数组。由于在 JavaScript 标准中 Arrays 不能保证是连续的数组,例如在 C 中就是这种情况,因此您实际上不需要为此使用 Array 实例。一个普通的对象(或地图)就可以了。

所以这里有两种可能的实现方式:

双向链表

class Deque {
    constructor() {
        this.front = this.back = undefined;
    }
    addFront(value) {
        if (!this.front) this.front = this.back = { value };
        else this.front = this.front.next = { value, prev: this.front };
    }
    removeFront() {
        let value = this.peekFront();
        if (this.front === this.back) this.front = this.back = undefined;
        else (this.front = this.front.prev).next = undefined;
        return value;
    }
    peekFront() { 
        return this.front && this.front.value;
    }
    addBack(value) {
        if (!this.front) this.front = this.back = { value };
        else this.back = this.back.prev = { value, next: this.back };
    }
    removeBack() {
        let value = this.peekBack();
        if (this.front === this.back) this.front = this.back = undefined;
        else (this.back = this.back.next).back = undefined;
        return value;
    }
    peekBack() { 
        return this.back && this.back.value;
    }
}

// demo
let deque = new Deque;
console.log(deque.peekFront()); // undefined
deque.addFront(1);
console.log(deque.peekBack()); // 1
deque.addFront(2);
console.log(deque.removeBack()); // 1
deque.addFront(3);
deque.addFront(4);
console.log(deque.peekBack()); // 2
deque.addBack(5);
deque.addBack(6);
console.log(deque.peekBack()); // 6
console.log(deque.removeFront()); // 4
console.log(deque.removeFront()); // 3
console.log(deque.removeFront()); // 2
console.log(deque.removeFront()); // 5
console.log(deque.removeFront()); // 6
console.log(deque.removeFront()); // undefined

圆形“数组”

class Deque {
    constructor() {
        this.data = {}; // Or Array, but that really does not add anything useful
        this.front = 0;
        this.back = 1;
        this.size = 0;
    }
    addFront(value) {
        if (this.size >= Number.MAX_SAFE_INTEGER) throw "Deque capacity overflow";
        this.size++;
        this.front = (this.front + 1) % Number.MAX_SAFE_INTEGER;
        this.data[this.front] = value;
    }
    removeFront()   {
        if (!this.size) return;
        let value = this.peekFront();
        this.size--;
        delete this.data[this.front];
        this.front = (this.front || Number.MAX_SAFE_INTEGER) - 1;
        return value;
    }
    peekFront()     { 
        if (this.size) return this.data[this.front];
    }
    addBack(value) {
        if (this.size >= Number.MAX_SAFE_INTEGER) throw "Deque capacity overflow";
        this.size++;
        this.back = (this.back || Number.MAX_SAFE_INTEGER) - 1;
        this.data[this.back] = value;
    }
    removeBack()   {
        if (!this.size) return;
        let value = this.peekBack();
        this.size--;
        delete this.data[this.back];
        this.back = (this.back + 1) % Number.MAX_SAFE_INTEGER;
        return value;
    }
    peekBack()     { 
        if (this.size) return this.data[this.back];
    }
}

// demo
let deque = new Deque;
console.log(deque.peekFront()); // undefined
deque.addFront(1);
console.log(deque.peekBack()); // 1
deque.addFront(2);
console.log(deque.removeBack()); // 1
deque.addFront(3);
deque.addFront(4);
console.log(deque.peekBack()); // 2
deque.addBack(5);
deque.addBack(6);
console.log(deque.peekBack()); // 6
console.log(deque.removeFront()); // 4
console.log(deque.removeFront()); // 3
console.log(deque.removeFront()); // 2
console.log(deque.removeFront()); // 5
console.log(deque.removeFront()); // 6
console.log(deque.removeFront()); // undefined

当尝试从空双端队列中检索值时,方法将返回 undefined

【讨论】:

  • 我建议在 remove 方法中添加delete,这样就不会泄漏内存。
  • @kaya3,好建议!在第二个 sn-p 中添加了delete
  • @trincot 你非常接近......我在我的 Q 中做了一些编辑你能看到它并给我一个想法是第三种可能的实现或只是你的答案需要一些改进或一些改变达到我在上面看到的结果......非常感谢
  • 非常感谢 trincot ...在@Bergi 的帮助下获得了第三个实现的源代码我希望能帮助任何人github.com/PacktPublishing/…
  • @AymanMorsy,你提到的那个实现真的很差。每当您执行addFront 并且lowestCount 为零时,它都会执行for 循环以将所有项目移动一个位置。这确实扼杀了您期望从双端队列获得的性能提升。此外,JavaScript 有一个 unshift 方法可以完成所有这些工作,但效率更高。高效的实现不需要循环。
【解决方案2】:

以简单的方式实现出队:

const dequeue = [];

// push element from rear end
dequeue.push(3); // [3]
dequeue.push(8); // [3, 8]

// push element from front end
dequeue.unshift(5); // [5, 3, 8]
dequeue.unshift(11); // [11, 5, 3, 8]     

// pop element from rear end
dequeue.pop(); // [11, 5, 3]

// pop element from front end
dequeue.shift(); // [5, 3]

【讨论】:

  • 这是错误的,在 Deque 中 pushleft 应该是 O(1) 时间,但是 JS 数组 unshift() 需要 O(n)。
猜你喜欢
  • 2011-08-09
  • 1970-01-01
  • 2013-03-10
  • 1970-01-01
  • 2016-08-18
  • 1970-01-01
  • 2018-07-27
  • 1970-01-01
  • 2012-01-27
相关资源
最近更新 更多