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...

相关推荐
日记成书14 分钟前
【HTML 基础教程】HTML 表格
前端·html
木木黄木木18 分钟前
HTML5贪吃蛇游戏开发经验分享
前端·html·html5
无名之逆24 分钟前
hyperlane:Rust HTTP 服务器开发的不二之选
服务器·开发语言·前端·后端·安全·http·rust
李鸿耀28 分钟前
前端包管理工具演进史:从 npm 到 pnpm 的技术革新
前端·面试
麓殇⊙30 分钟前
前端基础知识汇总
前端
MariaH33 分钟前
邂逅jQuery库
前端
Jenlybein38 分钟前
学完 Vue3 记不牢?快来看这篇精炼Vue3笔记复习一下 [ Route 篇 ]
前端·vue.js
页面魔术41 分钟前
[译]专访尤雨溪: 2025年有什么计划?
前端·vue.js·vite
zhangxiao43 分钟前
Vite项目打包生成dist.zip方法
前端
Version1 小时前
深入理解JavaScript 中的 this
前端