当前位置: 首页 > article >正文

线段树:解决区间查询和区间修改的利器

线段树是一种非常有用的数据结构,它可以在 O ( log ⁡ n ) O(\log n) O(logn) 的时间内支持区间修改和查询。在本文中,我们将介绍线段树的基本概念和实现方法,并通过一个例子来说明其使用方法。

基本概念

线段树是一种二叉树结构,用于处理区间查询和修改操作。它将一个区间分成多个小的子区间,然后用树状结构来表示这些子区间。每个节点代表一个区间,它的左右儿子代表该区间的两个子区间。叶子节点表示区间中的单个元素。

线段树的建立是通过递归的方式进行的。首先将整个区间分成两半,然后分别递归地建立左右子树。每个节点保存了其对应区间内的信息,例如区间和、最大值等。

实现方法

下面是一个简单的线段树实现。我们使用 JavaScript 来演示。假设我们要实现一个支持区间求和操作的线段树,下面是代码实现:

class SegmentTree {
  constructor(nums) {
    this.nums = nums;
    this.tree = new Array(nums.length * 4).fill(0);
    this.build(0, 0, nums.length - 1);
  }

  build(node, start, end) {
    if (start === end) {
      this.tree[node] = this.nums[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(node * 2 + 1, start, mid);
      this.build(node * 2 + 2, mid + 1, end);
      this.tree[node] = this.tree[node * 2 + 1] + this.tree[node * 2 + 2];
    }
  }

  update(node, start, end, i, val) {
    if (start === end) {
      this.nums[i] = val;
      this.tree[node] = val;
    } else {
      const mid = Math.floor((start + end) / 2);
      if (i >= start && i <= mid) {
        this.update(node * 2 + 1, start, mid, i, val);
      } else {
        this.update(node * 2 + 2, mid + 1, end, i, val);
      }
      this.tree[node] = this.tree[node * 2 + 1] + this.tree[node * 2 + 2];
    }
  }

  query(node, start, end, i, j) {
    if (i > end || j < start) {
      return 0;
    }
    if (i <= start && j >= end) {
      return this.tree[node];
    }
    const mid = Math.floor((start + end) / 2);
    return this.query(node * 2 + 1, start, mid, i, j) + this.query(node * 2 + 2, mid + 1, end, i, j);
  }
}

const nums = [1, 3, 5, 7, 9, 11];
const st = new SegmentTree(nums);

console.log(st)

应用实例

线段树的应用非常广泛,下面我们通过一个例子来说明其使用方法。假设我们有一个长度为 n n n 的数组 a a a,需要支持以下两种操作:

  1. 修改数组中的某个元素 a i a_i ai 的值为 v v v
  2. 查询区间 [ l , r ] [l, r] [l,r] 的元素和。

使用线段树可以很方便地实现这两个操作。我们首先需要建立一棵线段树,将数组中的元素保存在叶子节点上。每个节点保存了其对应区间内的元素和,以支持区间查询。

当需要修改某个元素时,我们可以使用线段树的 update 操作来实现。具体来说,我们从根节点开始递归,找到对应叶子节点,并将其值修改为新的值 v v v。同时,我们还需要更新该节点对应的父节点、祖先节点的值,以保证线段树的正确性。

当需要查询某个区间的元素和时,我们可以使用线段树的 query 操作来实现。具体来说,我们从根节点开始递归,找到包含区间 [ l , r ] [l, r] [l,r] 的节点。如果该节点的区间恰好为 [ l , r ] [l, r] [l,r],则直接返回其对应的元素和。否则,我们需要将区间 [ l , r ] [l, r] [l,r] 拆分成两个子区间 [ l , m i d ] [l, mid] [l,mid] [ m i d + 1 , r ] [mid+1, r] [mid+1,r],分别递归查询左右子树,最后将其结果合并即可。

下面是基于线段树实现的一个例子:

class SegmentTree {
  constructor(nums) {
    this.nums = nums;
    this.tree = new Array(nums.length * 4).fill(0);
    this.build(0, 0, nums.length - 1);
  }

  build(node, start, end) {
    if (start === end) {
      this.tree[node] = this.nums[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(node * 2 + 1, start, mid);
      this.build(node * 2 + 2, mid + 1, end);
      this.tree[node] = this.tree[node * 2 + 1] + this.tree[node * 2 + 2];
    }
  }

  update(node, start, end, i, val) {
    if (start === end) {
      this.nums[i] = val;
      this.tree[node] = val;
    } else {
      const mid = Math.floor((start + end) / 2);
      if (i >= start && i <= mid) {
        this.update(node * 2 + 1, start, mid, i, val);
      } else {
        this.update(node * 2 + 2, mid + 1, end, i, val);
      }
      this.tree[node] = this.tree[node * 2 + 1] + this.tree[node * 2 + 2];
    }
  }

  query(node, start, end, i, j) {
    if (i > end || j < start) {
      return 0;
	} else if (i <= start && j >= end) {
		return this.tree[node];
	} else {
		const mid = Math.floor((start + end) / 2);
		const leftSum = this.query(node * 2 + 1, start, mid, i, j);
		const rightSum = this.query(node * 2 + 2, mid + 1, end, i, j);
		return leftSum + rightSum;
	}
  }
}

// 使用示例
const nums = [1, 3, 5, 7, 9, 11];
const tree = new SegmentTree(nums);

console.log(tree.query(0, 0, nums.length - 1, 2, 4)); // 输出 15
tree.update(0, 0, nums.length - 1, 3, 8);
console.log(tree.query(0, 0, nums.length - 1, 2, 4)); // 输出 18

在上面的代码中,我们定义了一个名为 SegmentTree 的类,它包含了建立线段树、修改元素、查询区间元素和等方法。在构造函数中,我们先初始化了线段树的数组,并通过 build 方法建立线段树。在 update 方法中,我们首先递归找到要修改的节点,然后将其值修改为新的值,同时更新该节点的父节点和祖先节点。在 query 方法中,我们首先判断当前节点的区间与查询区间是否有交集,如果没有则返回 0;如果当前节点的区间包含在查询区间中,则直接返回该节点的元素和;否则我们需要将查询区间拆分成两个子区间,分别递归查询左右子树,最后将其结果合并。

通过上面的代码,我们可以方便地实现对数组的修改和区间求和操作,而且时间复杂度为 O ( log ⁡ n ) O(\log n) O(logn)


http://www.kler.cn/a/5913.html

相关文章:

  • LabVIEW 系统诊断
  • C++ STL 中的 vector 总结
  • 数据库环境安装(day1)
  • [笔记] 使用 Jenkins 实现 CI/CD :从 GitLab 拉取 Java 项目并部署至 Windows Server
  • MySQL insert or update方式性能比较
  • Linux(上):基本知识篇
  • Activity登堂入室
  • 树状数组 基础知识——C++数据结构
  • STM32学习(十三)
  • 讲解有哪些实用的数据恢复工具
  • 【C语言】整形数据的存储和读取过程
  • 【算法】【数组与矩阵模块】矩阵中的最短通路值
  • Autodesk AutoCAD 2023(CAD设计软件)自动化工具介绍以及图文安装教程
  • Python项目部署上线
  • 【Docker学习笔记】9.Docker Machine及Swarm 集群管理
  • 嵌入式linux网卡bonding配置
  • 13.Template Method模板方法(行为型模式)
  • ChatGPT编程秀:做一个简单爬虫程序
  • JDBC数据库驱动的下载与安装与连接
  • LeetCode-119. 杨辉三角 II
  • Azure SQL基础到实战(2)-部署
  • 提高工作效率,这 10 款 AI 工具不能错过
  • 2020年 第11届 蓝桥杯 Java B组 省赛真题详解及小结【第1场省赛 2020.7.5】
  • python接口自动化---接口测试报告模板(详解)
  • Linux第二次总结
  • MySQL数据库——MySQL是什么?它有什么优势?