英文:
Efficient implementation of finding segment number falls into
问题
让我们将介于0和n之间的数字,对于可能很大的n,分成s个段:0 = n_0 < n_1 < ... < n_s = n。 对于一个满足0 <= x < n的数字,我们可以通过二分查找在O(s)空间和O(log(s))时间内找到s,使得n_(s - 1) < x <= n_s,或者通过查找表在O(n)空间和O(1)时间内找到。 有没有可能更好地权衡空间和时间复杂性?
英文:
Let us divide the numbers between 0 and n, for potentially large n into s segments: 0 = n_0 < n_1 < ... < n_s = n. Given a number 0 <= x < n, we can find s such that n_(s - 1) < x <= n_s in O(s) space and O(log(s)) time by binary search, or O(n) space and O(1) time by a lookup table. Is it possible to do better? Trade off space for time complexity?
答案1
得分: 2
以下是已翻译的内容:
你可以提高平均性能,但代价是降低最差情况的速度。
为了演示,让我们考虑这样一种情况,其中s
相当大,但远小于n
。在n
的前p
比例之前,s
中有多少个元素?我们可以将此建模为在范围从1
到n-1
选择s-2
个随机数。每个数几乎是独立的(由于假设s
远小于n
),它们小于n*p
的概率为p
。标准概率告诉我们,每个数都是均值为p
,方差为p * (1-p)
的随机变量。它们的总和近似为均值avg = p*(s-2)
和方差p * (1-p) * (s-2)
的正态分布。标准偏差std
为sqrt(p * (1-p) * (s-2))
。大约95%的时间内,我们将在平均值的2个标准偏差内。 (如果您喜欢,大约99.7%的时间内,我们将在3个标准偏差内。)
因此,查看序列在avg + 2*std
和avg - 2*std
处的值,大约95%的时间内,我们可以将搜索范围缩小到2*std = O(sqrt(s))
内。这样做O(log(log(s)))
次将答案减少到1。 (如果您喜欢,使用3而不是2,边界更宽,但几率为99.7%。)
实际上,最好不要一次产生2个猜测。只需计算平均值,然后朝着中点的一半走2(或3,如果您喜欢)个标准偏差(如果会越过中点则停止)。看到答案后,您可以更精确地进行另一个猜测。平均运行时间保持不变。
现在,这样做的代价是使最差情况的性能变得更差。但如果您只是每10次中选择一次中点,您可以保持最坏情况为O(log(s))
(具有较差的常数),同时仍然享受改进的O(log(log(s)))
更好的平均时间。
英文:
You can make your average performance faster at the price of making your worst case slower.
To demonstrate, let's consider the case where s
is fairly large, but is much smaller than n
. How many things in s
are before a proportion p
of the way through n
? We can model this as picking s-2
random numbers in the range from 1
to n-1
. Each has an almost independent (thanks to the assumption that s
is much smaller than n
) probability p
of being less than n*p
. Standard probability says that each is a random variable with mean p
and variance p * (1-p)
. Their sum is approximately normal with mean avg = p*(s-2)
and variance p * (1-p) * (s-2)
. The standard deviation std
is sqrt(p * (1-p) * (s-2))
. About 95% of the time we will be within 2 standard deviations of the average. (If you prefer, about 99.7% we will be within 3 standard deviations.)
Therefore looking at the values of the sequence at avg + 2*std
and avg - 2*std
, about 95% of the time we can reduce the range to search to within 2*std = O(sqrt(s))
. Doing so O(log(log(s)))
times will reduce the answer to 1. (If you prefer, use a 3 instead of 2, and the bounds are wider, but the odds are 99.7%.)
It is actually better to not produce 2 guesses at a time. Just figure out the average, and then go 2 (or 3 if you prefer) standard deviations towards the half-way point (stopping there if you would pass it). After seeing the answer you can then make the other guess much more precisely. The average runtime remains the same.
Now this goes at the cost of making the worst case performance much worse. But if you simply choose the halfway point one time in 10, you can keep the worst case O(log(s))
(with worse constants) while still enjoying the improved O(log(log(s)))
better average time.
Here is a quick demonstration without the standard deviation logic. I left a debugging step so that you can see how many guesses it makes.
def find(s, x):
low_i = -1
high_i = len(s)
low_val = s[low_i+1]
high_val = s[high_i-1]
counter = 0
while low_i + 1 < high_i:
if 50 < counter:
break
counter += 1
if 0 == counter%8:
# Guarantee progress
mid_i = (high_i + low_i)//2
else:
p = (x - low_val) / (high_val - low_val)
mid_i = round(low_i + p * (high_i - low_i))
# Must be in our range.
if mid_i == low_i:
mid_i += 1
elif mid_i == high_i:
mid_i -= 1
print(counter, (low_i, high_i), (low_val, high_val), mid_i, s[mid_i])
if s[mid_i] < x:
low_i = mid_i
low_val = s[low_i]
else:
high_i = mid_i
high_val = s[high_i]
return (mid_i, s[mid_i])
And then to give it a realistic test.
import random
n = 10**12
numbers = set([0, n])
while len(numbers) < 10**6:
numbers.add(random.randint(0, n))
numbers = sorted(numbers)
find(numbers, random.randint(0, n))
We have a million numbers from 0 through a trillion. Finding the correct range from binary guessing should take 20 guesses. This usually takes 4-6 guesses instead.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论