资讯专栏INFORMATION COLUMN

数据结构知否知否系列之 — 队列篇

galois / 2620人阅读

摘要:初始化队列初始化一个存储队列中元素的数据结构,如果未传入默认赋值空数组,传入需先校验类型是否正确。头等舱和商务舱乘客的优先级要高于经济舱乘客。在有些国家,老年人和孕妇或带小孩的妇女登机时也享有高于其他乘客的优先级。

有一天,当回顾自己走过的路时,你会发现这些奋斗不息的岁月,才是最美好的人生。——弗洛伊德

队列,英文 First In First Out 简称 FIFO,遵从先进先出的原则,与 “栈” 相反,在队列的尾部添加元素,在队列的头部删除元素,如果队列中没有元素就称为空队列。

队列对应到生活场景中有很多例子,例如,我们去火车站窗口购票总要排队,先排队的人先购票,有新的人来了则在队尾排队等待前面的完成了依次购票。另外我们的订单超时队列、活动抢购先到先得等等,队列在生活中应用很广泛。

作者简介:五月君,Nodejs Developer,热爱技术、喜欢分享的 90 后青年,公众号「Nodejs技术栈」,Github 开源项目 https://www.nodejs.red

JavaScript 数组实现队列

JavaScript 中提供的数组功能即可实现一个简单的队列,使用起来也很方便,熟悉相关 API 即可,下面我们来看下基于 JS 数组的入队、出队过程实现。

以上图片展示了队列的初始化、入队、出队过程,下面我们采用 JavaScript 原型链的方式实现。

初始化队列

初始化一个存储队列中元素的数据结构,如果未传入默认赋值空数组,传入需先校验类型是否正确。

function QueueStudy(elements) {
    if (elements && !(elements instanceof Array)) {
        throw new Error("必须为数组格式!");
    }

    this.elements = elements || [];
}

队列添加元素

实现一个 enQueue 方法,向队列添加元素,注意只能是队列尾部添加,使用 JavaScript 数组中的 push 方法。

QueueStudy.prototype.enQueue = function(element) {
    this.elements.push(element);
}

队列移除元素

实现一个 deQueue 方法,向队列头部弹出元素,使用 JavaScript 数组中的 shift 方法。

QueueStudy.prototype.deQueue = function() {
    return this.elements.shift();
}

通过 JavaScript 数组实现是很简单的,源码参见 https://github.com/Q-Angelo/project-training/tree/master/algorithm/queue-js.js

优先队列

优先队列,元素的添加、删除是基于优先级进行的。一个现实的例子就是机场登机的顺序。头等舱和商务舱乘客的优先级要高于经济舱乘客。在有些国家,老年人和孕妇(或带小孩的妇女)登机时也享有高于其他乘客的优先级。

优先队列对应到我们生活场景中也有很多例子,例如我们去银行办理业务,一般都会排号先到的先办理,但是呢,还会有 VIP 会员优先办理,又或者去火车站窗口上购票也会有提示军人可以优先办理等等

实现步骤

核心实现继 JavaScript 数组实现队列的例子,对入队函数进行改造如下所示:

声明 queueElement 对象,包含了要添加到队列的元素

如果队列为空直接入队

如果找到一个比 priority 优先级大的元素,插入新元素,这里使用到了 JS 数组中的 splice 方法

最后如果队列中的所有元素的优先级都小于 priority,则直接在队列尾部入队

另外打印输出的方法也做了简单修改

代码示例

PriorityQueue.prototype.enQueue = function(element, priority) {
    const queueElement = { element, priority };

    if (this.isEmpty()) {
        return this.elements.push(queueElement);
    }

    let added = false;
    for (let i=0; i < this.elements.length; i++) {
        if (priority < this.elements[i]["priority"]) {
            added = true;
            this.elements.splice(i, 0, queueElement)
            break;
        }
    }

    if (!added) {
        this.elements.push(queueElement);
    }
}

PriorityQueue.prototype.print = function() {
    console.log(this.elements.map(item => item.element).join(" | "));
}

运行测试

const queue = new PriorityQueue();
queue.enQueue("普通会员1", 5);
queue.enQueue("普通会员2", 10);
queue.print() // 普通会员1 | 普通会员2
queue.enQueue("VIP会员1", 3);
queue.print() // VIP会员1 | 普通会员1 | 普通会员2
queue.enQueue("VIP会员2", 3);
queue.print() // VIP会员1 | VIP会员2 | 普通会员1 | 普通会员2
queue.deQueue();
queue.print() // VIP会员2 | 普通会员1 | 普通会员2

图例展示

下面以图例的形式展示以上优先队列程序的运行过程

以上是将优先级最小的元素放置于队列前面,称之为最小优先队列,最大优先队列的实现则反之。源码参见 https://github.com/Q-Angelo/project-training/tree/master/algorithm/queue-priority.js

循环队列

循环队列有些地方也称之为环形队列,其本身是一种环形结构的队列,相较于普通队列有个好处是第一个元素出队之后,剩下元素无需依次向前移位,充分利用了向量空间,在以下介绍中给出了完整的实现过程。

在设计环形队列时即可顺时针也可逆时针两个方向进行实现,在入队时可根据 (tail % capacity) 规则,进行队尾添加元素,tail 表示队尾的指针,capacity 表示容量,出队同样以(head % capacity)规则操作,head 表示队头指针,下面以长度为 6 的队列进行图文形式说明下实现过程。

ES6 实现循环队列

以下采用 EcameScript 6 的 Class 写法,实现一个环形队列,需要做哪些点呢?以下列出需要实现的功能点:

创建队列,初始化队列空间

检查队列是否为空

检查队列是否溢出

入队

出队

队列长度

清空队列

销毁队列,内存空间也将释放

队列遍历输出

const Init = Symbol("QueueStudy#Init");

class QueueStudy {
    constructor (capacity) {
        if (!capacity) {
            throw new Error("The capacity field is required!");
        }

        this.capacity = capacity; // 初始化容量
        this[Init]();
    }

    /**
     * 清空队列,内存保留
     */
    clear() {
        this[Init]()
    }

    [Init]() {
        this.queue = new Array(this.capacity); // 初始化队列内存空间
        this.queueLen = 0; // 初始化队列元素
        this.head = 0; // 队头
        this.tail = 0; // 尾部
    }

    /**
     * 队列是否为空
     */
    isEmpty() {
        return this.queueLen === 0 ? true : false;
    }

    /**
     * 队列是否溢出
     */
    isOverflow() {
        return this.queueLen === this.capacity
    }

    /**
     * 入队
     */
    enQueue(element) {
        if (this.isOverflow()) {
            return false;
        }

        this.queue[this.tail] = element;
        this.tail++;
        this.tail = this.tail % this.capacity;
        this.queueLen++;
        return true;
    }

    /**
     * 出队
     */
    deQueue() {
        if (this.isEmpty()) {
            throw new Error("队列为空");
        } else {
            const element = this.queue[this.head];
            this.head++; // 队头位置移动
            this.head = this.head % this.capacity;
            this.queueLen--;
            return element;
        }
    }

    /**
     * 队列长度
     */
    len() {
        return this.queueLen;
    }

    /**
     * 销毁队列,内存回收
     */
    destroy() {
        this.queue = null;
    }

    /**
     * 队列元素遍历
     */
    traversing() {
        console.log("------------traversing start------------");
        
        for (let i=this.head; i

运行测试

const q1 = new QueueStudy(6);

q1.enQueue("a");
q1.traversing();
q1.enQueue("b");
q1.enQueue("c");
q1.enQueue("d");
q1.enQueue("e");
q1.enQueue("f");
q1.traversing();
console.log("出队: ", q1.deQueue());
q1.enQueue("g");
q1.traversing();
console.log("出队: ", q1.deQueue());
console.log("出队: ", q1.deQueue());
q1.enQueue("h");
console.log("出队: ", q1.deQueue());
console.log("出队: ", q1.deQueue());
console.log("出队: ", q1.deQueue());
q1.traversing();
q1.clear();
q1.traversing();

源码参见 https://github.com/Q-Angelo/project-training/tree/master/algorithm/queue-ring.js

总结

以上就是队列的讲解,最开始讲解了在 JavaScript 中如何应用队列,同时也使用 JavaScript 数组提供的 API 功能实现了优先队列,最后介绍了从零开始如何实现一个环形队列,这个是重点,通过环形队列这个例子也可以帮助大家理解队列的基本实现机制是怎么样的,对环形队列这块不理解的建议多看几遍,总之多动手、多实践。

推荐我在学习数据结构中看的两本书 学习JavaScript数据结构与算法(第2版)、图解数据结构使用 Python 当然也不乏有其它更好的资源,供大家学习参考。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/106974.html

相关文章

  • 前端进击的巨人(六):否知否,须知this

    摘要:有关函数柯里化的详解,请回阅前端进击的巨人五学会函数柯里化。构造函数中的通过操作符可以实现对函数的构造调用。在了解构造函数中的前,有必要先了解下实例化对象的过程。 showImg(https://segmentfault.com/img/bVburMp?w=800&h=600); 常见this的误解 指向函数自身(源于this英文意思的误解) 指向函数的词法作用域(部分情况) th...

    Andrman 评论0 收藏0
  • 超融合的后劲儿,SmartX的韧劲儿

    摘要:超融合的持续演进与企业数字化转型的步调是一致的,这也是超融合受到业界追捧的原因。对于超融合市场的每一个参与者来说,目前最重要的任务是如何快速有效地获客。而这也正是超融合市场走向纵深所需的后劲儿,也是持续发展所需要的韧劲儿。超融合(HCI)市场可以一直火热,但超融合应用的落地最好理智而冷静。最热时,据说中国市场有上百家超融合供应商。随着超融合应用逐步落地,在市场和用户需求的双重考验下,有人离开...

    DangoSky 评论0 收藏0
  • 【Copy攻城狮日志】Node.jshttp下载图片失败

    摘要:当用户或搜索引擎向网站服务器发出浏览请求时,服务器返回的数据流中头信息中的状态码的一种,表示本网页永久性转移到另一个地址。通过在源代码中添加日志输出,我们也能清楚地看到的状态码。 Created 2019-4-5 22:24:33 by huqi Updated 2019-4-5 23:23:56 by huqi showImg(https://segmentfault.com/...

    darkbaby123 评论0 收藏0
  • 让前端面试不在难(深度克隆 clone)

    摘要:今天聊一下这个前端面试高频问题,由此引出这些。下面我们先详细的聊一下,完了解决下面试官的问题。数组之所以为是因为上边说了和其实就是想说这两个对于深度的实现来说不够严谨要不就是多层判断。 今天聊一下clone这个前端面试高频问题,由此引出typeof、instanceof、Object.prototype.toString这些javascript Api。 下面我们先详细的聊一下,完了解...

    hover_lew 评论0 收藏0

发表评论

0条评论

galois

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<