知道如何将这个O(n ^ 2)算法转换为O(n)

时间:2010-06-22 13:24:28

标签: algorithm data-structures

我有以下算法扫描大型圆形阵列(数据)。在数组中的某个点,我需要查看过去的值(0 =最新数据点,n =最旧的数据点)并确定是否有比当前值低5%的值。我最终编写了一个O(n ^ 2)算法,该算法运行正常,但这不能扩展。

        const int numberOfDataPointsInPast = 1000;
        int numberOfDataPoints = 0;
        for (int i = numberOfDataPointsInPast; i >= 0; i--)
        {
            double targetPoint = data[i] * 0.95;
            for (int j = i + numberOfDataPointsInPast; j > i; j--)
            {
                if (data[j] <= targetPoint)
                {
                    numberOfDataPoints++;
                    break;
                }
            }
        }

知道如何将其转换为O(n)算法吗?谢谢!

9 个答案:

答案 0 :(得分:7)

迭代数组时存储最低值。这需要创建一个min变量并在每一步中执行比较检查。不要将所有先前的值与新值进行比较,而只将其与最低值进行比较。

答案 1 :(得分:2)

编辑:

在考虑了它之后,可以使用简单的O(n)时间算法,而不需要RMQ或树(参见下面我的答案的前一部分)。

给定一个数组A [1 ... n]和一个窗口宽度W,你需要找到最小的A [i,... i + W],给定i。

为此,您执行以下操作。

将A [1 ... n]拆分为大小为W-1的连续块。 B1,B2,...... B(W-1)。

对于每个块B,再保留两个名为BStart和BEnd的块。

BStart [i] = B 1,B [2],...,B [i]的最小值。

BEnd [i] = B [W-1],B [W-2],...,B [W-i]的最小值。

这可以在每个块的O(W)时间内完成,因此O(n)时间总计。

现在给定一个i,子阵列A [i ... i + W]将跨越两个连续的块,比如B1和B2。

分别使用B1End和B2Start找到从i到块B1的最小值,以及块B2到i + w的开始。

这是O(1)时间,所以总O(n)。

对于圆形数组C [1 .... n],您需要做的就是在

上运行上面的内容

A [1 .... 2n],基本上是两个C连接在一起的副本,即A [1 ... n] = C [1 ... n]和A [n + 1 ... 2n ] = C [1 ... n]


上一篇文章。

确定。假设我这次正确地理解了你的问题......

可以在O(n)时间和O(n)空间中进行。

事实上,您可以将窗口大小更改为您喜欢的任何数字,使不同的元素有所不同,并且仍然有效!

给定一个数组A [1 ... n],它可以在O(n)时间和O(n)空间中进行预处理,以回答以下形式的查询:{em>常量<{1}}

这称为Range Minimum Query问题。

因此从理论上讲,可以在O(n)时间内完成。

使用树会给你O(nlogW)时间,其中W是窗口的大小,并且在实践中可能比RMQ好得多,因为我预计隐藏的常量可能会使RMQ变差。

您可以按如下方式使用树。

向后开始并插入W元素。找到最小值并推入堆栈。 现在删除第一个元素并插入第(W + 1)个元素。找到最小值,推进堆栈。

继续这样做。总处理时间为O(nlogW)。

最后你有一堆最小值,当你现在第二次走数组时,你可以继续弹出,这次按照正确的顺序,搜索0.95 *目标。

另外,你的问题不是很清楚,你说它是一个循环缓冲区,但你似乎没有用长度进行模数运算。编码后,你的算法是O(n),而不是O(n ^ 2),因为你的窗口大小是一个常数。

答案 2 :(得分:2)

我认为在O(n)中不可能这样做,因为通过用O(n)求解它你可以用O(n)对它进行排序,这是不可能的。 (最小,排序为O(nlogn))。

编辑 - 减少对此问题的排序

假设可以告诉每个点过去有多少点的值小于x%(这里x是5 - 但x也可以是0,那么计数将是过去的任何小点)。

现在 - 假设您要对n个元素的数组进行排序 如果点a的值大于点b,则可以获得O(n)中所有元素的过去较小点的数量,点a的计数也将是大于b的计数(因为数组是圆形的)。所以这个问题实际上产生了一个函数,从值到保留订单的计数 现在 - 新值绑定在o和n之间,这可以按时间n进行排序。

如果我错了,请纠正我(可能是我一开始并不理解这个问题)。

答案 3 :(得分:2)

您可以为包含按升序排序的当前“窗口”元素的buffArray元素维护数组numberOfDataPointsInPast

每次迭代:

  • 检查当前元素是否低于0.95 * buffArray[0]并执行必要的操作。
  • i+numberOfDataPointsInPast删除“窗口”(即buffArray')之外的元素。
  • 将新元素(即i')添加到buffArray维护排序顺序。

这不是我所理解的O(N),但肯定比O(N ^ 2)更有效,因为在排序数组中添加和删除元素是O(log N)。我怀疑最终效率是O(N log(W)),其中W是numberOfDataPointsInPast

答案 4 :(得分:2)

我想我理解你的要求......我将重述这个问题:

给定:滑动缓冲区大小K,以及大小为N的数据数组&gt; K,指数从0到N-1。

计算:计算点数j,例如K&lt; = j&lt; N-1,并且集合{data [j-1],data [j-2],data [j-3],... data [jK]}包含至少一个值为&lt; = 0.95 *数据[j]。

这可以通过以下方式实现:

  1. 使用最多插入/移除O(log N)成本的数据结构对点{data [0],data [1],... data [K-1]}进行排序。 / p>

  2. 将计数器R初始化为0,将j初始化为K。

  3. 检查已排序的数组以查看最低点是否为&lt; = data [j] * 0.95;如果是这样,增加R。

  4. 从已排序的数组中删除数据[j-K],并将数据[j]插入到已排序的数组中。

  5. 增量j

  6. 如果j < N,回到第3步。

  7. 这里的关键是选择合适的数据结构。我很确定二叉树会起作用。如果增量插入成本为O(log N),则总运行时间为O(N log N)。

答案 5 :(得分:1)

您可以在过去对第一个numberOfDataPointsInPast进行排序,即n log(n)。然后进行二进制搜索log(n),找到通过5%测试的最低数据点。这将告诉你我相信,在n log(n)时间内,numberOfDataPointsInPast中有多少点将通过测试。

答案 6 :(得分:0)

迭代需要从底部开始并递增(保持过去的最小值)。现在,正如发布的那样,算法总是回顾过去,而不是前进并记住过去的最小值。

随着新点的增加,数据点的范围只能增加上限或下限。随着下限的减小,保持下限就是必要的。任何超过下限/ 0.95的新点都是可以接受的(因为下限总是在过去):

const int numberOfDataPointsInPast = 1000; 
int numberOfDataPoints = 0; 
double lb = NAN;
for (int i = 0; i < numberOfDataPointsInPast; i++) 
{ 
    if ( lb == NAN || data[i] < lb ) {
        lb = data[i];
    }
    if ( data[i] >= lb / 0.95 ) {
        numberOfDataPoints++
    }
} 

答案 7 :(得分:0)

试试这个:

始终保持缓冲区中元素的两个指针。一个是遇到的最小值,另一个是下一个mimumum(按下一个最高的增量值)。请记住这些是指针到缓冲区。

在通过缓冲区的每个步骤中,确定当前值是否小于或等于min1或min2指向的值,如果是,则更新min1或min2以指向当前位置。 否则,如果通过指针算术,min1或min2的值在缓冲区中是1500个位置,则需要确定它是哪一个并相应地重新调整min1或min2,即min1指向min2,min2设置为指向当前位置, min2只是设置为指向当前位置。

min1或min2指向的值是否小于当前值的15%,然后可以通过简单的比较确定......

答案 8 :(得分:0)

您有两个选择:

  1. 排序 - O(n log n)

  2. Median of Medians algorithm