• 【数据结构】线段树(Segment Tree)

     

    假设我们现在拿到了一个非常大的数组,对于这个数组里面的数字要反复不断地做两个操作。

    分享图片

    1、(query)随机在这个数组中选一个区间,求出这个区间所有数的和。

    分享图片

    2、(update)不断地随机修改这个数组中的某一个值。

    分享图片

    时间复杂度:

    分享图片

    枚举

    枚举L~R的每个数并累加。

    • query:O(n)

    找到要修改的数直接修改。

    • update:O(1)

    如果query与update要做很多很多次,query的O(n)会被卡住,所以时间复杂度会非常慢。那么有没有办法把query的时间复杂度降成O(1)呢?其中一种方法如下:

    • 先建立一个与a数组一样大的数组。

    分享图片

    • s[1]=a[1];s[2]=a[1]+a[2];s[3]=a[1]+a[2]+a[3];...;s[n]=a[1]+a[2]+a[3]+...+a[n](在s数组中存入a的前缀和)

    分享图片

    • 此时a[L]+a[L+1]+...+a[R]=s[R]-s[L-1],query的时间复杂度降为O(1)。
    • 但若要修改a[k]的值,随之也需修改s[k],s[k+1],...,s[n]的值,时间复杂度升为O(n)。

    前缀和

    query:O(1)

    update:O(n)

    • 我们发现,当我们想尽方法把其中一个操作的时间复杂度改成O(1)后,另一个操作的时间复杂度就会变为O(n)。当query与update的操作特别多时,不论用哪种方法,总体的时间复杂度都不会特别快。
    • 所以,我们将要讨论一种叫线段树的数据结构,它可以把这两个操作的时间复杂度平均一下,使得query和update的时间复杂度都落在O(n log n)上,从而增加整个算法的效率。

    线段树

    假设我们拿到了如下长度为6的数组:

    分享图片

    在构建线段树之前,我们先阐述线段树的性质:

    1、线段树的每个节点都代表一个区间。

    2、线段树具有唯一的根节点,代表的区间是整个统计范围,如[1,N]。

    3、线段树的每个叶节点都代表一个长度为1的元区间[x,x]。

    4、对于每个内部节点[l,r],它的左子结点是[l,mid],右子节点是[mid+1,r],其中mid=(l+r)/2(向下取整)。

    依照这个数组,我们构建如下线段树(结点的性质为sum):

    分享图片

    若我们要求[2-5]区间中数的和:

    分享图片

    若我们要把a[4]改为6:

    • 先一层一层找到目标节点修改,在依次向上修改当前节点的父节点。

     

     

    分享图片

    接下来的问题是:如何保存这棵线段树?

    • 用数组存储。

    分享图片

    若我们要取node结点的左子结点(left)与右子节点(right),方法如下:

    • left=2*node+1
    • right=2*ndoe+2

    举结点5为例(左子结点为节点11,右子节点为节点12):

    • left5=2*5+1=11
    • right5=2*5+2=12

    接下来给出建树的代码:

     

    #include<bits/stdc++.h>
    using namespace std; const int N = 1000; int a[] = {1, 3, 5, 7, 9, 11}; int size = 6; int tree[N] = {0}; //建立范围为a[start]~a[end] 
    void build(int a[], int tree[], int node/*当前节点*/, int start, int end){ //递归边界(即遇到叶子节点时) 
        if (start == end){ //直接存储a数组中的值 
            tree[node] = a[start]; } else { //将建立的区间分成两半 
            int mid = (start + end) / 2; int left  = 2 * node + 1;//左子节点的下标 
            int right = 2 * node + 2;//右子节点的下标 //求出左子节点的值(即从节点left开始,建立范围为a[start]~a[mid])
     build(a, tree, left, start, mid); //求出右子节点的值(即从节点right开始,建立范围为a[start]~a[mid])
            build(a, tree, right, mid+1, end); //当前节点的职位左子节点的值加上右子节点的值 
            tree[node] = tree[left] + tree[right]; } } int main(){ //从根节点(即节点0)开始建树,建树范围为a[0]~a[size-1]
        build(a, tree, 0, 0, size-1); for(int i = 0; i <= 14; i ++) printf("tree[%d] = %d\n", i, tree[i]); return 0; }

    运行结果:

    分享图片

    update操作:

    • 确定需要改的分支,向下寻找需要修改的节点,再向上修改节点值。
    •  与建树的函数相比,update函数增加了两个参数x,val,即把a[x]改为val。

    例:把a[x]改为6(代码实现)

    分享图片

    void update(int a[], int tree[], int node, int start, int end, int x, int val){ //找到a[x],修改值 
        if (start == end){ a[x] = val; tree[node] = val; } else { int mid = (start + end) / 2; int left  = 2 * node + 1; int right = 2 * node + 2; if (x >= start && x <= mid) {//如果x在左分支 
     update(a, tree, start, mid, x, val); } else {//如果x在右分支 
                update(a, tree, right, mid+1, end, x, val); } //向上更新值 
            tree[node] = tree[left] + tree[right]; } } 在主函数中调用: //把a[x]改成6
    update(a, tree, 0, 0, size-1, 4, 6);

     

    运行结果:

    分享图片

    query操作:

    • 向下依次寻找包含在目标区间中的区间,并累加。
    • 与建树的函数相比,query函数增加了两个参数L,Rl,即把求a的区间[L,R]的和。

    例:求a[2]+a[3]+...+a[5]的值(代码实现)

    分享图片

    int query(int a[], int tree[], int node, int start, int end, int L,int R){ //若目标区间与当时区间没有重叠,结束递归返回0 
        if (start > R || end < L){ return 0; } //若目标区间包含当时区间,直接返回节点值 
        else if (L <=start && end <= R){ return tree[node]; } else { int mid = (start + end) / 2; int left  = 2 * node + 1; int right = 2 * node + 2; //计算左边区间的值 
            int sum_left  = query(a, tree, left, start, mid, L, R); //计算右边区间的值 
            int sum_right = query(a, tree, right, mid+1, end, L, R); //相加即为答案 
            return sum_left + sum_right; } } 在主函数中调用: //求区间[2,5]的和
    int ans = query(a, tree, 0, 0, size-1, 2, 5); printf("ans = %d", ans); 

    运行结果:

    分享图片

    最后,献上完整的代码:

    #include<bits/stdc++.h>
    using namespace std; const int N = 1000; int a[] = {1, 3, 5, 7, 9, 11}; int size = 6; int tree[N] = {0}; //建立范围为a[start]~a[end] 
    void build(int a[], int tree[], int node/*当前节点*/, int start, int end){ //递归边界(即遇到叶子节点时) 
        if (start == end) { //直接存储a数组中的值 
            tree[node] = a[start]; } else { //将建立的区间分成两半 
            int mid = (start + end) / 2; int left  = 2 * node + 1;//左子节点的下标 
            int right = 2 * node + 2;//右子节点的下标 //求出左子节点的值(即从节点left开始,建立范围为a[start]~a[mid])
     build(a, tree, left, start, mid); //求出右子节点的值(即从节点right开始,建立范围为a[start]~a[mid])
            build(a, tree, right, mid+1, end); //当前节点的职位左子节点的值加上右子节点的值 
            tree[node] = tree[left] + tree[right]; } } void update(int a[], int tree[], int node, int start, int end, int x, int val){ //找到a[x],修改值 
        if (start == end){ a[x] = val; tree[node] = val; } else { int mid = (start + end) / 2; int left  = 2 * node + 1; int right = 2 * node + 2; if (x >= start && x <= mid) {//如果x在左分支 
     update(a, tree, left, start, mid, x, val); } else {//如果x在右分支 
                update(a, tree, right, mid+1, end, x, val); } //向上更新值 
            tree[node] = tree[left] + tree[right]; } } //求a[L]~a[R]的区间和 
    int query(int a[], int tree[], int node, int start, int end, int L,int R){ //若目标区间与当时区间没有重叠,结束递归返回0 
        if (start > R || end < L){ return 0; } //若目标区间包含当时区间,直接返回节点值 
        else if (L <=start && end <= R){ return tree[node]; } else { int mid = (start + end) / 2; int left  = 2 * node + 1; int right = 2 * node + 2; //计算左边区间的值 
            int sum_left  = query(a, tree, left, start, mid, L, R); //计算右边区间的值 
            int sum_right = query(a, tree, right, mid+1, end, L, R); //相加即为答案 
            return sum_left + sum_right; } } int main(){ //从根节点(即节点0)开始建树,建树范围为a[0]~a[size-1]
        build(a, tree, 0, 0, size-1); for(int i = 0; i <= 14; i ++) printf("tree[%d] = %d\n", i, tree[i]); printf("\n"); //把a[x]改成6
        update(a, tree, 0, 0, size-1, 4, 6); for(int i = 0; i <= 14; i ++) printf("tree[%d] = %d\n", i, tree[i]); printf("\n"); //求区间[2,5]的和
        int ans = query(a, tree, 0, 0, size-1, 2, 5); printf("ans = %d", ans); return 0; }

    运行结果:

    分享图片

    学习视频链接

    相关文章
    相关标签/搜索
    福彩网天下彩特彩吧 囊谦县| 获嘉县| 永胜县| 修水县| 旌德县| 弋阳县| 青州市| 广安市| 区。| 奎屯市| 商水县| 肥城市| 黄浦区| 元谋县| 如皋市| 靖边县| 班玛县| 五寨县| 抚远县| 民县| 蒲城县| 宁夏| 宣武区| 抚顺市| 连山| 新蔡县| 南岸区| 松江区| 高州市| 朔州市| 青岛市| 旅游| 漳平市| 海口市| 类乌齐县| 大洼县| 内乡县| 宝应县| 英超| 和田市| 大邑县| 晋城| 开江县| 抚远县| 旬邑县| 马山县| 鲁甸县| 鹿泉市| 宁河县| 福海县| 尼勒克县| 富阳市| 庆云县| 大邑县| 囊谦县| 宁强县| 淅川县| 湖北省| 大埔区| 樟树市| 苏尼特左旗| 石嘴山市| 县级市| 苏尼特右旗| 沾益县| 绥江县| 鲁甸县| 贵阳市| 稻城县| 沅陵县| 烟台市| 莆田市| 丰原市| 新安县| 剑川县| 鲁山县| 克什克腾旗| 普兰县| 佛教| 嵊州市| 嘉禾县| 米林县| 旬阳县| 太白县| 天峻县| 岑巩县| 远安县| 荥阳市| 社会| 车险| 吴堡县| 开原市| 桐柏县| 丘北县| 凌源市| 广水市| 佛冈县| 广汉市| 临沧市| 龙陵县| 周口市| 上高县| 银川市| 监利县| 太仆寺旗| 恩施市| 灯塔市| 黔西县| 玉龙| 类乌齐县| 清涧县| 辽源市| 瓦房店市| 芷江| 揭阳市| 宜宾县| 乃东县| 紫阳县| 万宁市| 青州市| 达拉特旗| 黄石市| 逊克县| 大姚县| 十堰市| 秀山| 汉中市| 绍兴县| 防城港市| 阳山县| 渭源县| 奎屯市| 榆林市| 勐海县| 威信县| 马山县| 灵宝市| 宜丰县| 抚宁县| 谢通门县| 同江市| 通道| 夏河县| 五常市| 武鸣县| 平远县| 水富县| 乃东县| 侯马市| 珲春市| 丹阳市| 侯马市| 泸水县| 炎陵县| 泰来县| 台湾省| 衡阳市| 威宁| 南开区| 桐乡市| 辰溪县| 伊吾县| 汾西县| 新丰县| 山丹县| 邮箱| 南漳县| 聊城市| 信丰县| 安庆市| 临洮县| 梓潼县| 通化县| 黄龙县| 明水县| 巢湖市| 苍溪县| 腾冲县| 碌曲县| 汽车| 闽侯县| 定南县| 石城县| 大冶市| 福鼎市| 黎城县| 棋牌| 龙井市| 高安市| 宣城市| 五峰| 南和县| 北海市| 察雅县| 大石桥市| 长葛市| 岳池县| 长阳| 县级市| 千阳县| 湾仔区| 白沙| 常熟市| 枣强县| 白河县| 新化县| 遂宁市| 铜鼓县| 阿拉尔市| 阳西县| 辛集市| 株洲县| 盐池县| 壶关县| 天祝| 永修县| 神木县| 楚雄市| 潮安县| 新邵县| 驻马店市| 繁峙县| 治多县| 烟台市| 湾仔区| 孝感市| 东光县| 云安县| 沁源县| 墨竹工卡县| 七台河市| 维西| 江津市| 梁河县| 巴林右旗| 抚顺市| 平湖市| 肥东县| 滕州市| 芦溪县| 台湾省| 弥勒县| 丹棱县| 阜南县| 东乡| 古交市| 东方市| 宁城县| 宜城市| 阿图什市| 德惠市| 南康市| 钟祥市| 贵溪市| 东阳市| 汾西县| 蓝田县| 吉首市| 恩平市| 车险| 长泰县| 长子县| 南部县| 巴里| 宜宾市| 右玉县| 长垣县| 竹溪县| 吉隆县| 吉首市| 石门县| 乐至县| 榕江县| 湖南省| 清远市| 内乡县| 五河县| 抚顺市| 徐水县| 永善县| 阿拉善左旗| 镶黄旗| 麻江县| 垣曲县| 方城县| 启东市| 郧西县| 曲水县| 平定县| 抚顺县| 青田县| 宁晋县| 任丘市| 仲巴县| 四会市| 祁门县| 庄河市| 武山县| 清远市| 都兰县| 右玉县| 勐海县| 宁津县| 南雄市| 铜川市| 新平| 工布江达县| 荃湾区| 北票市| 城市| 金华市| 白山市| 华亭县| 海门市| 毕节市| 塔河县| 永善县| 故城县| 南丹县| 花莲市| 屯留县| 监利县| 麻江县| 鲁甸县| 嘉黎县| 文山县| 华蓥市| 平阴县| 吉林省| 平山县| 温宿县| 桂平市| 广德县| 呼图壁县| 白山市| 龙里县| 崇明县| 佛山市| 澄江县| 且末县| 铜山县| 长兴县| 桂东县| 泰安市| 常州市| 闻喜县| 长武县| 台前县| 松江区| 德庆县| 白银市| 茌平县| 綦江县| 浪卡子县| 深泽县| 普格县| 元谋县| 肃北| 北川| 平江县| 屏南县| 莆田市| 新民市| 华亭县| 罗山县| 屯门区| 广昌县| 武乡县| 宝鸡市| 铜陵市| 理塘县| 上饶市| 景泰县| 讷河市| 邵东县| 子长县| 定西市| 裕民县| 广宗县| 和政县| 容城县| 新乡市| 静乐县| 绿春县| 鄄城县| 寿阳县| 察隅县| 华安县| 甘肃省| 鄄城县| 黔南| 宝鸡市| 应用必备| 和林格尔县| 陆河县| 竹北市| 读书| 内黄县| 清丰县| 汝阳县| 庆元县| 阜城县| 宁城县| 泸水县| 章丘市| 新闻| 高阳县| 郎溪县| 杂多县| 黎城县| 甘谷县| 赞皇县| 永安市| 滕州市| 鹤山市| 天等县| 孝昌县| 屏南县| 东乡族自治县| 黄骅市| 科尔| 姜堰市| 鲜城| 渑池县| 乳源| 河间市| 会同县| 岗巴县| 辽阳县| 闵行区| 新余市| 太湖县| 招远市| 福清市| 吉林市| 沽源县| 新源县| 佛冈县| 汕头市| 彰武县| 旺苍县| 长岭县| 柯坪县| 洪江市| 无极县| 垦利县| 柞水县| 竹北市| 卓资县| 玉环县| 通州市| 阜城县| 邯郸市| 云林县| 天等县| 庆元县| 邮箱| 太和县| 昌吉市| 芦山县| 高阳县| 新昌县| 平凉市| 太和县| 德昌县| 沧州市| 阜阳市| 厦门市| 凤冈县| 洛隆县| 施甸县| 那坡县| 博罗县| 寿宁县| 云林县| 武宣县| 锦州市| 永宁县| 乾安县| 治县。| 香港| 临夏县| 交口县| 金山区| 黄冈市| 刚察县| 涡阳县| 巫溪县| 通江县| 沅陵县| 江门市| 连城县| 虹口区| 体育| 普格县| 巩义市| 顺义区| 醴陵市| 专栏| 周宁县| 财经| 白城市| 连平县| 姚安县| 盐山县| 临清市| 宜春市| 宝丰县| 泰安市| 集贤县| 辽中县| 图片| 潜江市| 四平市| 乌什县| 九江县| 聂拉木县| 宁都县| 泽库县| 巨鹿县| 新密市| 工布江达县| 江都市| 潮安县| 新龙县| 堆龙德庆县| 阳谷县| 大田县| 阿拉善盟| 兴和县| 高雄市| 罗江县| 永新县| 林口县| 伊宁县| 石城县| 社会| 休宁县| 攀枝花市| 扎兰屯市| 广元市| 湟源县| 寻甸| 湟源县| 凉山| 冕宁县| 水城县| 永善县| 雅安市| 江口县| 株洲县| 三河市| 文化| 新野县| 衢州市| 永德县| 拜泉县| 新田县| 法库县| 连江县| 鄄城县| 新乡县| 怀集县| 莱芜市| 郑州市| 金溪县| 繁昌县| 大兴区| 永昌县| 遂宁市| 芜湖市| 恭城| 泗洪县| 洱源县| 鲁山县| 峨眉山市| 乡宁县| 微山县| 蓬安县| 华宁县| 青铜峡市| 晋州市| 海林市| 奈曼旗| 温宿县| 惠东县| 上栗县| 瑞安市| 沧源| 蓬莱市| 东乌珠穆沁旗| 隆化县| 资源县| 读书| 绥芬河市| 温泉县| 四平市| 抚州市| 宁明县| 新宁县| 禄丰县| 青神县| 眉山市| 绵竹市| 左贡县| 晴隆县| 满城县| 南宁市| 北安市| 青河县| 雅安市| 肇州县| 临桂县| 游戏| 土默特左旗| http://www.mbripy.fit http://wnrxvl.fit http://www.tzlljw.fit http://eytjli.fit http://www.dicedk.fit http://www.vrbgdj.fit http://wap.tmhrcj.fit http://www.kylbkd.fit http://wap.nqalco.fit http://m.nqkcre.fit http://lzycce.fit http://www.amoktr.fit http://wap.qqxdeu.fit http://m.vgptbj.fit http://gvapcg.fit http://www.upjiob.fit http://wap.vqrcbk.fit http://www.fruwrl.fit