【算法初探】前端学算法之用链表实现队列

467 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 10 天,点击查看活动详情

前因

前面的文章中,我们通过来实现了队列,但是我们都知道用来实现其实是有性能问题的,因为我们是用数组来模拟,而在上一篇文章中,我们分析了数组链表的差异,知道了数组在进行查找元素的时候速度是很快的,而进行添加和删除的速度很慢;链表的查询虽然很慢,但是它的添加和删除是很快的,因此这一节我们一起用链表来实现队列

下面我们一起来看一下如何使用链表来实现队列吧!

代码实现

上一篇文章中,我们通过实现单向链表,以及反转单向链表,对链表的相关操作已经有了一个大致的了解,那我们该如何使用链表来实现队列呢?还记得队列是什么吗?队列先入先出的数据结构,是有序的;而链表也是有序的数据结构,那我们一起来看看代码的实现,如下:

/*
 * 用链表实现队列
 * @param 
 */
 
interface IListNode {
    value: number;
    next: IListNode | null;
}

class MyQueue {
    // 第一个节点
    private head: IListNode | null = null;
    // 最后一个节点
    private tail: IListNode | null = null;
    // 链表的长度
    private len = 0;
    
    /*
     * 在tail位置入队
     * @param n number
     */
    add(n: number) {
        const newNode: IListNode = {
            value: n,
            next: null
        };
        
        // 处理head
        if (this.head == null) {
            this.head = newNode;
        }
        
        // 处理tail
        const tailNode = this.tail;
        // 如果存在最后一个节点
        if (tailNode) {
            tailNode.next = newNode;
        }
        this.tail = newNode;
        
        // 记录长度
        this.len++;
    }
    /*
     * 在head位置出队
     */
    delete(): number | null {
        const headNode = this.head;
        // 如果当前第一个节点已经为空了,则直接返回空
        if (headNode == null) return null;
        if (this.len <= 0) return null;
        
        // 取值
        const value = headNode.value;
        
        // 处理head
        this.head = headNode.next;
        
        // 记录长度
        this.len--;
        
        return value;
    }
    get length(): number {
        // length需要单独存储,不能遍历链表来获取,否则时间复杂度太高O(n)
        return this.len;
    }
}

// 代码测试
const q = new MyQueue();
q.add(100);
q.add(200);
q.add(300);
console.info('length1:', q.length);
console.log(q.delete());
console.info('length2:', q.length);
console.log(q.delete());
console.info('length3:', q.length);

因为队列先进先出,我们通过add方法入队,执行了三次,所以队列中的值应该是[300, 200, 100],然后我们通过delete出队,每次出队的时候可以看到当前出队的值,最终完成用链表实现队列的算法。我们可以在TypeScript官网的Playground中测试一下该方法是否能够正常运行,运行的结果如下图所示:

image.png

具体的执行效果可以狠戳这里

当我们将队列中的值都出队后,最后返回的值就是null,因为已经没有值了,所以返回的就是null

性能分析

我们前面用实现过队列,而这一节又用链表来实现队列,为什么要再实现一次队列呢?还是因为性能原因,在前面的文章中,我们分析了用实现队列中的add时间的复杂度是O(1)delete的时间复杂度是O(n);整体的空间复杂度是O(n)。而我们这次用链表实现的队列中,

具体我们可以通过代码来测试一下用链表来实现队列的性能,测试代码如下:

// 性能测试
const q1 = new MyQueue()
console.time('queue with list');
for (let i = 0; i < 10 * 10000; i++) {
    q1.add(i);
}
for (let i = 0; i < 10 * 10000; i++) {
    q1.delete();
}
console.timeEnd('queue with list');

const q2 = [];
console.time('queue with array');
for (let i = 0; i < 10 * 10000; i++) {
    q2.push(i); // 入队
}
for (let i = 0; i < 10 * 10000; i++) {
    q2.shift(); // 出队
}
console.timeEnd('queue with array');

上述的代码中,我们通过链表数组来模拟队列的出队和入队,它们两个的执行时间在控制台中展示如下:

image.png

通过对比我们发现链表的执行效率差不多是数组的40倍左右,具体的执行效果可以狠戳这里

通过链表实现的队列,空间复杂度是O(n),和前面的一样;而链表中的add的时间复杂度是O(1)中的add方法的时间复杂度也是O(1);但是链表中的delete方法的时间复杂度是O(1)中的delete方法的时间复杂度却是O(n),因此使用链表来操作队列整体的时间复杂度是O(1),比的效率要高很多。

最后

我们通过对比链表两种不同的数据结构来实现队列,得出一个结论:数据结构的选择,要比算法优化更重要,因此如果数据结构选择不对,哪怕你的算法写的再精妙,可能效率都不会比选择正确的数据结构的算法要快。

最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家

往期回顾

【算法初探】前端学算法之旋转数组(1)

【算法初探】前端学算法之旋转数组(2)

【算法初探】前端学算法之有效的括号

【算法初探】前端学算法之用栈实现队列

【算法初探】前端学算法之反转单向链表