数据结构与算法之链表(1)

421 阅读9分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

前言

前面写了两篇文章, 互动都不好, 可能是水平有限, 可能是不合大家胃口, 因为没有人互动, 我也就不知道是什么问题啦, 我也就不管啦, 我只管继续写, 尽量做一些微调, 多写些类别, 争取得到互动~~

我也是学习的态度去写的文章, 保证原创, 不保证正确, 会持续更新

这里有一些是借鉴了javascript-algorithm这个github开源的库和mdn

概念

链表是由一系列的节点组成, 每个节点保存着数据和一个(只包含指向下一个节点的引用, 称为单链表)或两个(包含一个指向它上一个的节点的引用和一个指向下一个节点的引用, 称为双向链表)引用地址.

所以链表在内存中的位置可能是不连续的, 和数组相比

特点链表数组备注
内存中连续在链表中进行增,删操作比数组快, 但是进行查找比数组慢, 所以频繁增删, 很少查询,优先使用链表
存储内容数据 + 引用地址只有数据所以链表比数组更占内存空间, 所以存储大量数据优先使用数组

链表常见的操作

在 js 中, 数组是语言原生支持的, 在数组原型上提供了丰富的方法让我们很方便的操作数组. 链表也有一些常见的操作,需要我们手动支持一下

单向链表实现

这里先只实现单向链表, 有时间再实现一下双向链表

// Comparator.ts
type CompareFnReturn = 0 | -1 | 1
const defaultCompareFn = (a: any, b: any) => {
  if (a === b) {
    return 0
  }
  return a > b ? 1 : -1
}
/**
 * 比较
 */
class Comparator {
  compare = defaultCompareFn
  constructor(compareFn?: (a: any, b: any) => CompareFnReturn) {
    if (typeof compareFn === 'function') {
      this.compare = compareFn
    }
  }
  /**
   * 判断相等
   */
  equal(a: any, b: any) {
    return this.compare(a, b) === 1
  }
  /**
   * 判断a < b
   */
  lessThan(a: any, b: any) {
    return this.compare(a, b) < 0
  }
  /**
   * 判断 a > b
   */
  greaterThan(a: any, b: any) {
    return this.compare(a, b) > 0
  }
  /**
   * 大于等于, 和小于等于就不写了, 用小于和大于判断然后取反就行了, 如有必要再加上
   */
}
export default Comparator
// SingleLinkedList.ts
import Comparator from './Comparator'
const ISSINGLELINKEDLIST = Symbol('isSingleLinkedList')
export type SingleLinkedListNodeType = SingleLinkedListNode | null | undefined
interface ExtendsSingleLinkedList extends SingleLinkedList {
  [key: string]: any
}
class SingleLinkedListNode {
  data = 0
  next: SingleLinkedListNodeType = null
  constructor(data: any, next: SingleLinkedListNodeType = null) {
    this.data = data
    this.next = next
  }
}

type CallbackFn = (
  nodeData: any,
  index?: number,
  SingleLinkedList?: ExtendsSingleLinkedList
) => any
/**
 * 判断一个数据是否是单链表
 * @param data
 * @returns
 */
const isSingleLinkedList = (data: any) => {
  return !!data?.[ISSINGLELINKEDLIST]
}
class SingleLinkedList {
  // >>>>>>>>>>>  静态方法  >>>>>>>>>>>
  /**
   * 静态方法也加上
   */
  static isSingleLinkedList = isSingleLinkedList
  /**
   * 从伪数组或者可迭代对象生成链表
   * @param arrayLike 伪数组对象或者可迭代对象
   * @param mapFn 回调函数
   * @param thisArg 回调函数的this指向
   * @returns 一个新的链表
   */
  static from(
    arrayLike: any,
    mapFn?: any,
    thisArg?: any
  ): ExtendsSingleLinkedList {
    const singleLinkedList = new SingleLinkedList(
      Array.from(arrayLike, mapFn, thisArg)
    )
    return singleLinkedList
  }
  /**
   * 返回由参数列表组成的新的链表
   * @param args 参数列表
   * @returns 返回新的链表
   */
  static of(...args: any[]): ExtendsSingleLinkedList {
    const newSingleLinkedList = new SingleLinkedList(args)
    return newSingleLinkedList
  }
  // <<<<<<<<<<<  静态方法  <<<<<<<<<<<
  constructor(arr?: any[]) {
    if (Array.isArray(arr)) {
      arr.forEach((a) => {
        this.push(a)
      })
    }
    const obj: ExtendsSingleLinkedList = new Proxy(this, {
      get(target: ExtendsSingleLinkedList, p: string) {
        // 支持直接 this[0]  这种方式直接获取链表中的值
        if (Number.isInteger(+p)) {
          return target.getPositionValue(+p)
        }
        return target[p]
      },
      set(target: ExtendsSingleLinkedList, key: any, data: any) {
        if (key in target) {
          target[key] = data
          return true
        }
        // 支持直接 this[0] = data 这种方式修改链表的值
        target.changePositionValue(+key, data)
        return true
      },
    })
    return obj
  }
  /**
   * 标记是单链表
   */
  [ISSINGLELINKEDLIST] = true
  /**
   * 原型上加上判断是否是单链表的方法
   */
  isSingleLinkedList = isSingleLinkedList

  /**
   * 第一个节点, 头部节点
   */
  head: SingleLinkedListNodeType = null
  /**
   * 最后一个节点, 尾部节点
   */
  tail: SingleLinkedListNodeType = null
  /**
   * 链表节点个数
   */
  length: number = 0
  /**
   * 判断链表是否为空
   */
  isEmpty(): boolean {
    return this.length === 0
  }
  /**
   * 转为JSON字符串
   */
  toJSONString(indent = 2) {
    return JSON.stringify(this, null, indent)
  }
  /**
   * 链表转数组
   * @returns 返回数组
   */
  toArray(): any[] {
    let node = this.head
    const arr = []
    while (node) {
      arr.push(node.data)
      node = node.next
    }
    return arr
  }
  /**
   * 转为可读字符串
   */
  toReadString() {
    let node = this.head
    const arr: any[] = []
    while (node) {
      arr.push(node.data)
      node = node.next
    }
    return `
链表长度为: ${this.length}
${arr
  .map(
    (a) => `==> ${a}
`
  )
  .join('')}
`
  }
  /**
   * 比较函数
   */
  compare = new Comparator()
  /**
   * 清空链表
   * @returns 返回true
   */
  clear() {
    this.head = this.tail = null
    this.length = 0
    return true
  }
  /**
   * 头部插入
   * @param data 要出入的数据
   * @returns length 链表的新长度
   */
  unshift(data: any): number {
    /**
     * 创建一个节点, 默认指向当前的头节点
     */
    const node = new SingleLinkedListNode(data, this.head)
    /**
     * 一共有两种情况
     * 1. 原先链表是空的
     * 2. 原先链表不是空的
     */
    if (this.isEmpty()) {
      /**
       * 将头尾都指向这个新的节点, 然后节点数量加一
       */
      this.head = node
      this.tail = node
      this.length++
    } else {
      /**
       * 将头部指向这个新节点
       */
      this.head = node
      this.length++
    }
    return this.length
  }
  /**
   * 从尾部插入
   * @param data 要插入的数据
   * @returns length 链表的新长度
   */
  push(data: any): number {
    /**
     * 从尾部插入, 有两种情况
     * 1. 链表是空的, 这时候, 相当于从头部插入
     * 2. 链表不是空的
     */
    if (this.isEmpty()) {
      this.unshift(data)
    } else {
      // 链表不是空的, 需要新建一个节点
      const node = new SingleLinkedListNode(data)
      // 将原先的尾部下一个节点指向它
      this.tail!.next = node
      // 尾部的指向它
      this.tail = node
      // 链表长度加一
      this.length++
    }
    return this.length
  }
  /**
   * 从链表尾部删除一个节点, 并返回节点的值
   * @returns 返回删除的节点的值
   */
  pop(): any {
    /**
     * 一共三种情况
     * 1. 链表是空的
     * 2. 链表只有一个节点
     * 3. 链表不止一个节点
     */
    if (this.isEmpty()) {
      // 情况1, 链表是空的, 返回undefined, 其它啥都不干
      return void 0
    } else if (this.length === 1) {
      // 情况2, 链表只有一个节点
      // 保存尾结点的值
      const tailData = this.tail?.data
      // 清空链表
      this.clear()
      // 返回尾结点的值
      return tailData
    } else {
      // 情况3, 不止一个节点
      let node = this.head
      while (node) {
        if (node.next === this.tail) {
          // 找到尾结点上一个节点
          // 保存尾结点的值
          const tailData = this.tail?.data
          // 将尾结点的前一个节点变成尾结点
          node.next = null
          this.tail = node
          // 链表长度减一
          this.length--
          return tailData
        }
      }
    }
  }
  /**
   * 从链表头部删除一共节点, 并返回它的值
   * @returns 返回删除的值
   */
  shift(): any {
    /**
     * 一共可以分为两种情况
     * 1. 链表长度小于2, 即链表为空或者为1
     * 2. 链表长度大于等于2
     */
    if (this.length < 2) {
      return this.pop()
    } else {
      // 保存头部的值
      const headData = this.head?.data
      // 修改头部指向
      this.head = this.head!.next
      // 链表长度减一
      this.length--
      return headData
    }
  }
  /**
   * 判断一个位置是否在链表中是否是合法的位置
   * @param position 位置
   * @returns 是否是合法的位置
   */
  isValidPosition(position: number): boolean {
    if (!Number.isInteger(position)) {
      return false
    } else if (0 <= position && this.length >= position) {
      return true
    } else {
      return false
    }
  }
  /**
   * 返回位置的节点
   * @param position 位置
   * @returns 返回这个位置的节点
   */
  getPositionNode(position: number): SingleLinkedListNodeType {
    // 不是合法的位置, 返回null
    if (!this.isValidPosition(position)) {
      return null
    }
    // 合法的位置, 肯定是 0 到 this.length 之间了
    let node = this.head
    let index = position
    while (index !== 0 && node) {
      index--
      node = node.next
    }
    return node
  }
  /**
   * 返回位置上的值
   * @param position 位置
   * @returns 返回这个位置上的值
   */
  getPositionValue(position: number) {
    return this.getPositionNode(position)?.data
  }
  /**
   * 修改节点的值
   * @param node 节点
   * @param value 修改节点的值
   * @returns 是否修改成功
   */
  changeNodeValue(node: SingleLinkedListNodeType, value: any): boolean {
    if (node) {
      node.data = value
      return true
    } else {
      return false
    }
  }
  /**
   * 修改为位置为position的值
   * @param position 位置
   * @param data 修改的值
   * @returns 是否修改成功
   */
  changePositionValue(position: number, data: any) {
    const node = this.getPositionNode(position)
    return this.changeNodeValue(node, data)
  }
  /**
   *
   * @param value 要插入的数据
   * @param data 基准数据
   * @param after 在基准数据之后, 默认是, 假时是在这个基准数据之前
   */
  insertValue(value: any, data: any, before = true) {
    console.log(value, data, before)
  }
  /**
   * 查找这个基准数据下一个节点
   * @param data 基准数据
   * @returns 返回找到的节点
   */
  findNodeValueAfter(data: any): SingleLinkedListNodeType {
    let node = this.findNodeValueBefore(data)
    while (node) {}
    return node
  }
  /**
   * 查找基准数据之前的节点
   * @param data 基准数据
   * @returns 返回找到的节点
   */
  findNodeValueBefore(data: any): SingleLinkedListNodeType {
    let node = this.head
    while (node) {
      if (this.compare.equal(data, node.data)) {
        return node
      }
      node = node.next
    }
    return node
  }
  /**
   * 此方法用于合并两个或多个单链表, 不会改变现有单链表而是返回一个新的链表
   * @param args 单链表数组
   * @returns 拼接后的单链表
   */
  concat(...args: ExtendsSingleLinkedList[]): ExtendsSingleLinkedList {
    console.log(args)
    return this.concatLinkedList(this, ...args)
  }
  /**
   * 合并两个或多个链表
   * @param firstSingleLinked 基础链表
   * @param args 剩余链表
   * @returns 返回合并后的新链表
   */
  concatLinkedList(
    firstSingleLinked: ExtendsSingleLinkedList,
    ...args: ExtendsSingleLinkedList[]
  ): ExtendsSingleLinkedList {
    if (args.length < 1) {
      return firstSingleLinked
    }
    // todo
    if (args.length === 1) {
      // const newSingleLinkedList = new SingleLinkedList()

      return firstSingleLinked
    }
    const [first, ...rest] = args
    return this.concatLinkedList(first, ...rest)
  }
  // todo
  /**
   * 浅复制链表中的一部分到同一链表另一位置, 不会改变原链表的长度
   * @param target 0为基底的索引, 复制到该位置, 如果是负数, target会从末尾开始计算. 如果target大于链表的size, 复制将不会发生. 如果target大于
   * @param start
   * @param end
   * @returns
   */
  copyWidthIn(
    target: number,
    start = 0,
    end?: number
  ): ExtendsSingleLinkedList {
    console.log(target, start, end)

    return this
  }
  /**
   * 测试一个链表中所有元素都是否能够通过某个函数的测试, 它返回一个布尔值
   * @param fn 测试函数
   * @param argThis 测试函数的this指向
   * @returns 是否测试通过
   */
  every(fn: CallbackFn, argThis?: any): boolean {
    if (typeof fn !== 'function') {
      throw new TypeError()
    }
    if (this.isEmpty()) {
      // 如果链表是空的直接返回true
      return true
    } else {
      let node = this.head
      let index = 0
      while (node) {
        const result = fn.call(argThis, node.data, index, this)
        if (!result) {
          return false
        }
        node = node.next
        index++
      }
      return true
    }
  }
  /**
   * 用一个固定的值填充链表中的从开始索引到终止索引上的所有节点的值, 不包含终止索引
   * @param value 填充的值
   * @param start 开始索引
   * @param end 终止索引
   * @returns 修改后的链表
   */
  fill(value: any, start = 0, end?: number): ExtendsSingleLinkedList {
    end = end ?? this.length
    let node = this.head
    let index = 0
    while (node && index < end) {
      if (index >= start) {
        this.changeNodeValue(node, value)
      }
      node = node.next
      index++
    }
    return this
  }
  /**
   * 创建一个新链表, 其包含通过测试函数的所有节点
   * @param fn 测试函数
   * @param argThis  测试函数的this指向
   * @returns 返回一个新的链表
   */
  filter(fn: CallbackFn, argThis?: any): ExtendsSingleLinkedList {
    const newSingleLinkedList = new SingleLinkedList()
    this.loop(
      (
        nodeData: any,
        index: number,
        singleLinkedList?: ExtendsSingleLinkedList
      ): boolean => {
        if (fn.call(argThis, nodeData, index, singleLinkedList)) {
          newSingleLinkedList.push(nodeData)
        }
        return false
      }
    )
    return newSingleLinkedList
  }
  /**
   * 内部循环使用的函数
   * @param fn callback函数
   * @returns 返回是否通过所有的测试函数
   */
  protected loop(
    fn: (
      nodeData: any,
      index: number,
      SingleLinkedList: ExtendsSingleLinkedList
    ) => any
  ) {
    let node = this.head
    let index = 0
    while (node) {
      if (fn(node.data, index, this)) {
        return true
      }
      index++
      node = node.next
    }
    return false
  }
  /**
   * 返回链表中第一个满足测试函数的值, 否则返回undefined
   * @param fn 测试函数
   * @param argThis 测试函数的this指向
   * @returns 返回第一个通过测试函数的值, 否则返回undefined
   */
  find(fn: CallbackFn, argThis?: any): any {
    let findData
    this.loop(
      (
        nodeData: any,
        index: number,
        singleLinkedList?: ExtendsSingleLinkedList
      ): boolean => {
        if (fn.call(argThis, nodeData, index, singleLinkedList)) {
          findData = nodeData
          return true
        }
        return false
      }
    )
    return findData
  }
  /**
   * 返回满足测试函数的第一个节点的索引, 找不到返回-1
   * @param fn 测试函数
   * @param argThis 测试函数的this指向
   * @returns
   */
  findIndex(fn: CallbackFn, argThis?: any): number {
    let findIndex = -1
    this.loop(
      (
        nodeData: any,
        index: number,
        singleLinkedList?: ExtendsSingleLinkedList
      ): boolean => {
        if (fn.call(argThis, nodeData, index, singleLinkedList)) {
          findIndex = index
          return true
        }
        return false
      }
    )
    return findIndex
  }
  /**
   * 对链表中每个节点都执行一下测试函数
   * @param fn 测试函数
   * @param argThis 测试函数的this指向
   */
  forEach(fn: CallbackFn, argThis?: any): void {
    this.loop(
      (
        nodeData: any,
        index: number,
        singleLinkedList?: ExtendsSingleLinkedList
      ): boolean => {
        fn.call(argThis, nodeData, index, singleLinkedList)
        return false
      }
    )
  }
  /**
   * 判断链表中是否包含某个值
   * @param valueToFind 需要查找的值
   * @param fromIndex 开始查找的索引
   * @returns 是否包含
   */
  includes(valueToFind: any, fromIndex = 0): boolean {
    let includes = false
    this.loop((nodeData: any, index: number): boolean => {
      if (nodeData === valueToFind && fromIndex <= index) {
        return (includes = true)
      }
      return false
    })
    return includes
  }
  /**
   * 在链表中, 从指定下标开始查找指定的值, 返回找到的索引
   * @param valueToFind 要查找的值
   * @param fromIndex 开始查找的索引
   * @returns 返回找到的索引, 没找到返回-1
   */
  indexOf(valueToFind: any, fromIndex = 0): number {
    let index = -1
    this.loop((nodeData: any, nodeIndex: number): boolean => {
      if (nodeData === valueToFind && fromIndex <= nodeIndex) {
        index = nodeIndex
        return true
      }
      return false
    })
    return index
  }
  /**
   *
   * @param valueToFind 需要查找的值
   * @param fromIndex 从这个位置开始倒序查找
   * @returns
   */
  lastIndexOf(valueToFind: any, fromIndex = this.length): number {
    let index = -1
    // 由于单向链表不好倒序, 只好使用正序模拟倒序了
    this.loop((nodeData: any, nodeIndex: number): boolean => {
      if (nodeData === valueToFind && fromIndex >= nodeIndex) {
        index = nodeIndex
      }
      if (fromIndex <= nodeIndex) {
        // 找到结束位置了
        return true
      }
      return false
    })
    return index
  }
  /**
   * 将链表中的所有元素使用特定分隔符拼接成一个字符串
   * @param separator 分割符
   * @returns 返回拼接好的字符串
   */
  join(separator = ','): string {
    let str = ''
    this.loop((nodeData: any, nodeIndex: number): boolean => {
      const isLast = this.length === nodeIndex + 1
      str += nodeData + (isLast ? '' : separator)
      return false
    })
    return str
  }
  /**
   * 创建一个新链表, 其结果是源链表中每个元素通过调用回调函数的回调值
   * @param fn 回调函数
   * @param argThis 回调函数的this指向
   * @returns 返回新的链表
   */
  map(fn: CallbackFn, argThis?: any): ExtendsSingleLinkedList {
    let newSingleLinkedList = new SingleLinkedList()
    this.loop(
      (
        nodeData: any,
        index: number,
        singleLinkedList?: ExtendsSingleLinkedList
      ): boolean => {
        const result = fn.call(argThis, nodeData, index, singleLinkedList)
        newSingleLinkedList.push(result)
        return false
      }
    )
    return newSingleLinkedList
  }
  /**
   * 对链表中每个元素执行提供的回调函数, 将其结果汇总成单个返回值
   * @param callback 回调函数
   * @param initialValue 初始值
   * @returns 汇总的值
   */
  reduce(
    callback: (
      accumulator: any,
      currentValue: any,
      index: number,
      singleLinkedList: ExtendsSingleLinkedList
    ) => any,
    initialValue?: any
  ): any {
    let result
    this.loop(
      (
        nodeData: any,
        nodeIndex: number,
        singleLinkedList: ExtendsSingleLinkedList
      ): boolean => {
        if (nodeIndex === 0 && initialValue === void 0) {
          // 没有传入初始值时, 将第一个元素作为初始值
          result = nodeData
        } else {
          result = callback(initialValue, nodeData, nodeIndex, singleLinkedList)
        }
        return false
      }
    )
    return result
  }
  /**
   * 将链表中的元素进行反转
   * @returns 反转后的链表
   */
  reverse(): ExtendsSingleLinkedList {
    const arr = this.toArray()
    this.clear()
    arr.forEach((a) => this.unshift(a))
    return this
  }
  /**
   * 返回一个从链表开始索引到结束索引组成的新链表
   * @param begin 开始索引 包含
   * @param end 结束索引 不包含
   * @returns 新的链表
   */
  slice(begin = 0, end = this.length): ExtendsSingleLinkedList {
    const newSingleLinkedList = new SingleLinkedList()
    this.loop((nodeData: any, nodeIndex: number): boolean => {
      // 结束了, 不包含结束索引
      if (end <= nodeIndex) {
        return true
      }
      // 开始了, 包含开始索引
      if (begin <= nodeIndex) {
        newSingleLinkedList.push(nodeData)
      }

      return false
    })
    return newSingleLinkedList
  }
  /**
   * 返回链表中是否至少有一个元素通过测试函数
   * @param fn 测试函数
   * @param argThis 测试函数的this指向
   * @returns 返回是否通过测试
   */
  some(fn: CallbackFn, argThis?: any): boolean {
    let result = false
    this.loop(
      (
        nodeData: any,
        nodeIndex: number,
        singleLinkedList: ExtendsSingleLinkedList
      ): boolean => {
        if (fn.call(argThis, nodeData, nodeIndex, singleLinkedList)) {
          return (result = true)
        }

        return false
      }
    )
    return result
  }
  // values
  // toLocaleString
  // splice
  // sort
  // reduceRight
  // entries
  // flat
  // flatMap
  // keys
}
export default SingleLinkedList

写在最后

之前的文章列表

  1. vue2 源码解析之 nextTick
  2. 代码片段之 js 限流调度器