我有以下算法扫描大型圆形阵列(数据)。在数组中的某个点,我需要查看过去的值(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)算法吗?谢谢!
答案 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]。
这可以通过以下方式实现:
使用最多插入/移除O(log N)成本的数据结构对点{data [0],data [1],... data [K-1]}进行排序。 / p>
将计数器R初始化为0,将j初始化为K。
检查已排序的数组以查看最低点是否为&lt; = data [j] * 0.95;如果是这样,增加R。
从已排序的数组中删除数据[j-K],并将数据[j]插入到已排序的数组中。
增量j
如果j < N,回到第3步。
这里的关键是选择合适的数据结构。我很确定二叉树会起作用。如果增量插入成本为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)
您有两个选择:
排序 - O(n log n)