基于C#实现线段树

一、线段树

线段树又称"区间树”,在每个节点上保存一个区间,当然区间的划分采用折半的思想,叶子节点只保存一个值,也叫单元节点,所以最终的构造就是一个平衡的二叉树,拥有 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

XC3320 离线式、无电感交流输入线性稳压器 可替代KP3310 固定5V输出电压

XC3320 是一款紧凑型无电感设计的离线式线性稳压器。XC3320 可获得 5V输出电压。XC3320 是一种简单可靠的获得偏置供电的离线式电源解决方案。XC3320 集成了 650V 功率 MOSFET&#xff0c;启动控制电路,VDD 电压控制电路,AC 交流信号同步检测电路&#xff0c;低压差稳压器等。该…

电动机保护方式

3.3.1、电动机温度保护 温度保护是利用安装在电动机内部的温度继电器或变换器来实现的。当电动机达到一定温度时继电器动作&#xff0c;通过控制电路断开电动机的主电路。对于单相小容量电动机&#xff0c;可以用继电器直接断开动力电路。 根据温度传感器的不同可以分为&…

项目管理中的资源日历是什么?有什么作用

管理项目不仅需要规划和预算&#xff0c;还需要日程安排。 资源日历是一种显示项目经理或团队领导在特定时间内可用资源的工具。这种类型的项目日历可以显示团队成员和设备在特定时间段内的可用工作时间。 例如&#xff0c;如果一名员工每天工作 8 小时&#xff0c;而他已经在…

软件开发及交付的项目管理角色

在软件开发及交付过程中&#xff0c;通常会涉及不同的角色和职责&#xff0c;包括业务角色、技术角色和管理角色。这些角色在项目管理中发挥着不同的作用&#xff0c;以确保项目的成功和交付高质量的产品。 业务角色&#xff1a;包括产品经理、业务分析师和业务运营人员等职位…

MySQL数据库_01

Web后端开发_02 数据库介绍 什么是数据库&#xff1f; 数据库&#xff1a;DataBase&#xff08;DB&#xff09;&#xff0c;是存储和管理数据的仓库 数据库管理系统&#xff1a;DataBase Management System (DBMS)&#xff0c;操纵和管理数据库的大型软件。SQL&#xff1a;St…

APM工具skywalking部署

一 整体架构 整个架构&#xff0c;分成上、下、左、右四部分&#xff1a; 上部分 Agent &#xff1a;负责从应用中&#xff0c;收集链路信息&#xff0c;发送给 SkyWalking OAP 服务器。目前支持 SkyWalking、Zikpin、Jaeger 等提供的 Tracing 数据信息。而我们目前采用的是&…

css渐变详解(重复性线性渐变、径向渐变、重复性径向渐变的使用)

目录 线性渐变 重复性线性渐变 径向渐变 重复性径向渐变的使用 线性渐变 线性渐变是向下、向上、向左、向右、对角方向的颜色渐变。 其语法格式为&#xff1a; background-image: linear-gradient(side-or-corner|angle, linear-color-stop); 参数说明如下&#xff1a; …

【Proteus仿真】【51单片机】篮球比赛计分器

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用声光报警模块、动态数码管模块、按键模块等。 主要功能&#xff1a; 系统运行后&#xff0c;数码管显示比赛时间和AB队得分&#xff1b;系统还未开…

小白也看的懂的爬取视频操作

1.获取一段视频 可以直接从抖音下&#xff0c;也可以从b站上爬取&#xff08;注意法律谢谢&#xff09; 保护原创 b站的视频 直接复制网址链接到哔哩哔哩(bilibili)视频解析下载 - 保存B站视频到手机、电脑 去就好了&#xff0c;

QGIS之二十五两个面图层数据中选择图形完全一致的数据

效果 步骤 1、准备数据 2、按位置选择 在Qgis工具箱中搜索"按位置选择"工具 选择要素和比较要素根据实际选择 运行 3、结果

基于框架的线性回归

线性回归是机器学习中最简单和最常用的回归方法之一。它建立了自变量和因变量之间的线性关系&#xff0c;并通过拟合一条直线或超平面来预测和分析数据。 基于框架的线性回归是构建线性回归模型的一种常见方法&#xff0c;它利用现有的机器学习框架来实现线性回归模型的建立、…

40、Flink 的Apache Kafka connector(kafka source 和sink 说明及使用示例) 完整版

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

易涝积水点监测,内涝积水监测仪安装

城市内涝对人们来讲会有很多影响&#xff0c;比如出行需要绕远路或者家中涌入污水导致淤泥堆积&#xff0c;这还有可能让屋内的家具受到破坏&#xff0c;既影响正常生活也造成了经济损失。在街道上还可能对交通、通讯、电力等基础设施造成严重威胁。因此政府如果能实时监测路面…

实用工具推荐 | 在线制作电子书

​随着互联网的发展&#xff0c;越来越多的人开始关注知识的传播和分享。而电子书作为一种方便携带、易于分享的形式&#xff0c;越来越受到人们的青睐。今天&#xff0c;就为大家推荐一款实用的工具——FLBOOK在线制作电子杂志平台&#xff0c;让你轻松在线制作电子书&#xf…

邻趣连接力:如何无代码集成CRM、电商平台和营销系统,提升广告推广效率

连接即服务&#xff1a;邻趣无代码集成方法 传统的电商系统集成过程需要大量的时间和资源进行API开发&#xff0c;这不仅耗时耗力&#xff0c;还需要专业的技术团队支持。然而&#xff0c;邻趣通过提供一种无需API开发的连接方法&#xff0c;极大地简化了整个集成过程。商家只…

【独家发布】抖音半蓝V官方免费认证技术

先在巨量引擎升级dou账号 随后上传资料进行验证即可 逐步操作 全程实操保姆及教程 后续0粉点亮蓝v技术教程 来自&#xff1a;人类小徐-分享有价值的资源

Python 异常的传递性

实例 这里就简单用2个function来演示一下异常的传递性 func1 这里num 1/0明显是一个ZeroDivisionError错误&#xff0c;作为演示 def func1():print("fun1 开始执行")num 1 / 0print("func1 结束执行") func2 def func2():print("func2 开始执…

如何截留快手行业意向用户:10个合规方法大揭秘

先来看实操成果&#xff0c;↑↑需要的同学可看我名字↖↖↖↖↖&#xff0c;或评论888无偿分享 一、引言 随着互联网的发展&#xff0c;快手已成为一个巨大的流量池&#xff0c;吸引了无数用户。其中&#xff0c;不乏许多行业的意向用户。如何截留这些意向用户&#xff0c;成…

Python计算DICOM图像两点真实距离

Python计算DICOM图像两点真实距离 对比测量结果图Code对比测量结果图 DICOM阅读器(小赛看看)测量结果 python测量结果 Code import numpy as np import cv2 import math import pydicom from pydicom.pixel_data_handlers.util import convert_color_spaceds = pydicom.dc…

迅镭激光板材切割自动化生产线中标高端机械装备龙头豪迈集团!

近年来&#xff0c;中国制造业逐步由低端制造业向高端制造业迈进、由劳动密集型向技术密集型转变&#xff0c;智能制造带动了制造业生产环节的自动化、信息化、数字化、智能化的迭代升级。 位于山东省的高端机械装备龙头——豪迈集团&#xff0c;紧跟国家发展战略&#xff0c;加…