探索ES6数据结构的世界 -「高级前端」

什么是数据结构

数据结构实在计算机中组织和存储数据的一种特殊方式,是的数据可以高效的的别访问或者修改。准确的说,数据结构是数据的集合,表示数据之间的关系,包括作用在数据上的函数或者操作。

为什么我们需要数据结构

  • 数据是计算机科学当中最关键的实体,而数据结构则可以将数据以某种组织形式存储,因此,数据结构的价值不言而喻。
  • 无论你以何种方式解决何种问题,你都需要处理数据——无论是涉及员工薪水、股票价格、购物清单,还是只是简单的电话簿问题。
  • 数据需要根据不同的场景,按照特定的格式进行存储。有很多数据结构能够满足以不同格式存储数据的需求。

常见的八大数据结构

  • 数组: Array
  • 堆栈: Stack
  • 队列:Queue
  • 链表: Linked Lists
  • 树: Trees
  • 图: Graphs
  • 字典树:Trie
  • 散列表(哈希表): Hash Tables

在较高的层次上,基本有三种类型的数据结构:

  • 堆栈和队列类似于数组的结构,仅在项目的插入和删除方式上有所不同。
  • 链表、树、图结构的节点是引用到其他节点。
  • 散列表依赖于三列韩式来保存和定位数据。

在复杂性方面:

  • 堆栈和队列是最简单的,并且可以从中构建链表。
  • 树和图是最复杂的,因为他们扩展了链表的概念。
  • 散列表和字典树需要利用这些数据结构可靠地执行。

效率而言:

  • 链表是记录和存储数据的最佳选择。
  • 哈希表和字典树在搜索和检索数据方面效率最佳。

数组(你可能知道)

数组是最简单的数据结构,基本都能百度到,我们这里举一个例子,有个场景每个函数都运行10000次迭代
如:10000个随机密钥在10000个对象的数组中查找的效率对比图

[
  {
    id: "key0",
    content: "第0次"
  },
  {
    id: "key1",
    content: "第1次"
  },
  {
    id: "key2",
    content: "第2次"
  },
  ...
]

["key284", "key958", "key23", "key625", "key83", "key9", ... ]

在这里插入图片描述

for…in 为什么这么慢?像蜗牛

这个语法是我们常用,但是为啥子这么缓慢。根据是兼职发现比正常情况下慢了9倍还要多一点。why?这是因为for in 语法是第一个能够迭代对象键的javascript语法。循环对象键" { } “与在数组” [ ] "上的换换不同。因为引擎会执行一些额外的工作跟踪已经迭代的属性。

堆栈 Stack

堆栈是元素的集合,可以在顶部添加项目,思考一下常见的几个堆栈是列。

  • 浏览器的历史记录
  • 常见的撤销操作
  • 递归以及其它遍历

根据这些场景,我们总结并解释一下堆栈

  1. 两个原则操作:push和pop;push是将元素添加到数组顶部,为pop是将他们从同一位置上删除。
  2. 遵循“后进,先出”,即LIFO。

堆栈的实现

我们写一个例子,用来颠倒堆栈的顺序;我们分别使用unshift和shift方法代替push和pop。

class Stack {
    constructor(...items) {
        this.reverse = false;
        this.stack = [...items];
    }

    push(...items) {
        return this.reverse
            ? this.stack.unshift(...items)
            : this.stack.push(...items);
    }
    
    pop() {
        return this.reverse ? this.stack.shift() : this.stack.pop();
    }
}

const stack = new Stack(4, 5);
stack.reverse = true;
console.log(stack.push(1, 2, 3) === 5) // true
console.log(stack.stack ===[1, 2, 3, 4, 5]) // true

队列

在计算机科学中,一个队列(queue)是一种特殊类型的抽象数据类型或集合。集合中的实体按顺序保存。
而在前端开发中,最著名的队列使用当属浏览器/NodeJs中 关于宏任务与微任务,任务队列的知识。这里就不再赘述了。
以编程西乡而言,Queue可以描述为:

  • 只要具有两个主要操作的数组:unshift和pop。
  • 遵循先进先出,即FIFO。

队列的实现

我们写个小例子,用来颠倒队列的顺序。

class Queue {
    constructor(...items) {
        this.reverse = false;
        this.queue = [...items];
    }

    enqueue(...items) {
        return this.reverse
            ? this.queue.push(...items)
            : this.queue.unshift(...items);
    }

    dequeue() {
        return this.reverse ? this.queue.shift() : this.queue.pop();
    }
}

链表

与数组一样,链表是按顺序存储数据元素。链表不是保留索引,而是指向其他元素。
在这里插入图片描述

类似于流程图,第一个节点称为头部,最后一个节点称为尾部。一目了然清晰无比。

单链表和双链表:
  • 单链表是表示一系列节点的数据结构,其中每个节点指向列表中的下一个节点。
  • 链表通常需要遍历整个操作列表,因此性能较差。
  • 提高链表性能的一种方法是在每个节点上添加指向列表中上一个节点的第二个指针。
  • 双向链表具有指向其前后元素的节点。
链表的优点:
  • 链接具有常量时间 插入和删除,因为我们可以只更改指针。
  • 与数组一样,链表可以作为堆栈运行。
链表的应用场景(客户端和服务器上都很有用)
  • 在客户端上,像Redux就以链表方式构建其中的逻辑。
  • React 核心算法 React Fiber的实现就是链表。
    在这里插入图片描述

1、React Fiber之前的Stack Reconciler,是自顶向下的递归mount/update,无法中断(持续占用主线程),这样主线程上的布局、动画等周期性任务以及交互响应就无法立即得到处理,影响体验。
2、React Fiber解决过去Reconciler存在的问题的思路是把渲染/更新过程(递归diff)拆分成一系列小任务,每次检查树上的一小部分,做完看是否还有时间继续下一个任务,有的话继续,没有的话把自己挂起,主线程不忙的时候再继续。
3、在服务器上,像Express这样的Web框架也以类似的方式构建其中间件逻辑。当请求被接收时,它从一个中间件管道输送到下一个,直到响应被发出。

单链表的实现

单链表的操作核心方法主要有:

  • push(value) - 在链表的末尾/头部添加一个节点
  • pop() - 从链表的末尾/头部删除一个节点
  • get(index) 返回指定索引处的节点
  • delete(index) - 删除指定索引处的节点
  • isEmpty() - 根据列表长度返回true或false
  • print() - 返回链表的可见表示
class Node {
  constructor(data) {
    this.data = data
    this.next = null
  }
}

class LinkedList {
  constructor() {
    this.head = null
    this.tail = null
    // 长度非必要
    this.length = 0
  }
  push(data) {
    // 创建一个新节点
    const node = new Node(data)
    // 检查头部是否为空
    if (this.head === null) {
      this.head = node
      this.tail = node
    } 
    this.tail.next = node
    this.tail = node
    this.length++
  }
  pop(){
    // 先检查链表是否为空
    if(this.isEmpty()) {
      return null
    } 
    // 如果长度为1
    if (this.head === this.tail) {
      this.head = null
      this.tail = null
      this.length--
      return this.tail
    }
    let node = this.tail
    let currentNode = this.head
    let penultimate
    
    while (currentNode) {
      if (currentNode.next === this.tail) {
        penultimate = currentNode
        break
      }
      currentNode = currentNode.next
    }
    
    penultimate.next = null
    this.tail = penultimate
    this.length --
    return node
  }
  
  get(index){
    // 处理边界条件
    if (index === 0) {
      return this.head
    }
    if (index < 0 || index > this.length) {
      return null
    }
    
    let currentNode = this.head
    let i = 0
    while(i < index) {
      i++
      currentNode = currentNode.next
    }
    return currentNode
    
  }
  delete(index){
    let currentNode = this.head
    
    if (index === 0) {
      let deletedNode
      currentNode.next = this.head
      deletedNode = currentNode
      this.length--
      
      return deletedNode
    }
    
    if (index < 0 || index > this.length) {
      return null
    }
    
    let i = 0
    let previous
    
    while(i < index) {
      i++
      previous = currentNode
      currentNode = currentNode.next
    }
    previous.next = currentNode.next
    this.length--
    return currentNode
  }
  
  isEmpty() {
    return this.length === 0
  }
  print() {
    const list = []
    let currentNode = this.head
    while(currentNode){
      list.push(currentNode.data)
      currentNode = currentNode.next
    }
    return list.join(' => ')
  }
}

未完待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值