在 Python 中合并两个列表的运行时间
Runtime of merging two lists in Python
假设我们有两个列表A = [a1, a2, ..., an]
(n个元素)和B = [b1, b2, ..., bm]
(m个元素),我们在Python中使用“+”将两个列表合并为一个,所以
C = A + B;
我的问题是这个操作的运行时间是多少?我的第一个猜测是 O(n+m)
,不确定 Python 是否比它更聪明。
当您使用 A + B
连接两个列表时,您会在内存中创建一个全新的列表。这意味着您的猜测是正确的:复杂度为 O(n + m)
(其中 n
和 m
是列表的长度)因为 Python 必须依次遍历两个列表才能构建新列表。
您可以在 source code for Python lists 中的 list_concat
函数中看到这种情况:
static PyObject *
list_concat(PyListObject *a, PyObject *bb)
{
/* ...code snipped... */
src = a->ob_item;
dest = np->ob_item;
for (i = 0; i < Py_SIZE(a); i++) { /* walking list a */
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
src = b->ob_item;
dest = np->ob_item + Py_SIZE(a);
for (i = 0; i < Py_SIZE(b); i++) { /* walking list b */
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
/* ...code snipped... */
如果你不需要内存中的新列表,利用列表是可变的这一事实通常是个好主意(这就是 Python 聪明)。使用 A.extend(B)
是 O(m)
的复杂性,这意味着您避免了复制列表 a
.
的开销
here 在 Python wiki 上列出了各种列表操作的复杂性。
复制列表是O(n)
(n 是元素的数量)并且扩展是O(k)
(n 是第二个列表中的元素的数量)。基于这两个事实,我认为它不会少于O(n+k)
,因为这是一个复制和扩展操作,最起码你需要复制两个列表的所有元素。
My first guess is O(n+m)
, not sure if Python is smarter than that.
Nothing 在返回 copy 时比这更聪明。虽然即使 A
、B
是字符串等不可变序列; CPython 仍然制作完整副本,而不是为相同的内存设置别名(它简化了此类字符串的垃圾回收的实现)。
在某些特定情况下,操作可能是 O(1)
,具体取决于您要对结果执行的操作,例如,itertools.chain(A, B)
允许遍历所有项目(它不会制作副本, A
、B
的变化会影响产出的物品)。或者,如果您需要随机访问;您可以使用 Sequence
子类来模拟它,例如 WeightedPopulation
但在一般情况下,副本和因此 O(n+m)
运行时是不可避免的。
假设我们有两个列表A = [a1, a2, ..., an]
(n个元素)和B = [b1, b2, ..., bm]
(m个元素),我们在Python中使用“+”将两个列表合并为一个,所以
C = A + B;
我的问题是这个操作的运行时间是多少?我的第一个猜测是 O(n+m)
,不确定 Python 是否比它更聪明。
当您使用 A + B
连接两个列表时,您会在内存中创建一个全新的列表。这意味着您的猜测是正确的:复杂度为 O(n + m)
(其中 n
和 m
是列表的长度)因为 Python 必须依次遍历两个列表才能构建新列表。
您可以在 source code for Python lists 中的 list_concat
函数中看到这种情况:
static PyObject *
list_concat(PyListObject *a, PyObject *bb)
{
/* ...code snipped... */
src = a->ob_item;
dest = np->ob_item;
for (i = 0; i < Py_SIZE(a); i++) { /* walking list a */
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
src = b->ob_item;
dest = np->ob_item + Py_SIZE(a);
for (i = 0; i < Py_SIZE(b); i++) { /* walking list b */
PyObject *v = src[i];
Py_INCREF(v);
dest[i] = v;
}
/* ...code snipped... */
如果你不需要内存中的新列表,利用列表是可变的这一事实通常是个好主意(这就是 Python 聪明)。使用 A.extend(B)
是 O(m)
的复杂性,这意味着您避免了复制列表 a
.
here 在 Python wiki 上列出了各种列表操作的复杂性。
复制列表是O(n)
(n 是元素的数量)并且扩展是O(k)
(n 是第二个列表中的元素的数量)。基于这两个事实,我认为它不会少于O(n+k)
,因为这是一个复制和扩展操作,最起码你需要复制两个列表的所有元素。
My first guess is
O(n+m)
, not sure if Python is smarter than that.
Nothing 在返回 copy 时比这更聪明。虽然即使 A
、B
是字符串等不可变序列; CPython 仍然制作完整副本,而不是为相同的内存设置别名(它简化了此类字符串的垃圾回收的实现)。
在某些特定情况下,操作可能是 O(1)
,具体取决于您要对结果执行的操作,例如,itertools.chain(A, B)
允许遍历所有项目(它不会制作副本, A
、B
的变化会影响产出的物品)。或者,如果您需要随机访问;您可以使用 Sequence
子类来模拟它,例如 WeightedPopulation
但在一般情况下,副本和因此 O(n+m)
运行时是不可避免的。