typescript实现线段树(SegmentTree)

2022-09-12更新,更好修改的版本

class SegmentTree {
  // 堆
  public arr: number[]

  constructor(n: number = 0, fill: number = 0) {
    this.arr = new Array(n * 4)
    this.arr.fill(fill)
  }

  /**
   * 构建线段树
   * @param list 原始数组
   * @param idx 线段树堆索引 (一般取1)
   * @param l 当前区间起点 (一般取0)
   * @param r 当前区间终点 (一般取list.length - 1)
   */
  build(list: number[], idx: number, l: number, r: number) {
    if (l === r) {
      this.arr[idx] = list[l]
      return
    }
    let mid = (l + r) >> 1
    // 构建左子树
    this.build(list, idx << 1, l, mid)
    // 构建右子树
    this.build(list, (idx << 1) + 1, mid + 1, r)

    // 更新节点值
    this.arr[idx] = this.arr[idx << 1] + this.arr[(idx << 1) + 1]
  }

  /**
   * 区间查询
   * @param idx 线段树堆索引 (一般取1)
   * @param l 当前区间起点 (一般取0)
   * @param r 当前区间终点 (一般取list.length - 1)
   * @param L 要查询的区间的起点
   * @param R 要查询的区间的终点
   */
  queryRange(idx: number, l: number, r: number, L: number, R: number): number {
    // 当前区间与待查区间不相交
    if (r < L || l > R) return 0

    // 当前区间完全被待查区间覆盖
    if (l === L && r === R) return this.arr[idx]

    let mid: number = (l + r) >> 1
    // 查询区间取左区间与原查询区间的交集
    let resL: number = this.queryRange(idx << 1, l, mid, Math.max(l, L), Math.min(mid, R))
    // 查询区间取右区间与原查询区间的交集
    let resR: number = this.queryRange((idx << 1) + 1, mid + 1, r, Math.max(mid + 1, L), Math.min(r, R))
    return resL + resR
  }

  /**
   * 区间查询
   * @param idx 线段树堆索引 (一般取1)
   * @param val 更新后的值
   * @param l 当前区间起点 (一般取0)
   * @param r 当前区间终点 (一般取list.length - 1)
   * @param L 要查询的区间的起点
   * @param R 要查询的区间的终点
   */
  updateRange(idx: number, val: number, l: number, r: number, L: number, R: number) {
    // 当前区间与待查区间不相交
    if (r < L || l > R) return this.arr[idx]

    // 当前区间长度为1,更新区间
    if (l === r) return this.arr[idx] = val

    let mid: number = (l + r) >> 1
    // 查询区间取左区间与原查询区间的交集
    let resL: number = this.updateRange(idx << 1, val, l, mid, Math.max(l, L), Math.min(mid, R))
    // 查询区间取右区间与原查询区间的交集
    let resR: number = this.updateRange((idx << 1) + 1, val, mid + 1, r, Math.max(mid + 1, L), Math.min(r, R))
    this.arr[idx] = resL + resR
    return this.arr[idx]
  }
}

/* 测试样例
~function test() {
  let list: number[] = [3, 2, 1, 4, 5]
  let len = list.length - 1
  let st: SegmentTree = new SegmentTree(list.length)
  st.build(list, 1, 0, list.length - 1)

  // 测试查询
  console.log(st.queryRange(1, 0, len, 1, 3)) // 7, 2+1+4

  // 测试更新
  st.updateRange(1, 4, 0, len, 1, 1) // 将区间[1,1]更新为4, [3, 4, 1, 4, 5]
  console.log(st.queryRange(1, 0, len, 0, 3)) // 12, 3+4+1+4
  st.updateRange(1, 4, 0, len, 0, 3) // 将区间[0,3]更新为4, [4, 4, 4, 4, 5]
  console.log(st.queryRange(1, 0, len, 0, 3)) // 16, 4+4+4+4
}()
*/

之前的版本,参考【neko】线段树【算法编程#6】

含有懒惰标记

class SegmentTreeNode {
  // 该节点的区间起点
  public start: number
  // 该节点的区间终点
  public end: number
  // 该节点的区间结果
  public res: number

  // 懒惰标记
  public lazy: number

  constructor(start: number, end: number) {
    this.start = start
    this.end = end
    this.lazy = 0
  }
}

class SegmentTree {
  // 堆
  public arr: SegmentTreeNode[]

  constructor(list?: number[]) {
    this.arr = []
    if (!list) return this
    this.build(list, 0, list.length - 1)
  }

  /**
   * 构建线段树 (分割区间【start,mid】、【mid+1,end】)
   * @param list base数组
   * @param start 区间起点
   * @param end 区间终点
   * @param arrIndex 线段树堆指针,默认1
   */
  build(list: number[], start: number, end: number, arrIndex: number = 1): void {
    let node: SegmentTreeNode = new SegmentTreeNode(start, end)
    if (start === end) {
      node.res = list[start]
      this.arr[arrIndex] = node
      return
    }

    let mid = (start + end) >> 1
    // 构建左子树
    this.build(list, start, mid, arrIndex << 1)
    // 构建右子树
    this.build(list, mid + 1, end, (arrIndex << 1) + 1)

    // 更新根节点值
    node.res = this.arr[arrIndex << 1].res + this.arr[(arrIndex << 1) + 1].res
    this.arr[arrIndex] = node
  }

  // 下推懒惰节点
  lazyDown(root): void {
    let leftNode: SegmentTreeNode = this.arr[root << 1],
      rightNode: SegmentTreeNode = this.arr[(root << 1) + 1],
      node: SegmentTreeNode = this.arr[root]
    if (leftNode) {
      leftNode.lazy += node.lazy
      leftNode.res += node.lazy * (leftNode.end - leftNode.start + 1)
    }

    if (rightNode) {
      rightNode.lazy += node.lazy
      rightNode.res += node.lazy * (rightNode.end - rightNode.start + 1)
    }
    node.lazy = 0
  }

  // 修改区间结果
  change(start: number, end: number, val: number, root: number = 1): void {
    let node = this.arr[root]
    if (!node) return

    // 当前区间被结果区间完全覆盖,执行更新
    if (node.start >= start && node.end <= end) {
      // 区间内所有的数字都+val
      node.res += val * (node.end - node.start + 1)
      node.lazy += val
      return
    }

    // 当前区间完全覆盖结果区间,更新当前区间结果
    if (start >= node.start && end <= node.end) {
      // 区间内在 【start, end】 范围内的数字+val
      node.res += val * (end - start + 1)
    }

    // 下推懒惰节点
    this.lazyDown(root)

    // 此时区间未完全覆盖,需要继续搜索左右子树
    if (node.start <= end) // 与左区间有交集
      this.change(start, end, val, root << 1)
    if (start <= node.end) // 与右区间有交集
      this.change(start, end, val, (root << 1) + 1)
  }

  // 获取区间结果
  getRangeRes(start: number, end: number = start, root: number = 1): number {
    let node = this.arr[root]
    // 该段区间无值,返回0
    if (!node) return 0

    // 当前区间被完全覆盖,直接返回结果即可
    if (node.start >= start && node.end <= end) {
      return node.res
    }

    // 下推懒惰节点
    this.lazyDown(root)

    let resL = 0, resR = 0
    // 此时区间未完全覆盖,需要继续搜索左右子树
    if (node.start <= end) // 与左区间有交集
      resL = this.getRangeRes(start, end, root << 1)
    if (start <= node.end) // 与右区间有交集
      resR = this.getRangeRes(start, end, (root << 1) + 1)

    return resL + resR
  }
}

/* 测试样例
~function test() {
  let st: SegmentTree = new SegmentTree([3, 2, 1, 4, 5])
  // 测试查询
  console.log(st.getRangeRes(1, 4)) // 12, [2,1,4,5]
  console.log(st.getRangeRes(3, 4)) // 9, [4,5]
  console.log(st.getRangeRes(3))// 4, [4]
  console.log(st.getRangeRes(0, 5))// 15, [3,2,1,4,5]
  console.log('---')

  // 测试修改
  st.change(0, 4, 2)// 区间【0,4】的数都+2
  console.log(st.getRangeRes(0)) // 5, [3+2]
  console.log(st.getRangeRes(2, 4))// 16, [1+2,4+2,5+2]
  console.log(st.getRangeRes(1, 3))// 13, [2+2,1+2,4+2]
  console.log(st.getRangeRes(9,10))// 0, []
}()
*/

不含懒惰标记

class SegmentTreeNode {
  // 该节点的区间起点
  public start: number
  // 该节点的区间终点
  public end: number
  // 该节点的区间结果
  public res: number

  constructor(start: number, end: number) {
    this.start = start
    this.end = end
  }
}

class SegmentTree {
  // 堆
  public arr: SegmentTreeNode[]

  constructor(list?: number[]) {
    this.arr = []
    if (!list) return this
    this.build(list, 0, list.length - 1)
  }

  /**
   * 构建线段树 (分割区间【start,mid】、【mid+1,end】)
   * @param list base数组
   * @param start 区间起点
   * @param end 区间终点
   * @param arrIndex 线段树堆指针,默认1
   */
  build(list: number[], start: number, end: number, arrIndex: number = 1): void {
    let node: SegmentTreeNode = new SegmentTreeNode(start, end)
    if (start === end) {
      node.res = list[start]
      this.arr[arrIndex] = node
      return
    }

    let mid = (start + end) >> 1
    // 构建左子树
    this.build(list, start, mid, arrIndex << 1)
    // 构建右子树
    this.build(list, mid + 1, end, (arrIndex << 1) + 1)

    // 更新根节点值
    node.res = this.arr[arrIndex << 1].res + this.arr[(arrIndex << 1) + 1].res
    this.arr[arrIndex] = node
  }

  // 修改区间结果
  change(start: number, end: number, val: number, root: number = 1): void {
    let node = this.arr[root]
    if (!node) return

    // 当前区间被结果区间完全覆盖,执行更新
    if (node.start >= start && node.end <= end) {
      // 区间内所有的数字都+val
      node.res += val * (node.end - node.start + 1)
    }
    // 当前区间完全覆盖结果区间,更新当前区间结果
    else if (start >= node.start && end <= node.end) {
      // 区间内在 【start, end】 范围内的数字+val
      node.res += val * (end - start + 1)
    }

    // 此时区间未完全覆盖,需要继续搜索左右子树
    if (node.start <= end) // 与左区间有交集
      this.change(start, end, val, root << 1)
    if (start <= node.end) // 与右区间有交集
      this.change(start, end, val, (root << 1) + 1)
  }

  // 获取区间结果
  getRangeRes(start: number, end: number = start, root: number = 1): number {
    let node = this.arr[root]
    // 该段区间无值,返回0
    if (!node) return 0

    // 当前区间被完全覆盖,直接返回结果即可
    if (node.start >= start && node.end <= end) {
      return node.res
    }

    let resL = 0, resR = 0
    // 此时区间未完全覆盖,需要继续搜索左右子树
    if (node.start <= end) // 与左区间有交集
      resL = this.getRangeRes(start, end, root << 1)
    if (start <= node.end) // 与右区间有交集
      resR = this.getRangeRes(start, end, (root << 1) + 1)

    return resL + resR
  }
}

/* 测试样例
~function test() {
  let st: SegmentTree = new SegmentTree([3, 2, 1, 4, 5])
  // 测试查询
  console.log(st.getRangeRes(1, 4)) // 12, [2,1,4,5]
  console.log(st.getRangeRes(3, 4)) // 9, [4,5]
  console.log(st.getRangeRes(3))// 4, [4]
  console.log(st.getRangeRes(0, 5))// 15, [3,2,1,4,5]
  console.log('---')

  // 测试修改
  st.change(0, 4, 2)// 区间【0,4】的数都+2
  console.log(st.getRangeRes(0)) // 5, [3+2]
  console.log(st.getRangeRes(2, 4))// 16, [1+2,4+2,5+2]
  console.log(st.getRangeRes(1, 3))// 13, [2+2,1+2,4+2]
  console.log(st.getRangeRes(9,10))// 0, []
}()
*/
posted @ 2022-07-31 21:45  ぃ往事深处少年蓝べ  阅读(48)  评论(0编辑  收藏  举报