我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++高级数据结构之线段树

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C++高级数据结构之线段树

前言:

  • 高级数据结构(Ⅲ)线段树(Segment Tree)
  • 线段树的原理
  • 树的创建
  • 单点修改
  • 区间查找
  • 完整代码及测试

高级数据结构(Ⅲ)线段树(Segment Tree)

线段树的原理

线段树是一种二叉搜索树 , 对于线段树中的每一个非叶子结点[a,b],它的左儿子表示的区间为[a, (a+b)/2],右儿子表示的区间为[(a+b)/2+1, b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度,其空间复杂度为O(n)。

若对于一个数组使用前缀和数组保存它的区间和,那么查找区间和时间复杂度为O(1),而区间修改时间复杂度为O(n)。使用线段树可以快速查找或修改某个区间的值,时间复杂度为O(logN)。

线段树中每一个结点代表一个区间,对于区间【1, 7】线段树的结构如下图

实例:

class SegmentTree{
private static final int[] tree = new int[1000];
int[] arr;
SegmentTree() {
}
SegmentTree(int[] arr) {
this.arr = arr;
}
//创建树···
public void buildTree(){}
//单点修改更新树
public void updateTree(){}
//区间查找
public void queryTree(){}
}

树的创建

给定一个数组arr = [6, 4, 7, 5, 8, 3 , 9],创建它对应的线段树数组。

对于一个结点k,它的左孩子为2 * k,右孩子为 2 * k + 1,此公式适用于根结点从1开始。但我们在数组中存储元素时下标通常是从0开始的,即根结点从0开始,此时它的左孩子为 2 * k + 1, 右孩子为 2 * k + 2。

如下图所示,数组arr的长度为7,由于树是以2为基数的,且线段树中叶子结点保存的是arr中单个结点的值,我们可以将数组arr的长度设想为8 (2 ^ 3 = 8,理解为新添了一个元素0,这对区间和并不影响),此时它对应的线段树就是一棵结点数为15(1 + 2 + 4 + 8)的满二叉树。相应的结点值,映射到数组tree的值在图中可清晰的看出。

那么,如何用处程序来创建一棵树呢?

由于线段树叶子结点都是数组arr中的某一元素,所以我们可以使用两个变量low和high来标记数组arr的区间,

  • 若low == high,此时令tree[node] = arr[low],并终止递归
  • 否则,将区间二分,分别计算左区间[low, mid]和右区间[mid +1, high],并在最后更新tree[node]

实现:

//创建树
public void buildTree() {
this.buildTree(0, 0, arr.length - 1);
}
private void buildTree(int node, int low, int high) {
if(low == high) {
tree[node] = arr[low];
return;
}
int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
buildTree(lnode, low, mid);
buildTree(rnode, mid + 1, high);
tree[node] = tree[lnode] + tree[rnode];
}

单点修改

若现在将arr[4]的值修改为1,需要怎样操作呢?

从下图绿色标出的结点不难看出其更新过程,先将其所对应的叶子结点修改,然后继续向上修改其父节点即可。

当long==high&&low==index时更新两个数组的值,否则,缩小区间,在相应的区间搜索,最后更新结点和即可,相应代码如下

//单点修改更新树
public void updateTree(int index, int val) {
this.updateTree(0, 0, arr.length - 1, index, val);
}
private void updateTree(int node, int low, int high, int index, int val) {
if(low == high && low == index) {
arr[index] = val;
tree[node] = val;
return;
}
int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
if(index >= low && index <= mid) {
updateTree(lnode, low, mid, index, val);
}else {
updateTree(rnode, mid + 1, high, index, val);
}
tree[node] = tree[lnode] + tree[rnode];
}

区间查找

若现在查找数组arr区间和[3,6],如何利用线段树呢?

在线段树中,我们将它的和划分为两个区间[3]和[4,6],如图中的黄色结点

下面来看看相关代码如何实现,给定一个查找区间[L, R],同样使用变量low和high维护对数组arr的二分查找边界

  • 若当前区间low > R 或者 high < L,说明已超出查找范围,返回0
  • 若[low, high]处于区间[L, R]内,返回当前结点的值tree[node]

然后继续在左右区间查找并保存左右区间的值sumLeft和sumRight,最后返回sumLeft + sumRight即可

//区间查找
public int queryTree(int L, int R) {
return this.queryTree(0, 0, arr.length - 1, L, R);
}
private int queryTree(int node, int low,
int high, int L, int R) {
if(low > R || high < L) {
return 0;
}else if(low >= L && high <= R) {
return tree[node];
}
int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
int sumleft = queryTree(lnode, low, mid, L, R);
int sumRight = queryTree(rnode, mid + 1, high, L, R);
return sumleft + sumRight;
}

完整代码及测试

class SegmentTree{
private static final int[] tree = new int[1000];
int[] arr;
SegmentTree() {
}
SegmentTree(int[] arr) {
this.arr = arr;
}
//创建树
public void buildTree() {
this.buildTree(0, 0, arr.length - 1);
}
private void buildTree(int node, int low, int high) {
if(low == high) {
tree[node] = arr[low];
return;
}
int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
buildTree(lnode, low, mid);
buildTree(rnode, mid + 1, high);
tree[node] = tree[lnode] + tree[rnode];
}
//单点修改更新树
public void updateTree(int index, int val) {
this.updateTree(0, 0, arr.length - 1, index, val);
}
private void updateTree(int node, int low, int high, int index, int val) {
if(low == high && low == index) {
arr[index] = val;
tree[node] = val;
return;
}
int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
if(index >= low && index <= mid) {
updateTree(lnode, low, mid, index, val);
}else {
updateTree(rnode, mid + 1, high, index, val);
}
tree[node] = tree[lnode] + tree[rnode];
}
//区间查找
public int queryTree(int L, int R) {
return this.queryTree(0, 0, arr.length - 1, L, R);
}
private int queryTree(int node, int low, int high, int L, int R) {
if(low > R || high < L) {
return 0;
}else if(low >= L && high <= R) {
return tree[node];
}
int mid = low + (high - low) / 2;
int lnode = 2 * node + 1;
int rnode = 2 * node + 2;
int sumleft = queryTree(lnode, low, mid, L, R);
int sumRight = queryTree(rnode, mid + 1, high, L, R);
return sumleft + sumRight;
}
//输出线段树的值
public void printTree() {
int size = 15; //size值的大小由arr数组的大小而定
for (int i = 0; i < size; i++) {
System.out.print(tree[i] + " ");
}
System.out.println();
}
}
public class SegmentTreeTest {
public static void main(String[] args) {
int[] arr = {6, 4, 7, 5, 8, 3, 9};
SegmentTree st = new SegmentTree(arr);
//创建线段树
st.buildTree();
st.printTree();
//>>>42 22 20 10 12 11 9 6 4 7 5 8 3 0 0
//查找区间[3, 6]
int sum = st.queryTree(3, 6);
System.out.println(sum);
//>>>25
//单点修改更新树, 令arr[4] = 1
st.updateTree(4, 1);
st.printTree();
//>>>35 22 13 10 12 4 9 6 4 7 5 1 3 0 0
}
}

树结点版本:

此版本不使用数组保存,而是以结点来保存值,相应代码不难实现,如下:

import java.util.ArrayDeque;
import java.util.Deque;
class SegNode{
int val;
SegNode lnode;
SegNode rnode;
SegNode(){}
SegNode(int val) {
this.val = val;
}
}
class SegTree{
SegNode root;
int[] arr;
SegTree() {}
SegTree(int[] arr) {
this.arr = arr;
this.bulidTree();
}
//创建树
public void bulidTree() {
root = this.buildTree(0, arr.length - 1);
}
private SegNode buildTree(int low, int high) {
if(low == high) {
return new SegNode(arr[low]);
}
SegNode node = new SegNode();
int mid = low + (high - low) / 2;
node.lnode = buildTree(low, mid);
node.rnode = buildTree(mid + 1, high);
node.val = node.lnode.val + node.rnode.val;
return node;
}
//单点修改更新树
public void updateTree(int index, int val) {
root = updateTree(root ,0, arr.length - 1, index, val);
}
private SegNode updateTree(SegNode node, int low, int high, int index, int val) {
if(low == high && low == index) {
arr[index] = val;
node.val = val;
return node;
}
int mid = low + (high - low) / 2;
if(index >= low && index <= mid) {
node.lnode = updateTree(node.lnode, low, mid, index, val);
}else {
node.rnode = updateTree(node.rnode, mid + 1, high, index, val);
}
node.val = node.lnode.val + node.rnode.val;
return node;
}
//查找区间
public int queryTree(int L, int R) {
return queryTree(root, 0, arr.length - 1, L, R);
}
private int queryTree(SegNode node, int low, int high, int L ,int R) {
if(low > R || high < L) {
return 0;
}else if(low >= L && high <= R) {
return node.val;
}
int mid = low + (high - low) / 2;
int sumLeft = queryTree(node.lnode, low, mid, L, R);
int sumRight = queryTree(node.rnode, mid + 1, high, L, R);
return sumLeft + sumRight;
}
//输出树(层次遍历)
public void printTree() {
Deque<SegNode> queue = new ArrayDeque<SegNode>();
queue.offer(this.root);
while(!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
SegNode node = queue.poll();
System.out.print(node.val + " ");
if(node.lnode != null) queue.offer(node.lnode);
if(node.rnode != null) queue.offer(node.rnode);
}
}
}
}
public class SegmentTreeNodeTest {
public static void main(String[] args) {
int[] arr = {6, 4, 7, 5, 8, 3, 9};
//创建线段树
SegTree st = new SegTree(arr);
st.printTree();
System.out.println("");
//>>>42 22 20 10 12 11 9 6 4 7 5 8 3
//查找区间[3, 6]
int sum = st.queryTree(3, 6);
System.out.println(sum);
//>>>25
//单点修改更新树, 令arr[4] = 1
st.updateTree(4, 1);
st.printTree();
System.out.println("");
>>>35 22 13 10 12 4 9 6 4 7 5 1 3
}
}

到此这篇关于C++高级数据结构之线段树的文章就介绍到这了,更多相关C++线段树内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++高级数据结构之线段树

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C++高级数据结构之二叉查找树怎么实现

本文小编为大家详细介绍“C++高级数据结构之二叉查找树怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“C++高级数据结构之二叉查找树怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。高级数据结构(Ⅳ)
2023-06-30

C++数据结构之红黑树的实现

红黑树在表意上就是一棵每个节点带有颜色的二叉搜索树,并通过对节点颜色的控制,使该二叉搜索树达到尽量平衡的状态。本文主要为大家介绍了C++中红黑树的原理及实现,需要的可以参考一下
2022-11-13

C++数据结构之AVL树如何实现

这篇文章主要讲解了“C++数据结构之AVL树如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++数据结构之AVL树如何实现”吧!1.概念(1)二叉搜索树的缺点要手撕AVL树,我们首先
2023-07-02

用Python实现数据结构之树

树是由根结点和若干颗子树构成的。树是由一个集合以及在该集合上定义的一种关系构成的。集合中的元素称为树的结点,所定义的关系称为父子关系。父子关系在树的结点之间建立了一个层次结构。在这种层次结构中有一个结点具有特殊的地位,这个结点称为该树的根结
2023-01-30

PHP数据结构:AVL树的平衡之道,维持高效有序的数据结构

avl 树是一种平衡二叉搜索树,确保快速高效的数据操作。为了实现平衡,它执行左旋和右旋操作,调整违反平衡的子树。avl 树利用高度平衡,确保树的高度相对于节点数始终较小,从而实现对数时间复杂度 (o(log n)) 的查找操作,即使在大型数
PHP数据结构:AVL树的平衡之道,维持高效有序的数据结构
2024-05-14

Java数据结构之线索化二叉树怎么实现

这篇文章主要介绍“Java数据结构之线索化二叉树怎么实现”,在日常操作中,相信很多人在Java数据结构之线索化二叉树怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java数据结构之线索化二叉树怎么实现
2023-06-30

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录