它是 O(n),但索引查找参数是一个红鲱鱼。
迭代的工作原理
如果您这样做,索引查找速度会很重要:
for index in range(len(mystring)):
char = mystring[index]
...
但是您没有使用索引。您正在使用一个 iterator,更准确地说是一个 string 迭代器:
>>> iter('test')
<str_iterator object at 0x03569820>
那个迭代器记住它在字符串中的位置(不管它喜欢什么,不需要是一个“索引”)。并且可以反复询问下一个字符:
>>> it = iter('test')
>>> next(it)
't'
>>> next(it)
'e'
>>> next(it)
's'
>>> next(it)
't'
>>> next(it)
Traceback (most recent call last):
File "<pyshell#200>", line 1, in <module>
next(it)
StopIteration
这就是for-loop 的作用。它创建该迭代器,然后反复询问它的下一个值,直到迭代器告诉它停止。它从迭代器中获得的每个值,都会在您命名的变量中提供给您的代码。换句话说,for-loop 实际上只是迭代器和循环体中的代码之间的中间人。
与字符串相比,想象一个简单的链表。链表中的索引查找需要 O(n),因为每次查找都需要从链表的开头步行到所需的节点。但是您仍然可以轻松地在 O(n) 中进行完整的迭代,对吧?并且迭代器对象将保留对下一个节点的引用,因此它将在 O(1) 时间内提供它(然后将其引用向前移动)。因此,对于链表,使用索引的for-loop 将花费 O(n2) 但普通的 pythonic for-loop(隐式使用链表迭代器)将是 O(n )。
您甚至可以使用while-loop 和您自己处理的显式迭代器来模仿for-loop,而不是让for-loop 为您处理它。而不是
for char in 'test':
print(char)
这样做:
it = iter('test')
while True:
try:
char = next(it)
except StopIteration:
break
print(char)
打印这个:
t
e
s
t
回到字符串迭代的时间复杂度
让我们看看源代码。我对它不是很熟悉,但我会描述我所相信的。还记得它是str_iterator 吗? Python 3 中的 str 在 Python 2 中称为 unicode,这仍然是 Python 3 的 C 源代码中的名称。所以在 unicodeobject.c 中,我们找到字符串 "str_iterator",它位于“Unicode 迭代器”中“ 部分。摘录:
/********************* Unicode Iterator **************************/
typedef struct {
...
Py_ssize_t it_index;
PyObject *it_seq; /* Set to NULL when iterator is exhausted */
} unicodeiterobject;
...
unicodeiter_next(unicodeiterobject *it)
{
...
seq = it->it_seq;
...
void *data = PyUnicode_DATA(seq);
Py_UCS4 chr = PyUnicode_READ(kind, data, it->it_index);
item = PyUnicode_FromOrdinal(chr);
if (item != NULL)
++it->it_index;
return item;
...
}
...
PyTypeObject PyUnicodeIter_Type = {
...
"str_iterator", /* tp_name */
...
};
所以它是一个unicodeiterobject,带有一个指向它迭代的字符串的指针it_seq 和一个索引it_index。而它的next 函数使用它们来获取下一个字符,增加索引并返回该字符。好的,结果迭代器确实在内部使用了索引。但是这种索引比你在 Python 中使用unicode_getitem 函数的索引在更低、更直接的内部级别:
static PyObject *
unicode_getitem(PyObject *self, Py_ssize_t index)
{
void *data;
enum PyUnicode_Kind kind;
Py_UCS4 ch;
...
if (index < 0 || index >= PyUnicode_GET_LENGTH(self)) {
PyErr_SetString(PyExc_IndexError, "string index out of range");
return NULL;
}
kind = PyUnicode_KIND(self);
data = PyUnicode_DATA(self);
ch = PyUnicode_READ(kind, data, index);
return unicode_char(ch);
}
两者看起来相似,最终使用PyUnicode_READ(kind, data, index)。我找不到那个,但它应该相当简单并且 O(1),使得整个迭代 O(n)。
还有一件事:@NickParsons 上面指出的answer/question 解决了 Python 使用可变大小多字节字符表示的担忧,这可能使索引查找 O(n) 而不是 O(1)。即使 是 的情况,它也只会影响unicode_getitem 函数。不是 str_iterator 迭代器。因为迭代器绝对不会使用幼稚的“字符串索引”,而是使用指向下一个字符的第一个 byte 的指针,以便它可以在 O(1) 中读取和前进。所以它的整个迭代仍然是 O(n)。