简体   繁体   English

遍历 Python 中的双端队列的时间复杂度是多少?

[英]What is the time complexity of iterating through a deque in Python?

What is the time complexity of iterating, or more precisely each iteration through a deque from the collections library in Python?通过 Python 中的 collections 库中的双端队列进行迭代的时间复杂度是多少?

An example is this:一个例子是这样的:

elements = deque([1,2,3,4])
for element in elements:
  print(element)

Is each iteration a constant O(1) operation?每次迭代都是常数 O(1) 操作吗? or does it do a linear O(n) operation to get to the element in each iteration?还是在每次迭代中执行线性 O(n) 操作来获取元素?

There are many resources online for time complexity with all of the other deque methods like appendleft , append , popleft , pop .网上有很多关于时间复杂度的资源,以及所有其他双端队列方法,如appendleftappendpopleftpop There doesn't seem to be any time complexity information about the iteration of a deque.似乎没有关于双端队列迭代的任何时间复杂度信息。

Thanks!谢谢!

If your construction is something like: 如果您的构造是这样的:

elements = deque([1,2,3,4])
for i in range(len(elements)):
    print(elements[i])

You are not iterating over the deque , you are iterating over the range object, and then indexing into the deque . 没有遍历deque ,而是遍历了range对象, 然后索引到了deque This makes the iteration polynomial time, since each indexing operation, elements[i] is O(n). 由于每次索引操作, elements[i]为O(n),因此使迭代次数为多项式时间。 However, actually iterating over the deque is linear time. 但是,实际上对deque 迭代是线性时间。

for x in elements:
    print(x)

Here's a quick, empirical test: 这是一个快速的经验检验:

import timeit
import pandas as pd
from collections import deque

def build_deque(n):
    return deque(range(n))

def iter_index(d):
    for i in range(len(d)):
        d[i]

def iter_it(d):
    for x in d:
        x

r = range(100, 10001, 100)

index_runs = [timeit.timeit('iter_index(d)', 'from __main__ import build_deque, iter_index, iter_it; d = build_deque({})'.format(n), number=1000) for n in r]
it_runs = [timeit.timeit('iter_it(d)', 'from __main__ import build_deque, iter_index, iter_it; d = build_deque({})'.format(n), number=1000) for n in r]

df = pd.DataFrame({'index':index_runs, 'iter':it_runs}, index=r)
df.plot()

And the resulting plot: 和结果情节: 在此处输入图片说明

Now, we can actually see how the iterator protocol is implemented for deque objects in CPython source code : 现在,我们实际上可以看到如何在CPython源代码中deque对象实现迭代器协议:

First, the deque object itself: 首先, deque对象本身:

typedef struct BLOCK {
    struct BLOCK *leftlink;
    PyObject *data[BLOCKLEN];
    struct BLOCK *rightlink;
} block;

typedef struct {
    PyObject_VAR_HEAD
    block *leftblock;
    block *rightblock;
    Py_ssize_t leftindex;       /* 0 <= leftindex < BLOCKLEN */
    Py_ssize_t rightindex;      /* 0 <= rightindex < BLOCKLEN */
    size_t state;               /* incremented whenever the indices move */
    Py_ssize_t maxlen;
    PyObject *weakreflist;
} dequeobject;

So, as stated in the comments, a deque is a doubly-linked list of "block" nodes, where a block is essentially an array of python object pointers. 因此,如评论中所述, deque是“块”节点的双向链接列表,其中,块本质上是python对象指针的数组。 Now for the iterator protocol: 现在使用迭代器协议:

typedef struct {
    PyObject_HEAD
    block *b;
    Py_ssize_t index;
    dequeobject *deque;
    size_t state;          /* state when the iterator is created */
    Py_ssize_t counter;    /* number of items remaining for iteration */
} dequeiterobject;

static PyTypeObject dequeiter_type;

static PyObject *
deque_iter(dequeobject *deque)
{
    dequeiterobject *it;

    it = PyObject_GC_New(dequeiterobject, &dequeiter_type);
    if (it == NULL)
        return NULL;
    it->b = deque->leftblock;
    it->index = deque->leftindex;
    Py_INCREF(deque);
    it->deque = deque;
    it->state = deque->state;
    it->counter = Py_SIZE(deque);
    PyObject_GC_Track(it);
    return (PyObject *)it;
}

... ...

static PyObject *
dequeiter_next(dequeiterobject *it)
{
    PyObject *item;

    if (it->deque->state != it->state) {
        it->counter = 0;
        PyErr_SetString(PyExc_RuntimeError,
                        "deque mutated during iteration");
        return NULL;
    }
    if (it->counter == 0)
        return NULL;
    assert (!(it->b == it->deque->rightblock &&
              it->index > it->deque->rightindex));

    item = it->b->data[it->index];
    it->index++;
    it->counter--;
    if (it->index == BLOCKLEN && it->counter > 0) {
        CHECK_NOT_END(it->b->rightlink);
        it->b = it->b->rightlink;
        it->index = 0;
    }
    Py_INCREF(item);
    return item;
}

As you can see, the iterator essentially keeps track of a block index, a pointer to a block, and a counter of total items in the deque. 正如您所看到的,迭代器本质上跟踪着队列索引,指向块的指针以及双端队列中所有项目的计数器。 It stops iterating if the counter reaches zero, if not, it grabs the element at the current index, increments the index, decrements the counter, and tales care of checking whether to move to the next block or not. 如果计数器达到零,它将停止迭代,否则计数器将停止获取当前索引处的元素,然后递增索引,使计数器递减,然后检查是否要移至下一个块。 In other words, A deque could be represented as a list-of-lists in Python, eg d = [[1,2,3],[4,5,6]] , and it iterates 换句话说,双端队列可以表示为Python中的列表列表,例如d = [[1,2,3],[4,5,6]]并进行迭代

for block in d:
    for x in block:
        ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM