如何提高该算法的时间复杂度

时间:2011-02-24 16:53:08

标签: algorithm optimization data-structures time-complexity

这是一个简单的问题:我有一个长度为N的数组,给定2个边界(a,b)的函数返回a和b之间数组中所有元素的总和。

现在,这显然是O(N)时间复杂度......但是如果我想提高效率(如log(N)),使用第二个存储部分和的数据结构,我该如何实现呢?

我在想二叉树但找不到算法。当然我可以创建一个NxN矩阵,但它太多了。我想要一些不需要太多空间的东西,让我有一个对数时间复杂度;任何想法?

更新:我没有明确说明..但是:

  1. 边界在数组的索引上,而不是值
  2. 数组是动态的,值可以改变,我不希望线性复杂度来改变值!因此,我认为简单的部分和算法不起作用..
  3. 无并发编程(1个CPU)

7 个答案:

答案 0 :(得分:10)

好吧,您可以使用另一个相同大小的数组来存储部分总和。然后,无论何时给定边界,您都可以减去部分和,并获得该间隔中元素的总和。例如:

Elements:    1 2 3 4  5  6
Partial_Sum: 1 3 6 10 15 21

让我们说数组从index = 0开始,你想要区间[1,3]中的元素总和:

// subtract 1 from the index of the second sum, because we
// want the starting element of the interval to be included.
Partial_Sum[3] - Partial_Sum[1-1] = 10 - 1 = 9

答案 1 :(得分:1)

我似乎记得prefix sums可用于在O(lg n)时间内回答此类查询。

编辑:那里我有点太快了 - 它可以做得更快。如果您花费O(n)时间(和O(n)额外内存)预先计算前缀和数组(在单核计算机上),则可以在O(1)时间内通过减去每个查询的答案该数组的适当元素。如果您碰巧有n个处理器,则可以在O(lg n)时间内完成预计算。

答案 2 :(得分:1)

我一定错过了关于这个问题的一些事情。给定一个部分和的数组,你应该能够获得恒定的复杂性 - 从ab的元素总和是partial_sums[b] - partial_sums[a](或者如果你不能假设{{1} },a<b)。

也许你在谈论partial_sums[max(a,b)] - partial_sums[min(a,b)]a作为价值而不是位置的界限?如果是这样,那么假设您的数组已排序,您可以通过对ba的位置使用二进制搜索来获得O(log N)复杂度,然后按上述方式减去。如果数组不是(并且不能)排序,则可以通过创建对原始对象的引用数组,对引用进行排序以及为这些引用生成部分和来完成相同的操作。这增加了预处理的工作量,但保留了查询的O(log N)。

编辑:使数组动态化应该没有任何影响,至少在计算复杂性方面。如果只在主数组的末尾插入/删除,则可以在部分和数组中以常量时间插入/删除。对于插入,您可以执行以下操作:

b

要从最后删除,只需使用N = N + 1 main_array[N] = new_value partial_sum[N] = partial_sum[N-1] + new_value ,并忽略之前两个数组末尾的值。

如果需要在主阵列的中间支持插入/删除,则需要线性时间。更新部分和数组也可以在线性时间内完成。例如,要在索引N = N - 1处插入new_value,您可以执行以下操作:

i

删除是类似的,除了你从删除点到最后一步,然后减去要删除的值。

我确实说“应该”是有原因的 - 有一个可能的警告。如果您的数组是非常动态的,则内容是浮点数,您可以/将遇到问题:在插入/删除元素时重复添加和减去值(最终将 em>)导致舍入错误。在这种情况下,你有两个选择:一个是放弃这个想法。另一个使用更多存储 - 当您添加/删除项目时,保持已添加/减去的元素的绝对值的运行总和。当/如果这超过了该点的部分和的选定百分比,则重新计算部分和(并将运行总和归零)。

答案 3 :(得分:1)

好吧也许我找到了一个解决方案,在变量值和总和上都有log(n),并且线性空间开销。

我将尝试解释:我们构建一个二叉树,其中叶子是数组值,按照它们在数组中的顺序(未排序,不是排序树)。

然后我们一次创建树自下而上合并2个叶子,并将它们的总和放在父亲中。例如,如果数组的长度为4,值为[1,5,3,2],我们将得到一个有3个级别的树,根将是总和(11),其他将是1 + 5-&gt; ; 6和3 + 2-> 5

现在,要更改一个值,我们必须更新这个树(log n),并计算我算出这个算法的总和(log n):

acc = 0 //累加器

从下界开始,我们走上树。我向左走(当前节点是正确的孩子)然后是acc + = current_node - parent_node。如果我们向右走(当前节点是左子节点),我们什么都不做。

然后我们从上限做同样的事情,当然在这种情况下它是相反的(如果我们向上走的话我们会做总和)

我们交替进行,一次在下限,一次在上限。如果我们得到的2节点实际上是同一个节点,那么我们将该节点的值与累加器相加,然后返回累加器。

我知道我没有解释清楚......我在解释时遇到了一些困难.. 有人明白吗?

答案 4 :(得分:0)

有两种情况:静态数据或动态(变化)数据

1。静态数据

对于静态数据,这是一个众所周知的问题。首先计算“sum table”(n + 1个元素的数组):

st[0] = 0;
for (int i=0,n=x.size(); x<n; x++)
    st[i] = st[i-1] + x[i];

然后计算您需要做的ab之间的元素总和

sum = st[b] - st[a];

该算法也可用于更高维度。例如,如果你需要计算(x0,y0)和(x1,y1)之间所有值的总和,你可以做到

sum = st[y0][x0] + st[y1][x1] - st[y0][x1] - st[y1][x0];

其中st[y][x](x, y)上方和左侧所有元素的总和。

总和表的计算是 O(n)操作(其中 n 是元素的数量,例如2d矩阵的rows*columns ),但是对于非常大的数据集(例如图像),可以编写一个可以在 O(n / m)中运行的optimal parallel version,其中 m 是可用CPU数量。这是我发现相当令人惊讶的事情......

在第二种情况下进行简单(单线程)和表计算:

for (int y=0; y<h; y++)
  for (int x=0; x<w; x++)
    st[y+1][x+1] = st[y+1][x] + st[y][x+1] - st[y][x] + value[y][x];

2.动态数据

对于动态数据,您可以使用所谓的“多分辨率”方法:

void addDelta(std::vector< std::vector< int > >& data,
              int index, int delta)
{
    for (int level=0,n=data.size(); level<n; level++)
    {
        data[level][index] += delta;
        index >>= 1;
    }
}

int sumToIndex(std::vector< std::vector< int > >& data,
               int index)
{
    int result = 0;
    for (int level=0,n=data.size(); level<n; level++)
    {
        if (index & 1) result += data[level][index-1];
        index >>= 1;
    }
    return result;
}

int sumRange(std::vector< std::vector< int > >& data,
             int a, int b)
{
    return sumToIndex(data, b) - sumToIndex(data, a);
}

基本上在每个“级别”,一个单元格保存下一个更精细级别的两个单元格的总和。当您将数据添加到最低(更高分辨率)级别时,您还必须将其添加到更高级别(这是addDelta所做的)。 要计算从0到x的所有值的总和,您可以使用更高级别来节省计算...请参阅下图:

enter image description here

最后,为了得到从ab的总和,您只需计算从0开始的这两个总和之间的差异。

答案 5 :(得分:0)

根据问题陈述,您将获得一个数字数组,以及一对索引,这些索引表示要对内容求和的区间范围。由于此问题中没有搜索,因此将数据表示为二叉树结构在时间或空间复杂性方面没有任何优势。

由于您允许在多处理器环境中执行您的解决方案,因此您将“卡住”O(N)。

如果允许您的解决方案在多处理器环境中执行,则最佳复杂度为O(N / p + p + 1),其中p是可用处理器的数量。这是因为,在这种情况下,您可以将间隔划分为p个子间隔(+1),并行区间(N / p)的总和,然后将每个个体的结果相加子间隔(+ p),完成计算。

答案 6 :(得分:0)

创建一个平衡的二叉树,根据它们的值对数字进行排序。这样做可以使每个操作都需要线性时间。在每个节点中存储该节点下的所有值的总和。要计算范围[a,b]中的值的总和,您必须在a和b的下一个树上下载并添加适当的值。 O(ln n)每次计算总和或更改值。