基于C#实现树状数组

有一种数据结构是神奇的,神秘的,它展现了位运算与数组结合的神奇魅力,太牛逼的,它就是树状数组,这种数据结构不是神人是发现不了的。

一、概序

假如我现在有个需求,就是要频繁的求数组的前 n 项和,并且存在着数组中某些数字的频繁修改,那么我们该如何实现这样的需求?当然大家可以往真实项目上靠一靠。
**① 传统方法:**根据索引修改为 O(1),但是求前 n 项和为 O(n)。
**② 空间换时间方法:**我开一个数组 sum[],sum[i]=a[1]+…+a[i],那么有点意思,求 n 项和为 O(1),但是修改却成了 O(N),这是因为我的 Sum[i]中牵涉的数据太多了,那么问题来了,我能不能在相应的 sum[i]中只保存某些 a[i]的值呢?好吧,下面我们看张图。
image.png
从图中我们可以看到 S[]的分布变成了一颗树,有意思吧,下面我们看看 S[i]中到底存放着哪些 a[i]的值。

S[1]=a[1];
S[2]=a[1]+a[2];
S[3]=a[3];
S[4]=a[1]+a[2]+a[3]+a[4];
S[5]=a[5];
S[6]=a[5]+a[6];
S[7]=a[7];
S[8]=a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7]+a[8];

之所以采用这样的分布方式,是因为我们使用的是这样的一个公式:S[i]=a[i-2k+1]+…+a[i]。
其中:2k 中的 k 表示当前 S[i]在树中的层数,它的值就是 i 的二进制中末尾连续 0 的个数,2k 也就是表示 S[i]中包含了哪些 a[],举个例子: i=610=01102 ;可以发现末尾连续的 0 有一个,即 k=1,则说明 S[6]是在树中的第二层,并且 S[6]中有 21 项,随后我们求出了起始项:
a[6-21+1]=a[5],但是在编码中求出 k 的值还是有点麻烦的,所以我们采用更灵巧的 Lowbit 技术,即:2k=i&-i 。
则:S[6]=a[6-21+1]=a[6-(6&-6)+1]=a[5]+a[6]。

二、代码

1、神奇的 Lowbit 函数

 #region 当前的sum数列的起始下标/// <summary>/// 当前的sum数列的起始下标/// </summary>/// <param name="i"></param>/// <returns></returns>public static int Lowbit(int i){return i & -i;}#endregion

2、求前 n 项和

比如上图中,如何求 Sum(6),很显然 Sum(6)=S4+S6,那么如何寻找 S4 呢?即找到 6 以前的所有最大子树,很显然这个求和的复杂度为 logN。

 #region 求前n项和/// <summary>/// 求前n项和/// </summary>/// <param name="x"></param>/// <returns></returns>public static int Sum(int x){int ans = 0;var i = x;while (i > 0){ans += sumArray[i - 1];//当前项的最大子树i -= Lowbit(i);}return ans;}#endregion

3、修改

如上图中,如果我修改了 a[5]的值,那么包含 a[5]的 S[5],S[6],S[8]的区间值都需要同步修改,我们看到只要沿着 S[5]一直回溯到根即可,同样它的时间复杂度也为 logN。

 public static void Modify(int x, int newValue){//拿出原数组的值var oldValue = arr[x];for (int i = x; i < arr.Length; i += Lowbit(i + 1)){//减去老值,换一个新值sumArray[i] = sumArray[i] - oldValue + newValue;}}

最后上总的代码:

 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{static int[] sumArray = new int[8];static int[] arr = new int[8];public static void Main(){Init();Console.WriteLine("A数组的值:{0}", string.Join(",", arr));Console.WriteLine("S数组的值:{0}", string.Join(",", sumArray));Console.WriteLine("修改A[1]的值为3");Modify(1, 3);Console.WriteLine("A数组的值:{0}", string.Join(",", arr));Console.WriteLine("S数组的值:{0}", string.Join(",", sumArray));Console.Read();}#region 初始化两个数组/// <summary>/// 初始化两个数组/// </summary>public static void Init(){for (int i = 1; i <= 8; i++){arr[i - 1] = i;//设置其实坐标:i=1开始int start = (i - Lowbit(i));var sum = 0;while (start < i){sum += arr[start];start++;}sumArray[i - 1] = sum;}}#endregionpublic static void Modify(int x, int newValue){//拿出原数组的值var oldValue = arr[x];arr[x] = newValue;for (int i = x; i < arr.Length; i += Lowbit(i + 1)){//减去老值,换一个新值sumArray[i] = sumArray[i] - oldValue + newValue;}}#region 求前n项和/// <summary>/// 求前n项和/// </summary>/// <param name="x"></param>/// <returns></returns>public static int Sum(int x){int ans = 0;var i = x;while (i > 0){ans += sumArray[i - 1];//当前项的最大子树i -= Lowbit(i);}return ans;}#endregion#region 当前的sum数列的起始下标/// <summary>/// 当前的sum数列的起始下标/// </summary>/// <param name="i"></param>/// <returns></returns>public static int Lowbit(int i){return i & -i;}#endregion}
}

image.png

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

黑马React18: ReactRouter

黑马React: ReactRouter Date: November 21, 2023 Sum: React路由基础、路由导航、导航传参、嵌套路由配置 路由快速上手 1. 什么是前端路由 一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候&#xff0c;path 对应的组件会在页面中进行渲染 2. …

如何在Ubuntu的Linux系统中安装MySQL5.7数据库

前往MySQL数据库官网链接地址下载5.7数据库。 MySQL :: Download MySQL Community Server (Archived Versions)使用ssh的可视化工具将下载的mysql-5.7.40-linux-glibc2.12-x86_64.tar.gz文件上传到Linux服务器&#xff0c;并解压文件 tar -zxvf mysql-5.7.40-linux-glibc2.12-x…

lv11 嵌入式开发 FS4412开发环境搭建 8

目录 1 开发板硬件资源介绍 2 初始原理图 3 交叉开发环境搭建 3.1 在ubuntu下安装交叉编译工具链 3.2 在windows下安装SecureCRT 3.3 在windows下安装USB转串口驱动 3.4 下载测试程序 4 硬件控制原理 4.1 地址映射表 4.2 查看数据手册 1 开发板硬件资源介绍 …

外网IP和内网ip的区别

首先得先知道什么是ip地址&#xff0c;它就是唯一标识连接网络的设备的&#xff0c;即IP地址充当了设备在网络中的“住址”&#xff0c;使得设备能够相互通信和交换数据。 我们常听开发人员说外网内网&#xff0c;那么它们有什么区别呢&#xff1f; 外网可以理解为互联网&…

开发上门送桶装水小程序要考虑哪些业务场景

上门送水业务已经有很长一段时间了&#xff0c;但是最开始都是给用户发名片、贴小广告&#xff0c;然后客户电话订水&#xff0c;水站工作人员再上门去送&#xff0c;这种人工记单和派单效率并不高&#xff0c;并且电话沟通中也比较容易出现偏差&#xff0c;那么根据这个情况就…

二分查找——经典题目合集

文章目录 &#x1f99c;69. x 的平方根&#x1f33c;题目&#x1f33b;算法原理&#x1f337;代码实现 &#x1f433;35. 搜索插入位置&#x1f33c;题目&#x1f33b;算法原理&#x1f337;代码实现 &#x1f9ad;852. 山脉数组的峰顶索引&#x1f33c;题目&#x1f33b;算法原…

简单几步,借助Aapose.Cells将 Excel XLS 转换为PPT

数据呈现是商业和学术工作的一个重要方面。通常&#xff0c;您需要将数据从一种格式转换为另一种格式&#xff0c;以创建信息丰富且具有视觉吸引力的演示文稿。当您需要在幻灯片上呈现工作表数据时&#xff0c;需要从 Excel XLS 转换为 PowerPoint 演示文稿。在这篇博文中&…

原理Redis-QuickList

QuickList **问题1&#xff1a;**ZipList虽然节省内存&#xff0c;但申请内存必须是连续空间&#xff0c;如果内存占用较多&#xff0c;申请内存效率很低。怎么办&#xff1f; 为了缓解这个问题&#xff0c;我们必须限制ZipList的长度和entry大小。 **问题2&#xff1a;**但是…

Head、Neck、Backbone介绍

在深度学习中&#xff0c;通常将模型分为三个部分&#xff1a;backbone、neck 和 head。 Backbone&#xff1a;backbone 是模型的主要组成部分&#xff0c;通常是一个卷积神经网络&#xff08;CNN&#xff09;或残差神经网络&#xff08;ResNet&#xff09;等。backbone 负责…

Redis-Redis缓存高可用集群

1、Redis集群方案比较 哨兵模式 在redis3.0以前的版本要实现集群一般是借助哨兵sentinel工具来监控master节点的状态&#xff0c;如果master节点异常&#xff0c;则会做主从切换&#xff0c;将某一台slave作为master&#xff0c;哨兵的配置略微复杂&#xff0c;并且性能和高可…

视频直播美颜SDK全面解析:美颜SDK技术对比

美颜SDK的出现&#xff0c;为直播主和用户提供了更丰富的美颜体验。 一、美颜SDK的基本原理 美颜SDK多种技术协同工作&#xff0c;使得直播画面更加细腻、自然&#xff0c;给用户带来更好的视觉感受。不同的SDK可能采用不同的算法和处理流程&#xff0c;从而产生各具特色的美…

深度学习图像修复算法 - opencv python 机器视觉 计算机竞赛

文章目录 0 前言2 什么是图像内容填充修复3 原理分析3.1 第一步&#xff1a;将图像理解为一个概率分布的样本3.2 补全图像 3.3 快速生成假图像3.4 生成对抗网络(Generative Adversarial Net, GAN) 的架构3.5 使用G(z)生成伪图像 4 在Tensorflow上构建DCGANs最后 0 前言 &#…