基于C#实现线段树

news2024/9/22 17:31:39

一、线段树

线段树又称"区间树”,在每个节点上保存一个区间,当然区间的划分采用折半的思想,叶子节点只保存一个值,也叫单元节点,所以最终的构造就是一个平衡的二叉树,拥有 CURD 的 O(lgN)的时间。
image.png
从图中我们可以清楚的看到[0-10]被划分成线段的在树中的分布情况,针对区间[0-N],最多有 2N 个节点,由于是平衡二叉树的形式也可以像堆那样用数组来玩,不过更加耗费空间,为最多 4N 个节点,在针对 RMQ 的问题上,我们常常在每个节点上增加一些 sum,max,min 等变量来记录求得的累加值,当然你可以理解成动态规划的思想,由于拥有 logN 的时间,所以在 RMQ 问题上比数组更加优美。

二、代码

1、在节点中定义一些附加值,方便我们处理 RMQ 问题。

 #region 线段树的节点
 /// <summary>
 /// 线段树的节点
 /// </summary>
 public class Node
 {
     /// <summary>
     /// 区间左端点
     /// </summary>
     public int left;

     /// <summary>
     /// 区间右端点
     /// </summary>
     public int right;

     /// <summary>
     /// 左孩子
     /// </summary>
     public Node leftchild;

     /// <summary>
     /// 右孩子
     /// </summary>
     public Node rightchild;

     /// <summary>
     /// 节点的sum值
     /// </summary>
     public int Sum;

     /// <summary>
     /// 节点的Min值
     /// </summary>
     public int Min;

     /// <summary>
     /// 节点的Max值
     /// </summary>
     public int Max;
 }
 #endregion

2、构建(Build)
前面我也说了,构建有两种方法,数组的形式或者链的形式,各有特点,我就采用后者,时间为 O(N)。

  #region 根据数组构建“线段树"
 /// <summary>
 /// 根据数组构建“线段树"
 /// </summary>
 /// <param name="length"></param>
 public Node Build(int[] nums)
 {
     this.nums = nums;

     return Build(nodeTree, 0, nums.Length - 1);
 }
 #endregion

 #region 根据数组构建“线段树"
 /// <summary>
 /// 根据数组构建“线段树"
 /// </summary>
 /// <param name="left"></param>
 /// <param name="right"></param>
 public Node Build(Node node, int left, int right)
 {
     //说明已经到根了,当前当前节点的max,sum,min值(回溯时统计上一层节点区间的值)
     if (left == right)
     {
         return new Node
         {
             left = left,
             right = right,
             Max = nums[left],
             Min = nums[left],
             Sum = nums[left]
         };
     }

     if (node == null)
         node = new Node();

     node.left = left;

     node.right = right;

     node.leftchild = Build(node.leftchild, left, (left + right) / 2);

     node.rightchild = Build(node.rightchild, (left + right) / 2 + 1, right);

     //统计左右子树的值(min,max,sum)
     node.Min = Math.Min(node.leftchild.Min, node.rightchild.Min);
     node.Max = Math.Max(node.leftchild.Max, node.rightchild.Max);
     node.Sum = node.leftchild.Sum + node.rightchild.Sum;

     return node;
 }
 #endregion

3、区间查询
在线段树中,区间查询还是有点小麻烦的,存在三种情况。
① 完全包含:也就是节点的线段范围完全在查询区间的范围内,这说明我们要么到了“单元节点",要么到了一个子区间,这种情况就是我找到了查询区间的某一个子区间,直接累积该区间值就可以了。
② 左交集: 这种情况我们需要到左子树去遍历。
③ 右交集: 这种情况我们需要到右子树去遍历。
比如说:我要查询 Sum[4-8]的值,最终会成为:Sum 总=Sum[4-4]+Sum[5-5]+Sum[6-8],时间为 log(N)。

 #region 区间查询
 /// <summary>
 /// 区间查询(分解)
 /// </summary>
 /// <returns></returns>
 public int Query(int left, int right)
 {
     int sum = 0;

     Query(nodeTree, left, right, ref sum);

     return sum;
 }

 /// <summary>
 /// 区间查询
 /// </summary>
 /// <param name="left">查询左边界</param>
 /// <param name="right">查询右边界</param>
 /// <param name="node">查询的节点</param>
 /// <returns></returns>
 public void Query(Node node, int left, int right, ref int sum)
 {
     //说明当前节点完全包含在查询范围内,两点:要么是单元节点,要么是子区间
     if (left <= node.left && right >= node.right)
     {
         //获取当前节点的sum值
         sum += node.Sum;
         return;
     }
     else
     {
         //如果当前的left和right 和node的left和right无交集,此时可返回
         if (node.left > right || node.right < left)
             return;

         //找到中间线
         var middle = (node.left + node.right) / 2;

         //左孩子有交集
         if (left <= middle)
         {
             Query(node.leftchild, left, right, ref sum);
         }
         //右孩子有交集
         if (right >= middle)
         {
             Query(node.rightchild, left, right, ref sum);
         }

     }
 }
 #endregion

4、更新操作
这个操作跟树状数组中的更新操作一样,当递归的找到待修改的节点后,改完其值然后在当前节点一路回溯,并且在回溯的过程中一路修改父节点的附加值直到根节点,至此我们的操作就完成了,复杂度同样为 logN。

 #region 更新操作
 /// <summary>
 /// 更新操作
 /// </summary>
 /// <param name="index"></param>
 /// <param name="key"></param>
 public void Update(int index, int key)
 {
     Update(nodeTree, index, key);
 }

 /// <summary>
 /// 更新操作
 /// </summary>
 /// <param name="index"></param>
 /// <param name="key"></param>
 public void Update(Node node, int index, int key)
 {
     if (node == null)
         return;

     //取中间值
     var middle = (node.left + node.right) / 2;

     //遍历左子树
     if (index >= node.left && index <= middle)
         Update(node.leftchild, index, key);

     //遍历右子树
     if (index <= node.right && index >= middle + 1)
         Update(node.rightchild, index, key);

     //在回溯的路上一路更改,复杂度为lgN
     if (index >= node.left && index <= node.right)
     {
         //说明找到了节点
         if (node.left == node.right)
         {
             nums[index] = key;

             node.Sum = node.Max = node.Min = key;
         }
         else
         {
             //回溯时统计左右子树的值(min,max,sum)
             node.Min = Math.Min(node.leftchild.Min, node.rightchild.Min);
             node.Max = Math.Max(node.leftchild.Max, node.rightchild.Max);
             node.Sum = node.leftchild.Sum + node.rightchild.Sum;
         }
     }
 }
 #endregion

最后我们做个例子,在 2000000 的数组空间中,寻找 200-3000 区间段的 sum 值,看看他的表现如何。

 using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 using System.Diagnostics;
 using System.Threading;
 using System.IO;
 
 namespace ConsoleApplication2
 {
     public class Program
     {
         public static void Main()
         {
             int[] nums = new int[200 * 10000];
 
             for (int i = 0; i < 10000 * 200; i++)
             {
                 nums[i] = i;
             }
 
             Tree tree = new Tree();
 
             //将当前数组构建成 “线段树”
             tree.Build(nums);
 
             var watch = Stopwatch.StartNew();
 
             var sum = tree.Query(200, 3000);
 
             watch.Stop();
 
             Console.WriteLine("耗费时间:{0}ms,  当前数组有:{1}个数字, 求出Sum=:{2}", watch.ElapsedMilliseconds, nums.Length, sum);
 
             Console.Read();
         }
     }
 
     public class Tree
     {
         #region 线段树的节点
         /// <summary>
         /// 线段树的节点
         /// </summary>
         public class Node
         {
             /// <summary>
             /// 区间左端点
             /// </summary>
             public int left;
 
             /// <summary>
             /// 区间右端点
             /// </summary>
             public int right;
 
             /// <summary>
             /// 左孩子
             /// </summary>
             public Node leftchild;
 
             /// <summary>
             /// 右孩子
             /// </summary>
             public Node rightchild;
 
             /// <summary>
             /// 节点的sum值
             /// </summary>
             public int Sum;
 
             /// <summary>
             /// 节点的Min值
             /// </summary>
             public int Min;
 
             /// <summary>
             /// 节点的Max值
             /// </summary>
             public int Max;
         }
         #endregion
 
         Node nodeTree = new Node();
 
         int[] nums;
 
         #region 根据数组构建“线段树"
         /// <summary>
         /// 根据数组构建“线段树"
         /// </summary>
         /// <param name="length"></param>
         public Node Build(int[] nums)
         {
             this.nums = nums;
 
             return Build(nodeTree, 0, nums.Length - 1);
         }
         #endregion
 
         #region 根据数组构建“线段树"
         /// <summary>
         /// 根据数组构建“线段树"
         /// </summary>
         /// <param name="left"></param>
         /// <param name="right"></param>
         public Node Build(Node node, int left, int right)
         {
             //说明已经到根了,当前当前节点的max,sum,min值(回溯时统计上一层节点区间的值)
             if (left == right)
             {
                 return new Node
                 {
                     left = left,
                     right = right,
                     Max = nums[left],
                     Min = nums[left],
                     Sum = nums[left]
                 };
             }
 
             if (node == null)
                 node = new Node();
 
             node.left = left;
 
             node.right = right;
 
             node.leftchild = Build(node.leftchild, left, (left + right) / 2);
 
             node.rightchild = Build(node.rightchild, (left + right) / 2 + 1, right);
 
             //统计左右子树的值(min,max,sum)
             node.Min = Math.Min(node.leftchild.Min, node.rightchild.Min);
             node.Max = Math.Max(node.leftchild.Max, node.rightchild.Max);
             node.Sum = node.leftchild.Sum + node.rightchild.Sum;

             return node;
         }
         #endregion
 
         #region 区间查询
         /// <summary>
         /// 区间查询(分解)
         /// </summary>
         /// <returns></returns>
         public int Query(int left, int right)
         {
             int sum = 0;
 
             Query(nodeTree, left, right, ref sum);
 
             return sum;
         }
 
         /// <summary>
         /// 区间查询
         /// </summary>
         /// <param name="left">查询左边界</param>
         /// <param name="right">查询右边界</param>
         /// <param name="node">查询的节点</param>
         /// <returns></returns>
         public void Query(Node node, int left, int right, ref int sum)
         {
             //说明当前节点完全包含在查询范围内,两点:要么是单元节点,要么是子区间
             if (left <= node.left && right >= node.right)
             {
                 //获取当前节点的sum值
                 sum += node.Sum;
                 return;
             }
             else
             {
                 //如果当前的left和right 和node的left和right无交集,此时可返回
                 if (node.left > right || node.right < left)
                     return;
 
                 //找到中间线
                 var middle = (node.left + node.right) / 2;
 
                 //左孩子有交集
                 if (left <= middle)
                 {
                     Query(node.leftchild, left, right, ref sum);
                 }
                 //右孩子有交集
                 if (right >= middle)
                 {
                     Query(node.rightchild, left, right, ref sum);
                 }
 
             }
         }
         #endregion
 
         #region 更新操作
         /// <summary>
         /// 更新操作
         /// </summary>
         /// <param name="index"></param>
         /// <param name="key"></param>
         public void Update(int index, int key)
         {
             Update(nodeTree, index, key);
         }
 
         /// <summary>
         /// 更新操作
         /// </summary>
         /// <param name="index"></param>
         /// <param name="key"></param>
         public void Update(Node node, int index, int key)
         {
             if (node == null)
                 return;
 
             //取中间值
             var middle = (node.left + node.right) / 2;
 
             //遍历左子树
             if (index >= node.left && index <= middle)
                 Update(node.leftchild, index, key);
 
             //遍历右子树
             if (index <= node.right && index >= middle + 1)
                 Update(node.rightchild, index, key);
 
             //在回溯的路上一路更改,复杂度为lgN
             if (index >= node.left && index <= node.right)
             {
                 //说明找到了节点
                 if (node.left == node.right)
                 {
                     nums[index] = key;
 
                     node.Sum = node.Max = node.Min = key;
                 }
                 else
                 {
                     //回溯时统计左右子树的值(min,max,sum)
                     node.Min = Math.Min(node.leftchild.Min, node.rightchild.Min);
                     node.Max = Math.Max(node.leftchild.Max, node.rightchild.Max);
                     node.Sum = node.leftchild.Sum + node.rightchild.Sum;
                 }
             }
         }
         #endregion
     }
 }

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1244880.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决:javax.websocket.server.ServerContainer not available 报错问题

原因&#xff1a; 用于扫描带有 ServerEndpoint 的注解成为 websocket&#xff0c;该方法是 服务器端点出口&#xff0c;当进行 SpringBoot 单元测试时&#xff0c;并没有启动服务器&#xff0c;所以当加载到这个bean时会报错。 解决方法&#xff1a; 加上这个注解内容 Spr…

不做机器视觉工程师,转行,转岗的建议与想法

正所谓外行看热闹&#xff0c;内行看门道。提前咨询前辈们&#xff0c;多问问&#xff0c;多看看。要做就做&#xff0c;一定要提前做好防范。 无论你是要转行或者是转岗&#xff0c;看你有没有本钱和试错成本 有些人&#xff0c;家庭好&#xff0c;可以一直去试错和从头再来。…

MySQL 8 配置文件详解与最佳实践

MySQL 8 是一款强大的关系型数据库管理系统&#xff0c;通过适当的配置文件设置&#xff0c;可以充分发挥其性能潜力。在这篇博客中&#xff0c;我们将深入探究 MySQL 8 常用的配置文件&#xff0c;并提供一些建议&#xff0c;帮助您优化数据库性能。 配置文件概览 在 MySQL …

4.常见面试题--操作系统

特点&#xff1a;并发性、共享性、虚拟性、异步性。 Windows 和 Linux 内核差异 对于内核的架构⼀般有这三种类型&#xff1a; ● 宏内核&#xff0c;包含多个模块&#xff0c;整个内核像⼀个完整的程序&#xff1b; ● 微内核&#xff0c;有⼀个最⼩版本的内核&#xff0…

linux的基础命令

文章目录 linux的基础命令一、linux的目录结构&#xff08;一&#xff09;Linux路径的描述方式 二、Linux命令入门&#xff08;一&#xff09;Linux命令基础格式 三、ls命令&#xff08;一&#xff09;HOME目录和工作目录&#xff08;二&#xff09;ls命令的参数1.ls命令的-a选…

Java项目如何打包成Jar(最简单)

最简单的办法&#xff0c;使用Maven插件&#xff08;idea自带&#xff09; 1.选择需要打包的mudule&#xff0c;点击idea右侧的maven插件 2.clean操作 3.选择需要的其他mudule&#xff0c;进行install操作&#xff08;如果有&#xff09; 4.再次选择需要打包的module&#…

UDP客户端使用connect与UDP服务器使用send函数和recv函数收发数据

服务器代码编译运行 服务器udpconnectToServer.c的代码如下&#xff1a; #include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #include<errno.h> #inclu…

什么是网络爬虫技术?它的重要用途有哪些?

网络爬虫&#xff08;Web Crawler&#xff09;是一种自动化的网页浏览程序&#xff0c;能够根据一定的规则和算法&#xff0c;从互联网上抓取和收集数据。网络爬虫技术是随着互联网的发展而逐渐成熟的一种技术&#xff0c;它在搜索引擎、数据挖掘、信息处理等领域发挥着越来越重…

CentOS Stream 9系统Cgroup问题处理

安装docker容器启动失败 之前适配过Ubuntu系统的容器&#xff0c;由于版本比较高&#xff0c;没有挂载Cgroup的路径。这次使用Centos Stream 9系统安装docker容器时也遇到了这个情况。由于处理方式有些不一样&#xff0c;所以记录一下。 这是docker容器启动过报错的输出日志。…

C#串口通信从入门到精通(27)——高速通信下解决数据处理慢的问题(20ms以内)

前言 我们在开发串口通信程序时,有时候会遇到比如单片机或者传感器发送的数据速度特别快,比如10ms、20ms发送一次,并且每次发送的数据量还比较大,如果按照常规的写法,我们会发现接收的数据还没处理完,新的数据又发送过来了,这就会导致处理数据滞后,软件始终处理的不是…

5G智慧工地整体解决方案:文件全文115页,附下载

关键词&#xff1a;5G智慧工地&#xff0c;智慧工地建设方案&#xff0c;智慧工地管理平台系统&#xff0c;智慧工地建设调研报告&#xff0c;智慧工地云平台建设 一、5G智慧工地建设背景 5G智慧工地是利用5G技术、物联网、大数据、云计算、AI等信息技术&#xff0c;围绕“人…

docker 安装常用环境

一、 安装linux&#xff08;完整&#xff09; 目前为止docker hub 还是被封着&#xff0c;用阿里云、腾讯云镜像找一找版本直接查就行 默认使用latest最新版 #:latest 可以不写 docker pull centos:latest # 拉取后查看 images docker images #给镜像设置标签 # docker tag […

pytorch训练模型内存溢出

1、训练模型命令命令 如下所示是训练命名实体识别的命令&#xff0c;在win10系统下执行 activate pytorch cd F:\Python\github\ultralytics-main\submain\pytorch_bert_bilstm_crf_ner-main f: python main.py --bert_dir"../model_hub/chinese-bert-wwm-ext/" --…

【Fusion 360环境搭建】保姆级贴心教程

文章目录 选择Fusion 360的理由一、Fusion 360注册账号二、安装 选择Fusion 360的理由 稚辉君推荐&#xff01; 一、Fusion 360注册账号 官网网址 https://www.autodesk.com.cn/products/fusion-360 贴心提示&#xff1a;访问不了时试试手机热点 选择“ 教育” &#xf…

【Java】初识JDBC

&#x1f33a;个人主页&#xff1a;Dawn黎明开始 &#x1f380;系列专栏&#xff1a;Java ⭐每日一句&#xff1a;向阳而生&#xff0c;逐光而行 &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️ 文章目录 &#x1f4cb;前言 …

【2023 云栖】阿里云刘一鸣:Data+AI 时代大数据平台建设的思考与发布

云布道师 本文根据 2023 云栖大会演讲实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;刘一鸣 | 阿里云自研大数据产品负责人 演讲主题&#xff1a;DataAI 时代大数据平台应该如何建设 今天分享的主题是 DataAI 时代大数据平台应该如何建设&#xff0…

python+django高校科研项目管理系统2u3mx

高校科研项目管理系统采用拟开发的高校科研项目管理系统通过测试,确保在最大负载的情况下稳定运转,各个模块工作正常,具有较高的可用性。系统整体界面简洁美观,用户使用简单,满足用户需要。在因特网发展迅猛的当今社会,高校科研项目管理系统必然会成为在数字信息化建设的一个重…

51单片机应用从零开始(七)·循环语句(if语句,swtich语句)

51单片机应用从零开始&#xff08;一&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;二&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;三&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;四&#xff09;-CSDN博客 51单片机应用从零开始&#xff08;…

【数据结构(C语言)】浅谈栈和队列

目录 文章目录 前言 一、栈 1.1 栈的概念及结构 1.2 栈的实现 1.2.1. 支持动态增长的栈的结构 1.2.2 初始化栈 1.2.3 入栈 1.2.4 出栈 1.2.5 获取栈顶元素 1.2.6 获取栈中有效元素个数 1.2.7 检查栈是否为空 1.2.8 销毁栈 二、队列 2.1 队列的概念及结构 2.2 队…

将 Spring 微服务与 BI 工具集成:最佳实践

软件开发领域是一个不断发展的领域&#xff0c;新的范式和技术不断涌现。其中&#xff0c;微服务架构和商业智能&#xff08;BI&#xff09;工具的采用是两项关键进步。随着 Spring Boot 和 Spring Cloud 在构建强大的微服务方面的普及&#xff0c;了解这些微服务如何与 BI 工具…