数据结构(搁置)

  • JS中的数组是多种数据结构的封装,它存在快慢两种模式,快时数组开辟默认的内存空间,保存的每一项都是指针,所以能够保存不同类型的数据,同时有能实现数组的任意位高速读取的优势。但是当插入或删除时导致的空间不足,会导致需要开辟新的内存空间并整体复制当前值。慢的模式没有了解。
  • 数据结构实际上是数据在内存中的储存方法和读取方法。比如:栈和队列的存储都是一样的,使用一个连续的内存空间。他们的区别在于队列多使用了3个内存空间来保存位置变量,他们能够实现的功能也不一样。
  • 数据结构只是一种理论上的内存使用方式,在不同的语言中其实现方式是不同。数据结构更在乎的是在内存使用的不同方式(应该是结合了长期使用总结的功能需求上总结出来的)
  • 数据结构是为了方便程序访问数据和提高程序性能而使用,就像算法一样,优秀的数据结构能够以指数级提高程序速度
  • 实际在具体的编程实践中,数据结构的优化变得相当弱,更应该把精力关注在代码结构的优化及代码语义化上。(暂时这么认为中?数据结构或许在TypeScript中得到了优化)

栈的特点是只能在某一端添加或删除数据,遵循先进后出的原则

  • 可用于判断是否对称,例如判断括号是否对称
  • 用JS的数组实现栈不会有多余的开销,因为它们都一样,当空间不足时需要重新开辟空间并复制原有数据
var isValid = function (s) {
  let map = {
    '(': -1,
    ')': 1,
    '[': -2,
    ']': 2,
    '{': -3,
    '}': 3
  }
  let stack = []
  for (let i = 0; i < s.length; i++) {
    if (map[s[i]] < 0) {
      stack.push(s[i])
    } else {
      let last = stack.pop()
      if (map[last] + map[s[i]] != 0) return false
    }
  }
  if (stack.length > 0) return false
  return true
};

队列

队列一个线性结构,特点是在某一端添加数据,在另一端删除数据,遵循先进先出的原则。

  • 循环队列,更高效率的队列实现
  • 相对于JS数组,应该是一种更高效率的实现方式。JS中数组在快模式时,头部的出数据会导致数组的拷贝,所以以下队列的方式效率会更高。(可能)
class SqQueue {
  constructor(length) {
    this.queue = new Array(length)
    this.first = 0
    this.last = 0
    this.size = 0
    this.base = length
  }
  enQueue(item) {
    this.queue[this.last] = item
    this.size++
    this.last = (this.last + 1) % this.queue.length
    if (this.first === this.last) {
      this.__resize(this.size + this.base)
    }
  }
  deQueue() {
    if (this.first === this.last) {
      return undefined
    }
    let r = this.queue[this.first]
    this.queue[this.first] = null
    this.first = (this.first + 1) % this.queue.length
    this.size--
    if (this.size <= this.queue.length - 2 * this.base) {
      this.__resize(this.queue.length - this.base)
    }
    return r
  }
  getHeader() {
    if (this.first === this.last) {
      return undefined
    }
    return this.queue[this.first]
  }
  getLength() {
    return this.size
  }
  __resize(length) {
    let q = new Array(length)
    for (let i = 0; i < this.size; i++) {
      q[i] = this.queue[(i + this.first) % this.queue.length]
    }
    this.queue = q
    this.first = 0
    this.last = this.size
  }
}

链表

链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。(使用对象树实现)

  • 链表在删除和插入上的表现优于数组,因为数组需要移动改变位之后的所有数据的位置(其实还受查询插入位影响,数组查询快于链表,插入时数组需要整体移动,JS中数组做了优化,实际综合效率哪个更高不确定)
  • 查询效率低于数组,总要从第一个依次向下查询
  • 能够确定的是链表相对于栈和队列,它在头部添加和删除的表现上最为优秀。
  • 链表和树的内存本质是一样的,只是数据组织结构不同
class Node {
  constructor(v, next) {
    this.value = v
    this.next = next
  }
}
class LinkList {
  constructor() {
    this.size = 0
    this.dummyNode = new Node(null, null)
  }
  __find(header, index, currentIndex) {
    if (index === currentIndex) return header
    return this.__find(header.next, index, currentIndex + 1)
  }
  __addNode(v, index) { // 在某个位置添加,实际是改变这个位置上一项next的指向,及这个新增项next指向这个位置上一项next原本的指向
    if (index < 0 || index > this.size) throw Error('Index error')
    let prev = this.__find(this.dummyNode, index, 0)
    prev.next = new Node(v, prev.next)
    this.size++
    return prev.next
  }
  insertNode(v, index) {
    return this.__addNode(v, index)
  }
  addToFirst(v) {
    return this.__addNode(v, 0)
  }
  addToLast(v) {
    return this.__addNode(v, this.size)
  }
  __checkIndex(index){
    if (index < 0 || index > this.size-1) throw Error('Index error')
  }
  __removeNode(index) { // 获取删除项的前一项,修改它的next为删除项的next,设置删除项的next为null(删除关联让内存释放空间)
    this.__checkIndex(index)
    let prev = this.__find(this.dummyNode, index, 0)
    let node = prev.next
    prev.next = node.next
    node.next = null
    this.size--
    return node
  }
  removeFirstNode() {
    return this.__removeNode(0)
  }
  removeLastNode() {
    return this.__removeNode(this.size-1)
  }
  getNode(index) {
    this.__checkIndex(index)
    return this.__find(this.dummyNode, index, 0).next.value
  }
  getSize() {
    return this.size
  }
}

二叉树

二叉树拥有一个根节点,每个节点至多拥有两个子节点,分别为:左节点和右节点。树的最底部节点称之为叶节点,当一颗树的叶数量数量为满时,该树可以称之为满二叉树。

  • 树本身存在层级结构,能够方便的自上而下或自下而上获取数据

二分搜索树

二分搜索树也是二叉树,拥有二叉树的特性。但是区别在于二分搜索树每个节点的值都比他的左子树的值大,比右子树的值小。

  • 这种存储方式很适合于数据搜索。用于整理一堆数据,方便后面查询比较。
  • 容易获取最大值和最小值
  • 也方便获取排序好的数据,本身结构就带有顺序特点
  • 搜索效率其实和添加顺序还是有关的,如果倒序添加数据,这个二叉树就会变成链表
  • 二分搜索树只是数据结构,用于排序的话数组的sort会快得多
// 创建二叉树

class Node {
  constructor(value) {
    this.value = value
    this.left = null
    this.right = null
  }
}
class BST {
  constructor() {
    this.root = null
    this.size = 0
  }
  _addChild(node, v) {
    if (!node) {
      this.size++
      return new Node(v)
    }
    if (node.value > v) {
      node.left = this._addChild(node.left, v)
    } else if (node.value < v) {
      node.right = this._addChild(node.right, v)
    }
    return node
  }
  addNode(v) {
    this.root = this._addChild(this.root, v)
  }
  getSize() {
    return this.size
  }
}
// 三种深度遍历方法,分别是先序遍历、中序遍历、后序遍历 

  // 先序遍历可用于打印树的结构(似乎并不能很好体现)
  _pre(node) {
    if (node) {
      console.log(node.value)
      this._pre(node.left)
      this._pre(node.right)
    }
  }
  preTraversal() {
    this._pre(this.root)
  }

  // 中序遍历得到排好序的值
  _mid(node) {
    if (node) {
      this._mid(node.left)
      console.log(node.value)
      this._mid(node.right)
    }
  }
  midTraversal() {
    this._mid(this.root)
  }
  
  // 后序遍历可用于先操作子节点再操作父节点的场景(vue钩子mounted先执行子节点的后执行父节点的,保证所有子节点挂载完才挂载父节点)
  _back(node) {
    if (node) {
      this._back(node.left)
      this._back(node.right)
      console.log(node.value)
    }
  }
  backTraversal() {
    this._back(this.root)
  }
// 广度遍历
// 利用之前讲过的队列结构来完成

  breadthTraversal() {
    if (!this.root) return null
    let size = Math.pow(2,Math.ceil(Math.log(this.size + 1)/Math.log(2))-1) // 二叉树的总长度决定了插入队列时占的最大位置(依据等比数列公式获取),使用最大位置能加快插入读取速度,但也更消耗内存。还有待优化
    let q = new SqQueue(size)
    q.enQueue(this.root)
    while (q.getLength()) {
      let n = q.deQueue()
      console.log(n.value)
      if (n.left) q.enQueue(n.left)
      if (n.right) q.enQueue(n.right)
    }
  }
// 获取最大值和最小值
  _getNode(node,key) {
    if (!node[key]) return node.value
    return this._getNode(node[key],key)
  }
  getMin() {
    return this._getNode(this.root,'left')
  }
  getMax() {
    return this._getNode(this.root,'right')
  }
// 向下取整向上取整

  floor(v) {
    let node = this._floor(this.root, v)
    return node ? node.value : null
  }
  _floor(node, v) {
    if (!node) return null
    if (node.value > v) {
      return this._floor(node.left, v)
    }
    if(node.value < v) {
      let right = this._floor(node.right, v) 
      if (right) return right
    }
    return node
  }
  top(v){
    let node = this._top(this.root, v)
    return node ? node.value : null
  }
  _top(node,v){
    if (!node) return null
    if (node.value < v) {
      return this._top(node.right, v)
    }
    if(node.value > v) {
      let left = this._top(node.left, v) 
      if (left) return left
    }
    return node
  }
// 返回第K项

  select(k) {
    let node = this._select(this.root, k)
    return node ? node.value : null
  }
  _select(node, k) {
    if (!node) return null
    let size = node.left ? node.left.size  : 0
    if (size > k) return this._select(node.left, k)
    if (size < k) return this._select(node.right, k - size - 1)
    return node
  }
原文地址:https://www.cnblogs.com/qq3279338858/p/10213192.html