【问题标题】:How to update a range in segment tree while maintaining max and min?如何在保持最大值和最小值的同时更新段树中的范围?
【发布时间】:2012-08-02 14:32:55
【问题描述】:

我正在从一组数据中实现分段树,并且我还想在更新一系列数据时保持树的最大/最小值。这是我遵循本教程http://p--np.blogspot.com/2011/07/segment-tree.html 的初步方法。 不幸的是它根本不起作用,逻辑对我来说很有意义,但我对be 有点困惑,我想知道这是data 数组的范围吗?还是树的实际范围?据我了解,max_segment_tree[1] 应包含[1, MAX_RANGE] 范围的max,而min_segment_tree[1] 应包含[1, MAX_RANGE] 范围的min

int data[MAX_RANGE];
int max_segment_tree[3 * MAX_RANGE + 1];
int min_segment_tree[3 * MAX_RANGE + 1];
void build_tree(int position, int left, int right) {
    if (left > right) {
        return;
    }
    else if (left == right) {
        max_segment_tree[position] = data[left];
        min_segment_tree[position] = data[left];
        return;
    }

    int middle = (left + right) / 2;
    build_tree(position * 2, left, middle);
    build_tree(position * 2 + 1, middle + 1, right);
    max_segment_tree[position] = max(max_segment_tree[position * 2], max_segment_tree[position * 2 + 1]);
    min_segment_tree[position] = min(min_segment_tree[position * 2], min_segment_tree[position * 2 + 1]);
}

void update_tree(int position, int b, int e, int i, int j, int value) {
    if (b > e || b > j || e < i) {
        return;
    }

    if (i <= b && j >= e) {
        max_segment_tree[position] += value;
        min_segment_tree[position] += value;
        return;
    }

    update_tree(position * 2 , b , (b + e) / 2 , i, j, value);
    update_tree(position * 2 + 1 , (b + e) / 2 + 1 , e , i, j, value);

    max_segment_tree[position] = max(max_segment_tree[position * 2], max_segment_tree[position * 2 + 1]); 
    min_segment_tree[position] = min(min_segment_tree[position * 2], min_segment_tree[position * 2 + 1]);
}

编辑 添加测试用例:

#include <iostream>
#include <iomanip>
#include <vector>
#include <string>
#include <algorithm>
#include <map>
#include <set>
#include <utility>
#include <stack>
#include <deque>
#include <queue>
#include <fstream>
#include <functional>
#include <numeric>

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cassert>

using namespace std;

const int MAX_RANGE = 20;
int data[MAX_RANGE];
int max_segment_tree[2 * MAX_RANGE];
int min_segment_tree[2 * MAX_RANGE];
int added_to_interval[2 * MAX_RANGE] = {0};

void update_bruteforce(int x, int y, int z, int &smallest, int &largest) {
    for (int i = x - 1; i < y; ++i) {
        data[i] += z;       
    }

    // update min/max
    smallest = data[0];
    largest = data[0];
    for (int i = 0; i < MAX_RANGE; ++i) {
        if (data[i] < smallest) {
            smallest = data[i];
        }

        if (data[i] > largest) {
            largest = data[i];
        }
    }
}

void build_tree(int position, int left, int right) {
    if (left > right) {
        return;
    }
    else if (left == right) {
        max_segment_tree[position] = data[left];
        min_segment_tree[position] = data[left];
        return;
    }

    int middle = (left + right) / 2;
    build_tree(position * 2, left, middle);
    build_tree(position * 2 + 1, middle + 1, right);
    max_segment_tree[position] = max(max_segment_tree[position * 2], max_segment_tree[position * 2 + 1]);
    min_segment_tree[position] = min(min_segment_tree[position * 2], min_segment_tree[position * 2 + 1]);
}

void update_tree(int position, int b, int e, int i, int j, int value) {
    if (b > e || b > j || e < i) {
        return;
    }

    if (i <= b && e <= j) {
        max_segment_tree[position] += value;
        min_segment_tree[position] += value;
        added_to_interval[position] += value;
        return;
    }

    update_tree(position * 2 , b , (b + e) / 2 , i, j, value);
    update_tree(position * 2 + 1 , (b + e) / 2 + 1 , e , i, j, value);

    max_segment_tree[position] = max(max_segment_tree[position * 2], max_segment_tree[position * 2 + 1]) + added_to_interval[position]; 
    min_segment_tree[position] = min(min_segment_tree[position * 2], min_segment_tree[position * 2 + 1]) + added_to_interval[position];
}

void update(int x, int y, int value) {
    // memset(added_to_interval, 0, sizeof(added_to_interval));
    update_tree(1, 0, MAX_RANGE - 1, x - 1, y - 1, value);
}

namespace unit_test {
    void test_show_data() {
        for (int i = 0; i < MAX_RANGE; ++i) {
            cout << data[i] << ", ";
        }

        cout << endl << endl;
    }

    void test_brute_force_and_segment_tree() {
        // arrange
        int number_of_operations = 100;
        for (int i = 0; i < MAX_RANGE; ++i) {
            data[i] = i + 1;
        }

        build_tree(1, 0, MAX_RANGE - 1);

        // act
        int operation;
        int x;
        int y;
        int z;
        int smallest = 1;
        int largest = MAX_RANGE;

        // assert
        while (number_of_operations--) {
            operation = rand() % 1; 
            x = 1 + rand() % MAX_RANGE;
            y = x + (rand() % (MAX_RANGE - x + 1));
            z = 1 + rand() % MAX_RANGE;

            if (operation == 0) {
                z *= 1;
            }
            else {
                z *= -1;    
            }

            cout << "left, right, value: " << x - 1 << ", " << y - 1 << ", " << z << endl;
            update_bruteforce(x, y, z, smallest, largest);
            update(x, y, z);
            test_show_data();

            cout << "correct:\n";
            cout << "\tsmallest = " << smallest << endl;
            cout << "\tlargest = " << largest << endl;

            cout << "possibly correct:\n";
            cout << "\tsmallest = " << min_segment_tree[1] << endl;
            cout << "\tlargest = " << max_segment_tree[1] << endl;
            cout << "\n--------------------------------------------------------------\n";
            cin.get();
        }
    }
}

int main() {
    unit_test::test_brute_force_and_segment_tree();
}      

【问题讨论】:

  • 它究竟是如何失败的——错误的值、运行时错误?有查询功能吗?此外,您尝试支持的操作是“为一个范围内的所有数字添加一个值”,然后回答一个范围内的最小值/最大值查询,对吗?
  • @IvanVergiliev:它给了我错误的答案,是的,我想更新[left, right] 范围内的所有值,但仍保持树的max/min。没有必要查询范围[left, right],因为我只想查看max/min。谢谢。
  • 只是关于您的单元测试的一个小注释(您可能已经知道,但仍然知道):自动化测试通常比那些需要人工交互的测试要好。因此,我不会手动比较结果并按下一个键,而是使用断言或类似的东西,以便它们在正确时继续运行,并在出现问题时立即中断。此外,rand() % 1 始终为零,您可能想要rand() &amp; 1rand() % 2
  • @IvanVergiliev:谢谢。我经常在看到几个正确的测试用例后断言。在此之前,我发现控制台输出更容易可视化和跟踪我的错误。 rand() % 1 真的是我的错 :(。深夜编码的后果。

标签: c++ algorithm data-structures segment-tree


【解决方案1】:

[b, e]是范围,被*_segment_tree[ position ]所覆盖,[i, j]是当前查询范围。
关于范围存储:
*_segment_tree[ 1 ] 保存整个数据数组的ma​​x/min - 它是树的根,因为基于数组的二叉树有从 1 开始编入索引。这是因为树的第 n 个节点的子节点编号为 2*n2*n + 1,以及 0 不能用作 n,因为在那种情况下 2*n = n。因此,如果 *_segment_tree[k] 持有 data[b, e] 的 min/max,则 *segment_tree[ 2*k ] 持有data[ b, ( b + e ) / 2 ]*segment_tree[ 2*k + 1 ] - data[ ( b + e ) / 2 + 1, e ] - 你可以在代码中看到这些指标。

【讨论】:

    【解决方案2】:

    您需要分别存储每个间隔的最大值/最小值,以及添加了哪些值(只是它们的总和)。以下是它可能出错的原因:

    假设我们正在为数组 [5, 1, 3, 7] 构建一棵树(我在这里只展示最小树)。树看起来像这样:

       1
     1   3
    5 1 3 7
    

    然后我们将整个区间加 1。树看起来像这样:

       2
     1   3
    5 1 3 7
    

    因为更新间隔完全覆盖第一个节点后,传播已在第一个节点上停止。

    然后在范围 [0-1] 中加 1。这个范围并没有覆盖第一个节点的整个区间,所以我们更新children,然后将整个区间的最小值(即第一个节点的值)设置为节点2和3的最小值。这里是结果树:

       2
     2   3
    5 1 3 7
    

    这就是它出错的地方 - 数组中没有元素 2,但树声称整个数组的最小值是 2。发生这种情况是因为树的较低级别实际上从未获得它们的值已经增加 - 第二个节点不知道它的值不是 [5, 1] 而是 [6, 2]。

    为了使其正常工作,您可以添加第三个数组来保存已添加到整个间隔的值 - 例如,int added_to_interval[3 * MAX_RANGE + 1];。然后,当您更新整个间隔时(i &lt;= b &amp;&amp; j &gt;= e 的情况),您还必须将added_to_interval[position] 增加为value。此外,当上树以根据子节点的值更新节点时,您还必须添加已添加到整个区间的节点(例如 max_segment_tree[position] = max(max_segment_tree[position * 2], max_segment_tree[position * 2 + 1]) + added_to_interval[position];)。

    编辑:

    以下是代码的更改以使其正常工作:

    if (i <= b && j >= e) {
        max_segment_tree[position] += value;
        min_segment_tree[position] += value;
        added_to_interval[position] += value;
        return;
    }
    

    ...

    update_tree(position * 2 , b , (b + e) / 2 , i, j, value);
    update_tree(position * 2 + 1 , (b + e) / 2 + 1 , e , i, j, value);
    
    max_segment_tree[position] = max(max_segment_tree[position * 2], max_segment_tree[position * 2 + 1]) + added_to_interval[position];
    min_segment_tree[position] = min(min_segment_tree[position * 2], min_segment_tree[position * 2 + 1]) + added_to_interval[position];
    

    我没有对它进行广泛的测试 - 我把它留给你,但我尝试了一堆似乎可以正常工作的例子。

    另外,我认为数组中不需要 3 * MAX_RANGE + 1 个元素 - 2 * MAX_RANGE 或类似的东西就足够了。

    【讨论】:

    • 非常感谢!我会试一试,让你知道。顺便说一句,如果我们向下传播到每个孩子,时间还会是O(logN)吗?
    • 不,如果你向下传播到每个孩子,性能会比使用数组更差,因为对于一个长度范围 l,你必须访问大约 2*l 节点,而使用数组,您可以访问范围内的所有值。
    • 啊,明白了。抱歉问了一个非常愚蠢的问题。使用分而治之的情况会更糟。顺便说一下,我对 1000 条记录进行了快速测试,结果与预期的最大值一样,但最小值却失败了,这很奇怪。
    • 如果你能找到一个足够小的测试用例,我可以研究一下——或者你可以练习一下你的调试技巧。
    猜你喜欢
    • 2014-03-22
    • 1970-01-01
    • 1970-01-01
    • 2017-08-22
    • 2016-08-31
    • 2015-05-18
    • 1970-01-01
    • 1970-01-01
    • 2020-08-27
    相关资源
    最近更新 更多