首先,我得到一个固定大小的数组,称之为 v 。 v 的典型大小为几千个条目。我首先计算该数组的最大值。
在此之后,我会定期为 v [i] 指定一个新值,并需要重新计算最大值。
计算最大值的几乎快速方式(平均时间)是什么?
编辑:我们可以假设该过程是:
1)统一选择随机条目;
2)将其值更改为[0,1]之间的统一值。
我相信这会更好地指出问题并允许明确的“最佳答案”(这将取决于数组大小)。
答案 0 :(得分:0)
您可以维护该数组的max-heap
。元素可以是数组的索引。对于数组的每个元素,您还应该为max-heap
元素添加一些索引。因此,每次更改v[i]
时,您只需要O(log(n))
来维护堆。 (如果v[i]
增加,它将在堆中上升,如果v[i]
减少,它将在堆中下降。
答案 1 :(得分:0)
如果对数组的更改是随机的,例如v[rand()%size] = rand()
,然后大部分时间最大值不会减少。
我可以通过两种主要方式来处理这个问题:保持整个集合在运行中排序,或者只跟踪少数(或一个)最高元素。选择取决于最坏情况,平均情况和快速路径的相对重要性。 (包括常见情况下的代码和数据缓存占用空间,其中更改不会影响您正在跟踪的任何内容。)
复杂性/开销/代码大小非常低:O(1)平均情况,O(N)最坏情况。
只需跟踪当前的max
,(以及可选的位置,如果您无法在应用更改之前获取旧值== max
)。在极少数情况下,保持max
的元素减少,重新扫描整个数组。否则,只需查看新元素是否大于max
。
平均复杂度应为O(1)摊销:N(N)的变化,因为平均N个变化中的一个会影响持有最大值的元素。 (只有一半的变化减少了它。)
更多的开销和代码大小,但对整个阵列的扫描频率较低:O(1)典型情况,O(N)最坏情况。
保持数组中4或8个最高元素的优先级队列(位置和值)。修改PQueue中的元素后,将其从PQueue中删除。尝试将新值重新添加到PQueue,但前提是它不是最小的元素。 (它可能小于我们未跟踪的其他元素)。如果PQueue为空,则重新扫描阵列以将其重建为完整大小。当前最大值是PQueue的前面。重新扫描阵列应该非常少见,并且在大多数情况下我们只需触摸一个包含PQueue 的缓存数据线。
由于小PQueue需要支持快速访问最小的和最大元素,甚至找到不是min或max的元素,因此排序数组实现可能会使得最有意义的,而不是堆。如果它只有8个元素,那么线性搜索也可能是最好的。 (从最小的元素向上,如果修改的元素的旧值小于PQueue中的最小值,则搜索立即结束,搜索立即停止。)
如果要优化快速路径(PQueue中的位置已修改),可以将PQueue存储为struct pqueue { unsigned pos[8]; int val[8]; }
,并使用向量指令(例如x86 SSE / AVX2)进行测试i
针对一两次测试中的所有8个职位。嗯,实际上只是检查旧的val,看它是否小于PQ.val[0]
应该是一个很好的快速路径。
要跟踪PQueue的当前大小,最好使用单独的计数器,而不是pos[]
中的标记值。每次循环迭代检查sentinel可能会更慢。 (尤其是因为你有疑问。需要使用pos
来保存哨兵值;或许可以让它签名并使用-1
?)如果有一个哨兵你可以用{ {1}},这可能没问题。
较慢的O(log N)平均情况,但没有完全重新扫描的最坏情况:
小天培的解决方案让整个阵列成堆。 (如果val[]
的排序很重要,这不起作用。你可以将所有元素保存在堆中以及有序数组中,但这听起来很麻烦。)Re - 在更改随机元素后进行切换可能每次都会写入其他几个高速缓存行,因此常见情况要比仅跟踪前一个或几个元素的方法慢得多。
其他聪明的东西我还没有想过?