保持数字有序的有效方法

时间:2013-11-16 23:12:11

标签: python performance algorithm sorting computer-science

这是一个可以应用于任何语言的问题,但我将使用python来显示它。

假设您有一个数字列表ls = [0,100,200,300,400]

您可以在任何索引处插入元素,但元素必须始终按数字顺序排列。不允许重复。

例如,ls.insert(2, 150)会产生ls = [0,100,150,200,300,400]。元素的顺序正确,所以这是正确的。

但是,ls.insert(3, 190)会产生ls = [0,100,200,190,300,400]。这是不正确的。

对于任何索引i,在x中使用的最佳数量ls.insert(i,x)是什么,以最大限度地减少排序数量?

我的第一个直觉是将前一个和下一个数字之间的差异加到前一个数字上。因此,要在索引3处插入一个数字,x将等于200 +(300-200)或250.但是这对渐近线来说太快了。当差异太接近0时,我可以通过循环并更改每个数字来恢复差异,以产生更大的差异。我想为x选择最佳数字,以便最小化我需要重置的次数。

修改

我正在应用此问题的具体问题是带有列表视图的iOS应用。列表中的项目在Set中表示,每个对象都有一个属性orderingValue。我无法使用数组来表示列表(由于缓存服务器同步问题),因此每次向用户显示列表时都必须对该集进行排序。为此,ordersValue必须存储在ListItem对象上。

另外一个细节是,由于用户界面的性质,用户可能更有可能将项目添加到列表的顶部或底部,而不是将其插入中间。

2 个答案:

答案 0 :(得分:2)

如果使用字符串而不是整数,则可以无限期地生成排序键。那是因为字符串的字典顺序在任意两个字符串之间放置了无数个值(只要较大的字符串不是较小的字符串,后跟"a")。

这是一个在另外两个键之间生成小写字符串键的函数:

def get_key_str(low="a", high="z"):
    if low == "":
        low = "a"
    assert(low < high)
    for i, (a, b) in enumerate(zip(low, high)):
        if a < b:
            mid = chr((ord(a) + ord(b))//2) # get the character half-way between a and b
            if mid != a:
                return low[:i] + mid
            else:
                return low[:i+1] + get_key_str(low[i+1:], "z")
    return low + get_key_str("a", high[len(low):])

它始终返回s字符串"a" <= low < s < high <= "z""a""z"永远不会被用作关键字,它们是指示可能结果边界的特殊值。

您可以使用get_key_str([lst[i-1], lst[i])调用它来获取要在索引i的值之前插入的值。您可以使用lst.insert(i, get_key_str(lst[i-1], lst[i]))一次性插入和生成值。显然,列表的末尾需要特殊处理。

设置默认值,以便您可以省略参数以获取要在开头或结尾插入的值。也就是说,调用get_key_str(high=lst[0])来获取一个放在列表开头的值,或者get_key_str(lst[-1])来获取一个值,以便在最后添加。您也可以明确地将"a"作为low"z"作为high传递,如果这更容易的话。如果没有参数,它将返回"m",这是放入空列表的合理的第一个值。

当您在开始或结束时大部分添加时,您可以稍微调整一下以提供更短的键,但这会更复杂一些。如果你随机插入,这个版本的密钥应该大致均匀增长。

这是一个做一些随机插入的例子:

>>> import random
>>> lst = []
>>> for _ in range(10):
    index = random.randint(0, len(lst))
    print("inserting at", index)
    if index == 0:
        low = "a"
    else:
        low = lst[index-1]
    if index == len(lst):
        high = "z"
    else:
        high = lst[index]
    lst.insert(index, get_key_str(low, high))
    print(lst)


inserting at 0
['m']
inserting at 1
['m', 's']
inserting at 2
['m', 's', 'v']
inserting at 2
['m', 's', 't', 'v']
inserting at 2
['m', 's', 'sm', 't', 'v']
inserting at 0
['g', 'm', 's', 'sm', 't', 'v']
inserting at 3
['g', 'm', 's', 'sg', 'sm', 't', 'v']
inserting at 2
['g', 'm', 'p', 's', 'sg', 'sm', 't', 'v']
inserting at 2
['g', 'm', 'n', 'p', 's', 'sg', 'sm', 't', 'v']
inserting at 3
['g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v']

如果我们在开始和结束时做一堆插入,那么它的行为如何:

>>> for _ in range(10):
    lst.insert(0, get_key_str(high=lst[0]))  # start
    lst.insert(len(lst), get_key_str(low=lst[-1])) # end
    print(lst)


['d', 'g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v', 'x']
['b', 'd', 'g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v', 'x', 'y']
['am', 'b', 'd', 'g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v', 'x', 'y', 'ym']
['ag', 'am', 'b', 'd', 'g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v', 'x', 'y', 'ym', 'ys']
['ad', 'ag', 'am', 'b', 'd', 'g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v', 'x', 'y', 'ym', 'ys', 'yv']
['ab', 'ad', 'ag', 'am', 'b', 'd', 'g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v', 'x', 'y', 'ym', 'ys', 'yv', 'yx']
['aam', 'ab', 'ad', 'ag', 'am', 'b', 'd', 'g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v', 'x', 'y', 'ym', 'ys', 'yv', 'yx', 'yy']
['aag', 'aam', 'ab', 'ad', 'ag', 'am', 'b', 'd', 'g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v', 'x', 'y', 'ym', 'ys', 'yv', 'yx', 'yy', 'yym']
['aad', 'aag', 'aam', 'ab', 'ad', 'ag', 'am', 'b', 'd', 'g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v', 'x', 'y', 'ym', 'ys', 'yv', 'yx', 'yy', 'yym', 'yys']
['aab', 'aad', 'aag', 'aam', 'ab', 'ad', 'ag', 'am', 'b', 'd', 'g', 'm', 'n', 'o', 'p', 's', 'sg', 'sm', 't', 'v', 'x', 'y', 'ym', 'ys', 'yv', 'yx', 'yy', 'yym', 'yys', 'yyv']

因此,在开始时,您可能会以a为前缀的密钥结束,最后您将获得以y为前缀的密钥。

答案 1 :(得分:1)

就“最佳”值而言,它总是在前一个和下一个元素的中间。它将达到渐近线。

如果在特定索引处重复插入,则延迟到达渐近线的一种方法是递减前一个并递增下一个值(我假设您可以这样做)每次执行插入时。< / p>

因此,对于ls.insert(2,150),插入后

ls[1] = ls[1] - (ls[1] - ls[0])/2
ls[3] = ls[3] + (ls[4] - ls[3])/2

对于每个其他插入,此规则将成立,并假设插入是随机索引,您需要相当长的时间才能增加每个数字的值。

此外,当您遇到两个相邻数字相差1的那一刻时,您当然必须遍历数字并增加它们。