Skip to content
On this page

数据结构

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

实现

每种数据结构都可以用很多种方式来实现,其实可以把栈看成是数组的一个子集,所以这里使用数组来实现

class Stack {
  constructor() {
    this.stack = []
  }
  push(item) {
    this.stack.push(item)
  }
  pop() {
    this.stack.pop()
  }
  peek() {
    return this.stack[this.getCount() - 1]
  }
  getCount() {
    return this.stack.length
  }
  isEmpty() {
    return this.getCount() === 0
  }
}

队列

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

链表

链表是一个线性结构,同时也是一个天然的递归结构。链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

单向链表

class LinkedList {
  // 初始链表长度为 0
  length = 0

  // 初始 head 为 null,head 指向链表的第一个节点
  head = null

  // 内部类(链表里的节点 Node)
  Node = class {
    data
    next = null

    constructor(data) {
      this.data = data
    }
  }

  // ------------ 链表的常见操作 ------------ //

  // append() 往链表尾部追加数据
  append(data) {
    // 1、创建新节点
    const newNode = new this.Node(data)

    // 2、追加新节点
    if (this.length === 0) {
      // 链表长度为 0 时,即只有 head 的时候
      this.head = newNode
    } else {
      // 链表长度大于 0 时,在最后面添加新节点
      let currentNode = this.head

      // 当 currentNode.next 不为空时,
      // 循序依次找最后一个节点,即节点的 next 为 null 时
      while (currentNode.next !== null) {
        currentNode = currentNode.next
      }

      // 最后一个节点的 next 指向新节点
      currentNode.next = newNode
    }

    // 3、追加完新节点后,链表长度 + 1
    this.length++
  }

  // insert() 在指定位置(position)插入节点
  insert(position, data) {
    // position 新插入节点的位置
    // position = 0 表示新插入后是第一个节点
    // position = 1 表示新插入后是第二个节点,以此类推

    // 1、对 position 进行越界判断,不能小于 0 或大于链表长度
    if (position < 0 || position > this.length) return false

    // 2、创建新节点
    const newNode = new this.Node(data)

    // 3、插入节点
    if (position === 0) {
      // position = 0 的情况
      // 让新节点的 next 指向 原来的第一个节点,即 head
      newNode.next = this.head

      // head 赋值为 newNode
      this.head = newNode
    } else {
      // 0 < position <= length 的情况

      // 初始化一些变量
      let currentNode = this.head // 当前节点初始化为 head
      let previousNode = null // head 的 上一节点为 null
      let index = 0 // head 的 index 为 0

      // 在 0 ~ position 之间遍历,不断地更新 currentNode 和 previousNode
      // 直到找到要插入的位置
      while (index++ < position) {
        previousNode = currentNode
        currentNode = currentNode.next
      }

      // 在当前节点和当前节点的上一节点之间插入新节点,即它们的改变指向
      newNode.next = currentNode
      previousNode.next = newNode
    }

    // 更新链表长度
    this.length++
    return newNode
  }

  // getData() 获取指定位置的 data
  getData(position) {
    // 1、position 越界判断
    if (position < 0 || position >= this.length) return null

    // 2、获取指定 position 节点的 data
    let currentNode = this.head
    let index = 0

    while (index++ < position) {
      currentNode = currentNode.next
    }

    // 3、返回 data
    return currentNode.data
  }

  // indexOf() 返回指定 data 的 index,如果没有,返回 -1。
  indexOf(data) {
    let currentNode = this.head
    let index = 0

    while (currentNode) {
      if (currentNode.data === data) {
        return index
      }
      currentNode = currentNode.next
      index++
    }

    return -1
  }

  // update() 修改指定位置节点的 data
  update(position, data) {
    // 涉及到 position 都要进行越界判断
    // 1、position 越界判断
    if (position < 0 || position >= this.length) return false

    // 2、痛过循环遍历,找到指定 position 的节点
    let currentNode = this.head
    let index = 0
    while (index++ < position) {
      currentNode = currentNode.next
    }

    // 3、修改节点 data
    currentNode.data = data

    return currentNode
  }

  // removeAt() 删除指定位置的节点
  removeAt(position) {
    // 1、position 越界判断
    if (position < 0 || position >= this.length) return null

    // 2、删除指定 position 节点
    let currentNode = this.head
    if (position === 0) {
      // position = 0 的情况
      this.head = this.head.next
    } else {
      // position > 0 的情况
      // 通过循环遍历,找到指定 position 的节点,赋值到 currentNode

      let previousNode = null
      let index = 0

      while (index++ < position) {
        previousNode = currentNode
        currentNode = currentNode.next
      }

      // 巧妙之处,让上一节点的 next 指向到当前的节点的 next,相当于删除了当前节点。
      previousNode.next = currentNode.next
    }

    // 3、更新链表长度 -1
    this.length--

    return currentNode
  }

  // remove() 删除指定 data 的节点
  remove(data) {
    this.removeAt(this.indexOf(data))
  }

  // isEmpty() 判断链表是否为空
  isEmpty() {
    return this.length === 0
  }

  // size() 获取链表的长度
  size() {
    return this.length
  }

  // toString() 链表数据以字符串形式返回
  toString() {
    let currentNode = this.head
    let result = ""

    // 遍历所有的节点,拼接为字符串,直到节点为 null
    while (currentNode) {
      result += currentNode.data + " "
      currentNode = currentNode.next
    }

    return result
  }
}

双向链表

class DoublyLinkedList extends LinkedList {
  constructor() {
    super()
    this.tail = null
  }

  // ------------ 链表的常见操作 ------------ //
  // append(element) 往双向链表尾部追加一个新的元素
  // 重写 append()
  append(element) {
    // 1、创建双向链表节点
    const newNode = new DoublyNode(element)

    // 2、追加元素
    if (this.head === null) {
      this.head = newNode
      this.tail = newNode
    } else {
      // !!跟单向链表不同,不用通过循环找到最后一个节点
      // 巧妙之处
      this.tail.next = newNode
      newNode.prev = this.tail
      this.tail = newNode
    }

    this.length++
  }

  // insert(position, data) 插入元素
  // 重写 insert()
  insert(position, element) {
    // 1、position 越界判断
    if (position < 0 || position > this.length) return false

    // 2、创建新的双向链表节点
    const newNode = new DoublyNode(element)

    // 3、判断多种插入情况
    if (position === 0) {
      // 在第 0 个位置插入

      if (this.head === null) {
        this.head = newNode
        this.tail = newNode
      } else {
        //== 巧妙之处:相处腾出 this.head 空间,留个 newNode 来赋值 ==//
        newNode.next = this.head
        this.head.perv = newNode
        this.head = newNode
      }
    } else if (position === this.length) {
      // 在最后一个位置插入

      this.tail.next = newNode
      newNode.prev = this.tail
      this.tail = newNode
    } else {
      // 在 0 ~ this.length 位置中间插入

      let targetIndex = 0
      let currentNode = this.head
      let previousNode = null

      // 找到要插入位置的节点
      while (targetIndex++ < position) {
        previousNode = currentNode
        currentNode = currentNode.next
      }

      // 交换节点信息
      previousNode.next = newNode
      newNode.prev = previousNode

      newNode.next = currentNode
      currentNode.prev = newNode
    }

    this.length++

    return true
  }

  // getData() 继承单向链表
  getData(position) {
    return super.getData(position)
  }

  // indexOf() 继承单向链表
  indexOf(data) {
    return super.indexOf(data)
  }

  // removeAt() 删除指定位置的节点
  // 重写 removeAt()
  removeAt(position) {
    // 1、position 越界判断
    if (position < 0 || position > this.length - 1) return null

    // 2、根据不同情况删除元素
    let currentNode = this.head
    if (position === 0) {
      // 删除第一个节点的情况

      if (this.length === 1) {
        // 链表内只有一个节点的情况
        this.head = null
        this.tail = null
      } else {
        // 链表内有多个节点的情况
        this.head = this.head.next
        this.head.prev = null
      }
    } else if (position === this.length - 1) {
      // 删除最后一个节点的情况

      currentNode = this.tail
      this.tail.prev.next = null
      this.tail = this.tail.prev
    } else {
      // 删除 0 ~ this.length - 1 里面节点的情况

      let targetIndex = 0
      let previousNode = null
      while (targetIndex++ < position) {
        previousNode = currentNode
        currentNode = currentNode.next
      }

      previousNode.next = currentNode.next
      currentNode.next.perv = previousNode
    }

    this.length--
    return currentNode.data
  }

  // update(position, data) 修改指定位置的节点
  // 重写 update()
  update(position, data) {
    // 1、删除 position 位置的节点
    const result = this.removeAt(position)

    // 2、在 position 位置插入元素
    this.insert(position, data)
    return result
  }

  // remove(data) 删除指定 data 所在的节点(继承单向链表)
  remove(data) {
    return super.remove(data)
  }

  // isEmpty() 判断链表是否为空
  isEmpty() {
    return super.isEmpty()
  }

  // size() 获取链表的长度
  size() {
    return super.size()
  }

  // forwardToString() 链表数据从前往后以字符串形式返回
  forwardToString() {
    let currentNode = this.head
    let result = ""

    // 遍历所有的节点,拼接为字符串,直到节点为 null
    while (currentNode) {
      result += currentNode.data + "--"
      currentNode = currentNode.next
    }

    return result
  }

  // backwardString() 链表数据从后往前以字符串形式返回
  backwardString() {
    let currentNode = this.tail
    let result = ""

    // 遍历所有的节点,拼接为字符串,直到节点为 null
    while (currentNode) {
      result += currentNode.data + "--"
      currentNode = currentNode.prev
    }

    return result
  }
}

二叉树

树拥有很多种结构,二叉树是树中最常用的结构,同时也是一个天然的递归结构。

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

二分搜索树

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

这种存储方式很适合于数据搜索。如下图所示,当需要查找 6 的时候,因为需要查找的值比根节点的值大,所以只需要在根节点的右子树上寻找,大大提高了搜索效率。

堆通常是一个可以被看做一棵树的数组对象。

堆的实现通过构造二叉堆,实为二叉树的一种。这种数据结构具有以下性质。

任意节点小于(或大于)它的所有子节点 堆总是一棵完全树。即除了最底层,其他层的节点都被元素填满,且最底层从左到右填入。 将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

优先队列也完全可以用堆来实现,操作是一模一样的。