JavaScript数据结构学习笔记(二)

330 阅读8分钟

JavaScript数据结构学习笔记(一)

JavaScript数据结构学习笔记(三)

4. 集合

特点

集合是由一组无序唯一(即不能重复)的项组成的。可以将其想象成一个既没有重复元素,也没有顺序概念的数组。

实现

class Set {
  constructor() {
    this.items = {}
  }
  // 添加一个元素
  add(element) {
    if (this.has(element)) {
      return false
    }
    // 重点注意,这里是和栈/队列等数据结构区别最为明显的地方
    // 另外两种数据结构使用下标作为key,Set使用element本身作为key
    // 对象不允许同一个key指向不同的value,确保了集合里元素的唯一性
    this.items[element] = element
    return true
  }
  // 删除元素
  delete(element) {
    if (!this.has(element)) {
      return undefined
    }
    const result = this.items[element]
    delete this.items[element]
    return result
  }
  // set中是否有element元素
  has(element) {
    return this.items.hasOwnProperty(element)
  }
  // 获取集合长度
  size() {
    return Object.keys(this.items).length
  }
  // 获取集合所有element值
  values() {
    return Object.values(this.items)
  }
  // 清空集合
  clear() {
    this.items = {}
  }
}

实际使用场景

并集

function union(setA, setB) {
  const unionSet = new Set()
  setA.values().forEach(value => unionSet.add(value))
  setB.values().forEach(value => unionSet.add(value))
  return unionSet
}

交集

function intersection(setA, setB) {
  const intersectionSet = new Set()
  // 找到size更小的集合,减少循环次数
  const sizeRelation = setA.size() > setB.size()
  const [smallerSet, biggerSet] = [
    sizeRelation ? setB : setA,
    sizeRelation ? setA : setB,
  ]
  smallerSet.values().forEach(value => {
    if (biggerSet.has(value)) {
      intersectionSet.add(value)
    }
  })
  return intersectionSet
}

差集

function difference(setA, setB) {
  const differenceSet = new Set()
  setA.values().forEach(value => {
    if (!setB.has(value)) {
      differenceSet.add(value)
    }
  })
  return differenceSet
}

是否是子集

function isSubsetOf(setA, setB) {
  if (setA.size() > setB.size()) {
    return false
  }
  let isSubset = setA.values.every(value => {
    return setB.has(value)
  })
  return isSubset
}

通过ES6 Set数据结构实现

let a = new Set([1, 2, 3])
let b = new Set([4, 3, 2])

// 并集
let union = new Set([...a, ...b])
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)))
// set {2, 3}

// 差集
let difference = new Set([...a].filter(x => !b.has(x)))

5. 字典和散列表

特点

  • 字典集合很相似,都是由一组无序唯一的项组成的;之间的区别是,集合[value, value]的形式存储元素,字典则是以[key, value]的形式进行存储。
  • 散列表字典的一种实现,通过散列算法尽可能快地在数据结构中找到一个值。

实现

字典

class ValuePair {
  constructor(key, value) {
    this.key = key
    this.value = value
  }
  toString() {
    return `${this.key}: ${this.value}`
  }
}

class Dictionary {
  constructor() {
    this.table = {}
  }
  set(key, value) {
    if (!key || !value) {
      return false
    }
    // 通过ValuePair保存key value
    this.table[key] = new ValuePair(key, value)
  }
  get(key) {
    if (!this.hasKey(key)) {
      return undefined
    }
    return this.table[key]
  }
  remove(key) {
    if (!this.hasKey(key)) {
      return undefined
    }
    const result = this.get(key)
    delete this.table[key]
    return result
  }
  // 是否已有这个键
  hasKey(key) {
    return !!this.table[key]
  }
  // keys和values取出的ValuePair实例,需要map获取具体的值
  keys() {
    return Object.keys(this.table)
  }
  values() {
    return Object.values(this.table).map(valuePair => valuePair.value)
  }
  size() {
    return Object.keys(this.table).length
  }
  isEmpty() {
    return this.size() === 0
  }
  clear() {
    this.table = {}
  }
  forEach(callbackFn) {
    const valuePairs = Object.values(this.table)
    for (let i = 0; i < valuePairs.length; i++) {
      // 执行cb函数
      const result = callbackFn(valuePairs[i].key, valuePairs[i].value)
      if (result === false) {
        break // 如果cb函数返回false,终止循环
      }
    }
  }
  toString() {
    if (this.isEmpty()) {
      return ''
    }
    const values = this.values()
    const result = ''
    for (let i = 0; i < values.length; i++) {
      result += `${values[i]}${i === values.length - 1 ? '' : ','}`
    }
    return result
  }
}

散列表

定义散列函数,常见的有三种方法是lose losedjb2sdbm,具体实现可以参考这篇文章

// 定义djb2散列函数
function djb2HashCode(key) {
  // 初始化一个hash值并赋值为一个质数(目前大多数都是用5381)
  let hash = 5381
  for (let i = 0; i < key.length; i++) {
    hash = hash * 33 + key.charCodeAt(i)
  }
  // 将使用相加的和与另一个质数相除
  return hash % 1013
}

即使通过散列函数djb2key进行处理,也有可能造成返回值相同,后一个相同的key覆盖前面value的情况,这里采用分离链接的方式来解决,既为散列表的每一个位置创建一个链表并将元素存储在里面

class HashTable {
  constructor() {
    this.table = {}
  }
  put(key, value) {
    if (!key || !value) {
      return false
    }
    // 通过散列函数获取当前key在table中的位置
    const position = djb2HashCode(key)
    if (!this.table[position]) {
      // 如果当前位置没有值,则生成一个链表实例
      this.table[position] = new LinkedList()
    }
    // 为当前位置的链表添加一个元素
    this.table[position].push(new ValuePair(key, value))
  }
  get(key) {
    const position = djb2HashCode(key)
    const linkedList = this.table[position]
    // 判断table当前位置是否有值,该位置的链表是否为空
    if (!this.table[position] || linkedList.isEmpty()) {
      return undefined
    }
    // 遍历链表,对比key
    let current = linkedList.getElementAt(0)
    while (current) {
      if (current.element.key === key) {
        return current.element.value
      }
      current = current.next
    }
  }
  remove(key) {
    const position = djb2HashCode(key)
    const linkedList = this.table[position]
    if (!this.table[position] || linkedList.isEmpty()) {
      return undefined
    }
    let current = linkedList.getElementAt(0)
    while (current) {
      if (current.element.key === key) {
        const result = current.element.value
        linkedList.remove(current.element)
        return result
      }
      current = current.next
    }
  }
}

6. 树

特点

  • 是包含一系列存在父子关系的节点的数据结构;位于树顶部的节点叫作根节点,至少有一个子节点的节点称为内部节点,没有子元素的节点称为外部节点叶子节点。每个节点都包含一个属性,深度,节点的深度取决于它的祖先节点的数量;树的高度取决于所有节点深度的最大值
  • 二叉树中的节点最多只能有两个子节点:一个是左侧子节点,另一个是右侧子节点。
  • 二叉搜索树(Binary Search Tree)是二叉树的一种,但是只允许你在左侧节点存储(比父节点)小的值, 在右侧节点存储(比父节点)大的值。
  • 自平衡树(Adelson-Velskii-Land)是在BST的基础上增加了平衡因子概念,每次在添加或者移除节点后,都会实现自平衡(即任意节点下左右子树高度差不超过±1)

实现

二叉搜索树BST

const Compare = {
  LESS_THAN: -1,
  BIGGER_THAN: 1,
}

function defaultCompare(a, b) {
  if (a === b) {
    return 0
  }
  return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN
}

class Node {
  constructor(key) {
    this.key = key
    this.left = null
    this.right = null
  }
}

class BinarySearchTree {
  constructor(compareFn = defaultCompare) {
    this.root = null
    this.compareFn = compareFn
  }
  insert(key) {
    if (this.root === null) {
      this.root = new Node(key)
    } else {
      // 从root开始向下查找合适的位置并添加key
      this.insertNode(this.root, key)
    }
  }
  insertNode(node, key) {
    if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
      // 由于BST左侧子节点小于父节点的特点,如果比较结果是小于的话,递归朝左侧寻找
      if (node.left === null) {
        node.left = new Node(key)
      } else {
        this.insertNode(node.left, key)
      }
    } else {
      // 反之向右侧寻找
      if (node.right === null) {
        node.right = new Node(key)
      } else {
        this.insertNode(node.right, key)
      }
    }
  }
  // 中序遍历,以上行顺序(从小到大)访问BST所有节点
  inOrderTraverse(callback) {
    this.inOrderTraverseNode(this.root, callback)
  }
  inOrderTraverse(node, callback) {
    if (node !== null) {
      // 递归调用leftNode rightNode
      this.inOrderTraverseNode(node.left, callback)
      callback(node.key)
      this.inOrderTraverseNode(node.right, callback)
    }
  }
  // 先序遍历,以父级优先于后代的顺序访问
  preOrderTraverse(callback) {
    this.preOrderTraverseNode(this.node, callback)
  }
  preOrderTraverseNode(node, callback) {
    callback(node.key)
    this.preOrderTraverseNode(node.left, callback)
    this.preOrderTraverseNode(node.right, callback)
  }
  // 后序遍历,和先序遍历相反,后代优先于父级的顺序
  postOrderTraverse(callback) {
    this.postOrderTraverseNode(this.node, callback)
  }
  postOrderTraverseNode(node, callback) {
    this.postOrderTraverseNode(node.left, callback)
    this.postOrderTraverseNode(node.right, callback)
    callback(node.key)
  }
  // 搜索最小值,访问最左侧节点
  min() {
    return this.minNode(this.root)
  }
  // 可以根据node取当前树的最小值
  minNode(node) {
    let current = node
    while (current !== null && current.left !== null) {
      current = current.left
    }
    return current
  }
  // 最大值,访问最右侧节点
  max() {
    return this.maxNode(this.root)
  }
  maxNode(node) {
    let current = node
    while (current !== null && current.right !== null) {
      current = current.right
    }
    return current
  }
  // 搜索一个特定的值
  search(key) {
    return this.searchNode(this.root, key)
  }
  searchNode(node, key) {
    if (node === null) {
      return undefined
    }
    // 比较key和node.key 向左或者右递归查找
    if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
      return this.searchNode(node.left, key)
    } else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
      return this.searchNode(node.rigth, key)
    } else {
      return node
    }
  }
  // 移除节点
  remove(key) {
    // removeNode返回值是一个node,需要重新为整棵树赋值
    this.root = this.removeNode(this.root, key)
  }
  removeNode(node, key) {
    if (node === null) {
      return node
    }
    // 这里注意,对比后需要重新为需要改变的一边赋值,这也是为什么remove函数要给root重新赋值的原因
    // 重新赋值后返回node节点
    if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
      node.left = this.removeNode(node.left, key)
      return node
    } else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
      node.right = this.removeNode(node.rigth, key)
      return node
    } else {
      // 第一种情况,节点本身就是叶子节点
      if (node.left === null && node.right === null) {
        // 将node清空,即为删除,直接返回即可
        node = null
        return node
      } else {
        // 第二种情况,有left或者right节点,返回该节点
        if (node.left !== null) {
          node = node.left
          return node
        } else if (node.right !== null) {
          node = node.right
          return node
        }
      }
      // 第三种情况,node是内部节点,且left right都有子节点
      // 可以将left子树中的最大值取出来(或者right子树最小值),作为node节点的值
      const rightMinNode = this.minNode(node.right)
      node.key = rightMinNode.key
      // 删除掉叶子节点(重新调用removeNode函数)
      node.right = this.removeNode(node.right, rightMinNode.key)
      /**
       * const leftMaxNode = this.maxNode(node.left)
       * node.key = leftMaxNode.key
       * node.left = this.removeNode(node.left, leftMaxNode.key)
       */
      return node
    }
  }
}

自平衡树AVL

// 定义平衡因子常量
const BalanceFactor = {
  UNBALANCED_RIGHT: 1,
  SLIGHTLY_UNBALANCED_RIGHT: 2,
  BALANCED: 3,
  SLIGHTLY_UNBALANCED_LEFT: 4,
  UNBALANCED_LEFT: 5,
}

class AVL extends BinarySearchTree {
  constructor() {
    super()
  }
  // 计算node节点高度
  getNodeHeight(node) {
    if (node === null) {
      return -1
    }
    // 递归查找左右子树的高度,因为node是null的时候返回-1,因此结果+1
    return (
      Math.max(this.getNodeHeight(node.left), this.getNodeHeight(node.right)) +
      1
    )
  }
  /**
   * 计算平衡因子
   * -1 0 1 即为平衡
   * -2 2 则需要调整
   */
  getBalanceFactor(node) {
    const heightDifference =
      this.getNodeHeight(node.left) - this.getNodeHeight(node.right)
    switch (heightDifference) {
      case -2:
        return BalanceFactor.UNBALANCED_RIGHT
      case -1:
        return BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT
      case 1:
        return BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
      case 2:
        return BalanceFactor.UNBALANCED_LEFT
      default:
        return BalanceFactor.BALANCED
    }
  }
  /**
   * 向右单旋转
   * 节点的左侧子节点的高度大于右侧子节点的高度,且左侧子节点也是平衡或者左侧较重的
   */
  rotateLL(node) {
    const temp = node.left // 左子树节点
    node.left = temp.right // 将node的左子树节点设置为左子树节点的右节点(即左子树的右树作为node节点的左树)
    temp.right = node // 将node作为左子树节点的右数
    return temp // 返回原来的左子树节点
  }
  // 向左单旋转,和rotateLL相反
  rotateRR(node) {
    const temp = node.right
    node.right = temp.left
    temp.left = node
    return temp
  }
  /**
   * 向右双旋转
   * 节点的左侧子节点的高度大于右侧子节点的高度,且左侧子节点也是平衡或者右侧较重的
   * 1. 先对node的左子树进行左旋转
   * 2. 再对整棵树进行右旋转
   */
  rotateLR(node) {
    node.left = this.rotateRR(node.left)
    return this.rotateLL(node)
  }
  // 向左双旋转
  rotateRL(node) {
    node.right = this.rotateLL(node.right)
    return this.rotateRR(node)
  }
  // 重写insert方法,主要是判断平衡因子
  insert(key) {
    this.root = this.insertNode(this.root, key)
  }
  insertNode(node, key) {
    if (node === null) {
      return new Node(key)
    } else if (this.compareFn(key, node.key) === Compare.LESS_THAN) {
      node.left = this.insertNode(node.left, key) // 由于一开始对node进行了!null判断,这里不需要像BST树一样再判断一次
    } else if (this.compareFn(key, node.key) === Compare.BIGGER_THAN) {
      node.right = this.insertNode(node.right, key)
    } else {
      return node // 重复的值
    }
    // 判断是否平衡
    const balanceFactor = this.getBalanceFactor(node)
    if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
      // 左侧高度比右侧高,且左子树左边较重
      if (this.compareFn(key, node.left.key) === Compare.LESS_THAN) {
        return this.rotateLL(node)
      } else {
        return this.rotateLR(node)
      }
    }
    if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
      if (this.compareFn(key, node.right.key) === Compare.BIGGER_THAN) {
        return this.rotationRR(node)
      } else {
        return this.rotationRL(node)
      }
    }
  }
  removeNode(node, key) {
    node = super.removeNode(node, key) // 删除方法沿用BST
    if (node === null) {
      return node
    }
    // 和insertNode方法一致,操作后需要检查平衡因子
    const balanceFactor = this.getBalanceFactor(node.left)
    if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
      const balanceFactorLeft = this.getBalanceFactor(node) // 左子树的平衡因子
      if (balanceFactorLeft === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT) {
        return this.rotationLL(node)
      } else if (
        balanceFactorLeft === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT
      ) {
        return this.rotationLR(node.left)
      }
    } else if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
      const balanceFactorRight = this.getBalanceFactor(node.right) // 右子树的平衡因子
      if (balanceFactorRight === BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT) {
        return this.rotationRR(node)
      } else if (
        balanceFactorRight === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT
      ) {
        return this.rotationRL(node.right)
      }
    }
    return node
  }
}