js实现常见数据结构

139 阅读4分钟

先进后出,可以想象为碟子一个个叠起来,拿走时从上面拿,也就是最先叠进来的在最下面,最后拿出去

/*
栈
先进后出,可以想象为碟子一个个叠起来,拿走时从上面拿,也就是最先叠进来的在最下面,最后拿出去
*/
class Stack {
  constructor() {
    this.stack = []
  }
  push (item) {
    this.stack.push(item)
  }
  pop () {
    return this.stack.pop()
  }
  print () {
    console.log(this.stack)
  }
}
const stack = new Stack()
stack.push(1)
stack.push(2)
stack.push(3)
stack.print() // [1, 2, 3]

stack.pop()
stack.print() // [1, 2]

队列

先进先出,可以想象为排队打饭,先排的人先打饭

/*
队列
先进先出,可以想象为排队打饭,先排的人先打饭
*/
class Queue {
  constructor() {
    this.queue = []
  }
  enqueue (item) {
    this.queue.push(item)
  }
  dequeue () {
    return this.queue.shift()
  }
  print () {
    console.log(this.queue)
  }
}
const queue = new Queue()

queue.enqueue(1)
queue.enqueue(2)
queue.enqueue(3)
queue.print() // [1, 2, 3]

queue.dequeue()
queue.print() // [2, 3]

优先队列

设置优先级,按优先级插入,先插先出,可以想象为银行排队时,vip 可以插在前面普通用户,vvip 又可以插在 vip 前面

/*
优先队列
设置优先级,按优先级插入,先插先出,可以想象为银行排队时,vip 可以插在前面普通用户,vvip 又可以插在 vip 前面
*/
class PriorityQueue {
  constructor() {
    this.priorityQueue = []
  }

  enqueue(value, priority) {
    const enqueueItem = { value, priority }

    // 队列为空直接 push
    if (!this.priorityQueue.length) {
      this.priorityQueue.push(enqueueItem)
    } else {
      // 找到第一个 priority 比当前 priority 大的元素的 index ,插入此位置
      const index = this.priorityQueue.findIndex(item => enqueueItem.priority < item.priority)
      if (index > -1) {
        this.priorityQueue.splice(index, 0, enqueueItem)
      } else {
        this.priorityQueue.push(enqueueItem)
      }
    }
  }

  dequeue() {
    return this.priorityQueue.shift()
  }
  print() {
    console.log(this.priorityQueue)
  }
}

const priorityQueue = new PriorityQueue()
priorityQueue.enqueue('小明', 1)
priorityQueue.enqueue('小强', 3)
priorityQueue.enqueue('小花', 1)
priorityQueue.enqueue('小杰', 2)
priorityQueue.print() // [{"value":"小明","priority":1},{"value":"小花","priority":1},{"value":"小杰","priority":2},{"value":"小强","priority":3}]

链表

链表是指每个元素包含 值 和 指向下一个元素的指针。数组的缺点在于插入一个元素时,其后的元素都要向后移一位,而链表插入一个元素时,不需要按顺序插进去,只需指针指向正确就行,但是链表查找元素时需要循环

/*
链表
链表是指每个元素包含 值 和 指向下一个元素的指针。数组的缺点在于插入一个元素时,其后的元素都要向后移一位,而链表插入一个元素时,不需要按顺序插进去,只需指针指向正确就行,但是链表查找元素时需要循环
*/

// 一个节点包含 值 和 链接下一个节点的指针
class Node {
  constructor(value) {
    this.value = value
    this.next = null
  }
}
class LinkedList {
  constructor() {
    // 设置 头 和 尾 节点 ,设置 尾 可以更容易 append
    this.head = null
    this.tail = this.head
    this.length = 0
  }

  // 尾部插入
  append(value) {
    const newNode = new Node(value)
    // 如果 头 是空的 直接赋值给头,否则赋值给 现有的尾的next 并且 尾节点更新为插入节点
    if (!this.head) {
      this.head = newNode
      this.tail = newNode
    } else {
      this.tail.next = newNode
      this.tail = newNode
    }
    this.length++
  }

  // 头部插入
  prepend(value) {
    const newNode = new Node(value)
    newNode.next = this.head
    this.head = newNode
    this.length++
  }

  // 插入 当前元素 就是找到插入位置 之前 和 之后 的元素,将 之前的元素next 指向 当前元素,将 当前元素next 指向 之后的元素
  insert(value, index) {
    const node = new Node(value)

    const { prevNode, nextNode } = this.getPrevNextNodes(index)
    prevNode.next = node
    node.next = nextNode

    this.length++
  }

  getPrevNextNodes(index) {
    let count = 0;
    let prevNode = this.head;
    let nextNode = prevNode.next;

    // 其实就是从 0 开始向前找,找到 目标index 之前那个元素作为 prevNode ,prevNode.next 作为之后那个元素,将目标元素插入这俩元素之间
    while (count < index - 1) {
      prevNode = prevNode.next;
      nextNode = prevNode.next;
      count++;
    }

    return {
      prevNode,
      nextNode
    }
  }

  // 不断查找 prevNode 和 curNode ,让 curNode 的 next 指向 prevNode
  reverse() {
    let prevNode = null;
    let curNode = this.head;
    this.tail = curNode;

    while (curNode !== null) {
      let nextNode = curNode.next;
      curNode.next = prevNode;

      prevNode = curNode
      curNode = nextNode
    }
    this.head = prevNode
  }

  // 当前元素 之前的元素 的 next 改为 当前元素之后的元素
  remove(index) {
    let { prevNode, nextNode: currentNode } = this.getPrevNextNodes(index)
    prevNode.next = currentNode.next
    this.length--
  }

  getNode(index) {
    let curNode = this.head
    let count = 0

    while (count < index) {
      curNode = curNode.next
      count++
    }
    return curNode
  }
}

const linkedList = new LinkedList()
linkedList.append('小明')
linkedList.append('小强')
linkedList.insert('小一', 1)
linkedList.remove(1)
console.log(linkedList) // {"head":{"value":"小明","next":{"value":"小强","next":null}},"tail":{"value":"小强","next":null},"length":2}
console.log(linkedList.getNode(1)) // { "value": "小强", "next": null }
linkedList.reverse()
console.log(linkedList)
/* 双向链表 */

/* 循环链表 */

集合

无重复数据的数组,es6 已实现,可通过对象模拟实现

/*
集合
无重复数据的数组,es6 已实现,可通过对象模拟实现
*/
class Set {
  constructor() {
    this.set = {}
  }

  add(v) {
    if (!this.has(v)) {
      this.set[v] = v
    }
  }

  remove() {
    if (this.has(v)) {
      delete this.set[v]
    }
  }

  has(v) {
    return this.set.hasOwnProperty(v)
  }

  size() {
    return this.value.length
  }

  get values() {
    return Object.keys(this.set)
  }

  // 并集
  unionSet(otherSet) {
    const unionSet = new Set()
    Array.prototype.concat(otherSet.values, this.values).forEach(v => {
      unionSet.add(v)
    })
    return unionSet.values
  }

  // 交集
  intersection(otherSet) {
    const intersection = new Set()
    otherSet.values.forEach(v => {
      if (this.has(v)) {
        intersection.add(v)
      }
    })
    return intersection.values
  }

  // 差集
  difference(otherSet) {
    const difference = new Set()
    this.values.forEach(v => {
      if (!otherSet.has(v)) {
        difference.add(v)
      }
    })
    return difference.values
  }

  // 子集
  subset(otherSet) {
    if (this.size > otherSet.size) return false
    return !this.values.some(v => !otherSet.has(v))
  }
}
const set = new Set()
set.add(1)
set.add(1)
set.add(2)
set.add(3)
console.log('set', set.values) // [1, 2, 3]

const set2 = new Set()
set2.add(3)
set2.add(4)
set2.add(4)
console.log('set2', set2.values) // ["3","4"]

const union = set.unionSet(set2)
console.log('并集', union) // ["1","2","3","4"]

const intersection = set.intersection(set2)
console.log('交集', intersection) // ["3"]

const difference = set.difference(set2)
console.log('差集', difference) // ["1","2"]

const set3 = new Set()
set3.add(1)
set3.add(2)
set3.add(3)
set3.add(4)
console.log('set 是否 set2 子集', set.subset(set2)) // false
console.log('set 是否 set3 子集', set.subset(set3)) // true

二叉树

二叉树是节点最多只能有两个的树结构,一个左侧一个右侧,左侧存比父节点小的值,右侧存比父节点大的值 二叉树删除很麻烦,如果涉及频繁删除不适合使用

1. 先序遍历

对于二叉树中的任意一个节点,先打印该节点,然后是它的左子树,最后右子树

2. 中序遍历

对于二叉树中的任意一个节点,先打印它的左子树,然后是该节点,最后右子树

3. 后序遍历

对于二叉树中的任意一个节点,先打印它的左子树,然后是右子树,最后该节点

/*
二叉树
二叉树是节点最多只能有两个的树结构,一个左侧一个右侧,左侧存比父节点小的值,右侧存比父节点大的值
二叉树删除很麻烦,如果涉及频繁删除不适合使用
*/
class Node {
  constructor(key) {
    this.key = key
    this.left = null
    this.right = null
  }
}

class BinarySearchTree {
  constructor() {
    this.root = null
  }

  insert(key) {
    const newNode = new Node(key)
    const insertNode = (node, newNode) => {
      if (newNode.key < node.key) {
        if (node.left === null) {
          node.left = newNode
        } else {
          insertNode(node.left, newNode)
        }
      } else {
        if (node.right === null) {
          node.right = newNode
        } else {
          insertNode(node.right, newNode)
        }
      }
    }
    if (!this.root) {
      this.root = newNode
    } else {
      insertNode(this.root, newNode)
    }
  }

  // 遍历有点难理解,主要是方法执行完才会继续向下执行,debugger 看看调用栈会比较清楚
  // 中序遍历
  inOrderTraverse(callback) {
    const inOrderTraverseNode = (node, callback) => {
      if (node !== null) {
        inOrderTraverseNode(node.left, callback)
        callback(node.key)
        inOrderTraverseNode(node.right, callback)
      }
    }
    inOrderTraverseNode(this.root, callback)
  }

  // 先序遍历
  preOrderTraverse(callback) {
    const preOrderTraverseNode = (node, callback) => {
      if (node !== null) {
        callback(node.key)
        preOrderTraverseNode(node.left, callback)
        preOrderTraverseNode(node.right, callback)
      }
    }
    preOrderTraverseNode(this.root, callback)
  }

  // 后序遍历
  postOrderTraverse(callback) {
    const postOrderTraverseNode = (node, callback) => {
      if (node !== null) {
        postOrderTraverseNode(node.left, callback)
        postOrderTraverseNode(node.right, callback)
        callback(node.key)
      }
    }
    postOrderTraverseNode(this.root, callback)
  }

  // 有 left 节点 则继续找下去,没有则返回自身
  min(node) {
    const minNode = node => {
      return node ? (node.left ? minNode(node.left) : node) : null
    }
    return minNode(node || this.root)
  }

  // 有 right 节点 则继续找下去,没有则返回自身
  max(node) {
    const maxNode = node => {
      return node ? (node.right ? maxNode(node.right) : node) : null
    }
    return maxNode(node || this.root)
  }

  search(key) {
    const searchNode = (node, key) => {
      if (node === null) return false
      if (node.key === key) return node
      return searchNode((key < node.key) ? node.left : node.right, key)
    }
    return searchNode(root, key)
  }

  // 删除叶节点,或者只有一个子节点的节点,有两个子节点的情况不实现
  /* 删除节点的思路:
    删除叶节点:
        叶节点比其父节点小,则将父节点的 left 赋值为 null;叶节点比父节点大,则将父节点的 right 赋值为 null
    删除只有一个子节点的节点:
        该节点比父节点小:则将父节点的 left 赋值为该节点唯一的子节点;比父节点大,则将父节点的 right 赋值为该节点唯一的子节点
  */
  remove(key) {
    const removeNode = (node, key) => {
      if (node === null) return null
      if (key === node.key) {
        if (node.left === null && node.right === null) {
          return null
        }
        if (node.left === null) {
          return node.right
        }
        if (node.right === null) {
          return node.left
        }
      } else if (key < node.key) {
        node.left = removeNode(node.left, key)
        return node
      } else {
        node.right = removeNode(node.right, key)
        return node
      }
    }
    removeNode(this.root, key)
  }
}
var bst = new BinarySearchTree();
bst.insert(5);
bst.insert(3);
bst.insert(7);
bst.insert(2);
bst.remove(3)
bst.inOrderTraverse((v) => {
  console.log(v)
}) // 2 5 7