【问题标题】:Alternative to python's .sort() (for inserting into a large list and keeping it sorted)替代 python 的 .sort() (用于插入大列表并保持排序)
【发布时间】:2015-08-11 07:03:17
【问题描述】:

我需要不断地将数字添加到预先排序的列表中:

for num in numberList:
    list.append(num)
    list.sort()

每次迭代都很短,但是当给定的 numberList 包含数万个值时,此方法会减慢速度。是否有更有效的功能可以使列表保持完整并找出要插入新数字的索引以保持正确的数字顺序?我自己写的任何东西都比 .sort() 花费更长的时间

【问题讨论】:

  • 你为什么要对每次迭代进行排序?你可以在 for 循环结束时这样做是吗?这是故意的吗?
  • 在 list.sort() 之后有一个单独的代码部分运行一些计算,因此无法对最终列表进行排序,每次迭代都必须这样做。
  • @AMR 我从未阅读过有关 Python 的建议,即您应该绑定实例方法以避免在任何地方查找。这似乎违反了PEP 20。为列表保留一个变量更简单,更容易理解,也更难搞砸。此外,方法查找产生的任何开销都是微不足道的;这样的改变充其量只是过早的优化。
  • @AMR 你也应该这样。您跳过了“配置文件如果慢”部分。 “选择正确的数据结构”是建议进行优化的第一个标题,它(正确地)建议您应该在考虑这样的更改之前找到合适的数据结构,以及需要考虑的其他一些事情。文档本身甚至警告了这种更改可能会造成混淆:“应谨慎使用此技术。如果循环很大,维护起来会更加困难。”也就是说,no 的意思是“一种好的做法”;这是最后的加速。
  • 你可以这样做:list_append = list.appendlist_sort = list.sort然后list_append(num)list_sort()你所做的只是交换和_.,你已经改进了运行-在可读性没有实质性改变的情况下,时间缩短了 40%。

标签: python python-2.7 sorting


【解决方案1】:

您可以使用bisect.insort() function 将值插入到已排序的列表中:

from bisect import insort

insort(list, num)

请注意,这仍然需要一些时间,因为插入点之后的其余元素都必须上移一步;您可能需要考虑将列表重新实现为链表。

但是,如果您保持列表排序只是为了始终能够获得最小或最大数字,则应使用heapq module 代替;堆不是按照严格的排序顺序保持的,但在任何时候都可以非常快速地为您提供最小值或最大值。

【讨论】:

  • 这是对性能增强的分析。在100,000随机生成的整数列表上运行。Time for insort 1.649233102798462Time for list_append and list_sort 344.4988350868225Time for list.append and list.sort 559.2065420150757
  • 树木呢?我已经有一段时间没有需要它们了,但即使排序后它们也没有为访问、插入和删除提供合理的时间吗?
  • 二叉搜索树也可以工作,是的,具体取决于用例。
  • AFAIK 搜索树不是在 python 中原生实现的,因此使用它们意味着使用第三方模块,这可能会导致性能成本,因为它们是更复杂的数据结构,而 bisect 在简单列表上具有相同的复杂性.一个好的中间立场可以是使用比二叉搜索树更简单的跳过列表,但仍然比列表少。
  • 链接列表和跳过列表也不是标准库的一部分。使用第三方库并不意味着它们更慢。
【解决方案2】:

查看在列表上实现插入排序的本机 bisect.insort(),这应该完全符合您的需求,因为 complexity is O(n) at best and O(n^2) at worst 而不是 O(nlogn) 与您当前的解决方案(插入后重新排序)。

但是,有更快的替代方法来构建排序的数据结构,例如Skip Lists 和二叉搜索树,它们允许插入复杂度最高为 O(log n),最坏为 O(n),甚至更好的 B -trees,Red-Black trees,Splay 树和 AVL 树,在最佳和最坏情况下都具有 O(log n) 的复杂度。有关所有这些解决方案和其他解决方案的复杂性的更多信息,请参阅 Eric Rowell 的 BigO CheatSheet。但请注意,所有这些解决方案都需要您安装第三方模块,并且通常需要使用 C 编译器进行编译。

但是,有一个名为 sortedcontainers 的纯 python 模块,它声称与 C 编译的 AVL 树和 B 树实现的 Python 扩展 (benchmark available here) 一样快或更快。

我对一些解决方案进行了基准测试,看看哪个解决方案执行插入排序最快:

sortedcontainers: 0.0860911591881
bisect: 0.665865982912
skiplist: 1.49330501066
sort_insert: 17.4167637739

这是我用来进行基准测试的代码:

from timeit import Timer
setup = """
L = list(range(10000)) + list(range(10100, 30000))
from bisect import insort

def sort_insert(L, x):
    L.append(x)
    L.sort()

from lib.skiplist import SkipList
L2 = SkipList(allowDups=1)
for x in L:
    L2.insert(x)

from lib.sortedcontainers import SortedList
L3 = SortedList(L)
"""

# Using sortedcontainers.SortedList()
t_sortedcontainers = Timer("for i in xrange(10000, 10100): L3.add(i)", setup)
# Using bisect.insort()
t_bisect = Timer("for i in xrange(10000, 10100): insort(L, i)", setup)
# Using a Skip List
t_skiplist = Timer("for i in xrange(10000, 10100): L2.insert(i)", setup)
# Using a standard list insert and then sorting
t_sort_insert = Timer("for i in xrange(10000, 10100): sort_insert(L, i)", setup)

# Timing the results
print t_sortedcontainers.timeit(number=100)
print t_bisect.timeit(number=100)
print t_skiplist.timeit(number=100)
print t_sort_insert.timeit(number=100)

因此,结果表明 sortedcontainers 确实比 bisect 快了近 7 倍(我预计速度差距会随着列表大小而增加,因为复杂性是一个数量级的不同)。 p>

更令人惊讶的是skip list比bisect慢,但可能是因为它没有bisect优化,它是用C实现的,可能会使用一些优化技巧(注意我使用的skiplist.py模块是我能找到的最快的纯 Python 跳过列表,pyskip module 要慢很多)。

另外值得注意的是:如果您需要使用比列表更复杂的结构,sortedcontainers 模块提供了 SortedList、SortedListWithKey、SortedDict 和 SortedSet(而 bisect 仅适用于列表)。另外,您可能会对这个somewhat related benchmark 和这个complexity cheatsheet of various Python operations 感兴趣。

【讨论】:

  • "复杂度为 O(log n)" - 为什么? O(log n) 用于查找插入的位置,但插入仍然需要 O(n)
  • 使用 bisect 在 python 的列表中插入似乎是在恒定时间 O(1) 内完成的,所以一旦找到插入排序的索引,就没有额外的成本
  • “使用 bisect 在 python 列表中插入似乎是在恒定时间 O(1) 内完成的”——这是不可能的,伙计。 Python 的列表是一个数组,不是链表。
  • @SmitJohnth 你是对的,我读过的邮件列表是错误的(或部分正确的):仅通过二等分找到正确的索引在 O(log n) 中,用于插入排序充其量是 O(n),最坏的是 O(n^2)。我已经更新了我的答案,谢谢。
猜你喜欢
  • 2021-05-02
  • 1970-01-01
  • 1970-01-01
  • 2012-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-24
  • 2013-02-09
相关资源
最近更新 更多