携手创作,共同成长!这是我参与「掘金日新计划 · 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中测试一下该方法是否能够正常运行,运行的结果如下图所示:
具体的执行效果可以狠戳这里。
当我们将队列中的值都出队后,最后返回的值就是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');
上述的代码中,我们通过链表和数组来模拟队列的出队和入队,它们两个的执行时间在控制台中展示如下:
通过对比我们发现链表的执行效率差不多是数组的40倍左右,具体的执行效果可以狠戳这里。
通过链表实现的队列,空间复杂度是O(n),和前面的栈一样;而链表中的add的时间复杂度是O(1),栈中的add方法的时间复杂度也是O(1);但是链表中的delete方法的时间复杂度是O(1),栈中的delete方法的时间复杂度却是O(n),因此使用链表来操作队列整体的时间复杂度是O(1),比栈的效率要高很多。
最后
我们通过对比栈和链表两种不同的数据结构来实现队列,得出一个结论:数据结构的选择,要比算法优化更重要,因此如果数据结构选择不对,哪怕你的算法写的再精妙,可能效率都不会比选择正确的数据结构的算法要快。
最后,如果这篇文章有帮助到你,❤️关注+点赞❤️鼓励一下作者,谢谢大家