Как список элементов списка выглядит O (1) в Python?
Сегодня в классе мы узнали, что извлечение элемента из списка - это O(1)
в Python. Почему это так? Предположим, у меня есть список из четырех элементов, например:
li = ["perry", 1, 23.5, "s"]
Эти элементы имеют разные размеры в памяти. И поэтому невозможно взять ячейку памяти li[0]
и добавить в три раза больше размера каждого элемента, чтобы получить ячейку памяти li[3]
. Итак, как интерпретатор знает, где li[3]
не нуждается в перемещении списка для извлечения элемента?
Ответы
Ответ 1
Список в Python реализуется как массив указателей 1. Итак, что действительно происходит при создании списка:
["perry", 1, 23.5, "s"]
что вы на самом деле создаете массив указателей, например:
[0xa3d25342, 0x635423fa, 0xff243546, 0x2545fade]
Каждый указатель "указывает" на соответствующие объекты в памяти, так что строка "perry"
будет сохранена по адресу 0xa3d25342
а номер 1
будет сохранен в 0x635423fa
и т.д.
Поскольку все указатели имеют одинаковый размер, интерпретатор может фактически добавить в 3 раза размер элемента к адресу li[0]
чтобы перейти к указателю, хранящемуся в li[3]
.
1 Получить более подробную информацию: конский рот (исходный код CPython на GitHub).
Ответ 2
Когда вы говорите a = [...]
, a
фактически является указателем на PyObject
содержащий массив указателей на PyObject
s.
Когда вы запрашиваете a[2]
, интерпретатор сначала следует указателю на список PyObject
, затем добавляет 2
к адресу массива внутри него, а затем возвращает этот указатель. То же самое происходит, если вы попросите a[0]
или a[9999]
.
В принципе, все объекты Python доступны по ссылке вместо значения, даже целые литералы, такие как 2
. В системе указателей есть несколько трюков, чтобы все это было эффективным. И указатели имеют известный размер, поэтому их удобно хранить в массивах C-стиля.
Ответ 3
Короткий ответ: списки Python - это массивы.
Длинный ответ. Список терминов компьютерной науки обычно означает либо связанный список (как используется в функциональном программировании), либо список с двойной связью (как используется в процедурном программировании). Эти структуры данных поддерживают ввод O (1) либо в начале списка (функционально), либо в любом месте, которое не нужно искать (процедурно). Список "Python" не имеет ни одной из этих характеристик. Вместо этого он поддерживает (амортизируется) O (1), добавляемый в конце списка (например, C++ std :: vector или Java ArrayList). Списки Python - это действительно масштабируемые массивы в терминах CS.
Следующий комментарий из документации Python объясняет некоторые характеристики производительности списков Python:
Также можно использовать список в качестве очереди, где первым добавленным элементом является первый извлеченный элемент ("first-in, first-out"); однако списки для этой цели неэффективны. В то время как appends и pops из конца списка бывают быстрыми, выполнение вставок или всплывающих окон с начала списка происходит медленно (потому что все остальные элементы должны быть сдвинуты на один).