yocto-queue 队列 链表源码学习

1. 前言

2. 基础知识学习

2.1 队列学习

队列是一种先进先出线性数据结构,类似于现实中的排队场景。

特性

  • 入队:元素添加到队尾
  • 出队:元素从队首移除

用数组实现一个队列

kotlin 复制代码
class MyQueue{
    queue = [];
    constructor() {
    }
    // 入队
    enqueue(val) {
        this.queue.push(val);
    }
    // 出队
    dequeue() {
        return this.queue.shift();
    }
    // 返回队首值
    peek() {
        return this.queue[0];
    }
    // 是否是一个空对队列
    isEmpty() {
        return this.queue.length === 0;
    }
    // 队列长度
    size () {
        return this.queue.length
    }
}

const queue = new MyQueue();
queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3,)
queue.enqueue(4)
queue.dequeue()
queue.dequeue() // [3, 4]
console.log('fffffffffffffffffff',queue.peek()) // 3

2.2 链表学习

链表是一种动态性的线性数据结构,通过指针(引用)将零散的内存快串联起来,每个节点包含"数据域"和"指针域"。与数组相比,链表适合频繁的插入和删除操作

对比项 链表 数组
内存分配 动态分配,无浪费 连续分配的内存,需预分配
访问速度 O(n) 需要遍历 O(1) 通过索引
插入/删除 O(1) O(n)需要搬移元素
适用场景 支持快速插入、删除,但随机访问效率低。 频繁访问、数据量固定

单链表实现如下

kotlin 复制代码
// 链表节点类
class Node {
    constructor(val) {
        this.val = val;
        this.next = null;
    }
}

const isNull = v => v === null || v === undefined || v === ''
// 链表类
class LinkNode {
    constructor() {
        this.head = null; // 头节点
    }
    // 尾部添加节点
    appendNode(node) {
        if (!this.head) {
            this.head = node;
            return
        }
        const tail = this.getLastNode()
        tail.next = node
    }
    // 头部添加节点
    prependNode(node) {
        if (!this.head) {
            this.head = node;
        }
        node.next = this.head
        this.head = node;
    }
    // 根据索引位置添加节点
    addNodeByIndex(index, node) {
        if (isNull(index)) return
        if (index < 0 || index > this.getSize()) return
        if (index === 0) {
            this.head = this.head.next
            return
        }
        let preNode = this.getNodeByIndex(index - 1)

        let t = preNode.next
        preNode.next = node
        node.next = t
    }
    // 根据索引位置删除节点
    delNodeByIndex(index) {
        if (isNull(index)) return
        if (index < 0 || index > this.getSize()) return

        if (index === 0) {
            const r = this.head
            this.head = this.head.next
            return r
        }
        let preNode = this.getNodeByIndex(index - 1)
        const r = preNode.next
        preNode.next = preNode.next.next
        return r

    }
    // 根据索引位置查询节点
    getNodeByIndex(index) {
        if (isNull(index)) return
        if (index < 0 || index > this.getSize()) return

        let cur = this.head;
        while (index > 0) {
            cur = cur.next
            index--
        }
        return cur
    }
    // 获取链表长度
    getSize() {
        let count = 0;
        let cur = this.head;
        while (cur) {
            count++
            cur = cur.next;
        }
        return count
    }
    // 获取最后一个节点
    getLastNode() {
        let cur = this.head
        while (cur.next) {
            cur = cur.next
        }
        return cur
    }
    // 反转链表
    reverse() { 
        let cur = this.head
        let pre = null
        while(cur) {
            let next = cur.next
            cur.next = pre
            pre = cur
            cur = next
        }
        this.head = pre
        return this.head
    }
}

// 构建一个节点数组 【1,2,3,4,5 】
const nodeList = [1,2,3,4,5].map(x => new Node(x))
// 初始化列表
const linkNode = new LinkNode()
// 得到链表 1 => 2 => 3 => 4 => 5
nodeList.forEach(x => linkNode.appendNode(x)) 
// 向链表头部添加一个节点0
const node0 = new Node(0)
// 得到链表 0 => 1 => 2 => 3 => 4 => 5
linkNode.prependNode(node0) 
// 删除索引为3的节点 得到链表 0 => 1 => 2 => 4 => 5
linkNode.delNodeByIndex(3)
// 向索引为2的位置添加一个节点8, 得到链表 0 => 1 => 8 =>2 => 4 => 5
const node8 = new Node(8)
linkNode.addNodeByIndex(2, node8)
// 获取链表最后一个节点 5
const tail = linkNode.getLastNode()
console.log(tail)
// 链表反转, 得到链表 5 => 4 => 2 => 8 => 1 => 0
const head = linkNode.reverse()
console.log(head)

2.3 类的私有属性和Symbol.iterator学习

2.3.1

  • ES2022正式为class添加了私有属性,方法是在属性名之前使用#表示
  • 私有属性只能在类的内部使用,如果在类的外部使用就会报错
arduino 复制代码
class TestCount{
    #count = 0
    addCount() {
        this.#count++
    }
    getCount() {
        return this.#count
    }
}
const c = new TestCount()
c.addCount()

// 浏览器会抛错错误 Uncaught SyntaxError: Private field '#count' must be declared in an enclosing class
c.#count

2.3.2

ES6规定,只有一个数据结构具有Symbol.iterator属性,就认为是可遍历的,就可以用for...of循环遍历这个数据结构,任意一个对象,我们给他添加Symbol.iterator属性,这个属性是一个函数,返回一个迭代器,我们就可以遍历啦~

但是啥子是迭代器呢 ?

它是一个对象,里面有个next方法,调用迭代器的next方法,返回一个对象,这个对象有2个属性

  • value: 当前迭代的值
  • done: 是否迭代完成

让我们照葫芦画瓢写一个

javascript 复制代码
const obj = {
    [Symbol.iterator]() {
    
    // 看我看我,我是一个迭代器对象,我有一个next属性,他是一个函数,next函数的返回值是一个对象{value: any,  done: true | false}
    const iteratorObje =  {
        value: 1,
        next: function () {
         // 此处返回一个对象,该对象的value属性包含迭代值,done属性表示是否完成迭代。
        // 这里我们让他可以输出1-10的数字
         if(this.value <= 10) {
            return {
              value: this.value++,
              done: false
            };
         }
          return {
            value: 1,
            done: true
          };
        }
      };
      
      return iteratorObje
    }
  };

  for (let num of obj) {
    console.log(num)  // 输出 1 2 3 4 5 6 7 8 9 10
  }

为啥Array、Map、String可以直接使用for of循环呢? 用的时候也没有给这些数据结构加Symbol.iterator属性哒?

因为他们原生就有Symbol.iterator属性,直接在浏览器控制台打印任意一个数组,我们就可以在Array.prototype里面找到Symbol.iterator属性

3. 源码解析

kotlin 复制代码
class Node {
	value;
	next;

	constructor(value) {
		this.value = value;
	}
}
class Queue {
	// 头部节点指针
	#head;
	// 尾部节点指针
	#tail;
	// 长度
	#size;

	constructor() {
		this.clear();
	}
	// 入队
	enqueue(value) {
		const node = new Node(value);

		// 如果队列不为空,则将新节点添加到尾部
		if (this.#head) {
			this.#tail.next = node;
			this.#tail = node;
		} else {
			// 添加到队首
			this.#head = node;
			this.#tail = node;
		}

		// 记录队列长度
		this.#size++;
	}
	// 出队
	dequeue() {
		const current = this.#head;
		if (!current) {
			return;
		}

		// 直接将头部节点指向下一个节点,即出队
		this.#head = this.#head.next;
		this.#size--;
		return current.value;
	}

	peek() {
		if (!this.#head) {
			return;
		}

		// 返回队首节点值
		return this.#head.value;
	}

	// 清空队列
	clear() {
		this.#head = undefined;
		this.#tail = undefined;
		this.#size = 0;
	}
	// 获取队列长度
	get size() {
		return this.#size;
	}
	// 迭代器方法,这样就可以遍历队列啦
	* [Symbol.iterator]() {
		let current = this.#head;

		while (current) {
			yield current.value;
			current = current.next;
		}
	}
    // 连续出队队列中的所有元素, 循环结束后,队列将为空
	* drain() {
		while (this.#head) {
			yield this.dequeue();
		}
	}
}

const queue = new Queue()

queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
queue.enqueue(4)
queue.enqueue(5)
queue.enqueue(6)
for(let q of queue) {
	console.log(q) // 1 2 3 4 5 6
}
queue.dequeue()
console.log(queue.size) // 5

// 测试drain
for(const value of queue.drain()) {
    console.log('dequeue value', value) // 2 3 4 5 6
}
console.log(queue.size) // 0

4. 总结

平时多用数组来实现队列,链表实现队列也是如此有趣~

如有问题,欢迎指正,thanks

5. 引用

juejin.cn/post/725406...

相关推荐
weixin_382395234 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__4 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.5 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~6 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华6 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子9 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅9 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni9 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学10 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端