mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-11 05:26:42 +08:00
deploy
This commit is contained in:
@@ -4360,8 +4360,8 @@
|
||||
<h3 id="1">1. Ключевые выводы<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<ul>
|
||||
<li>Массивы и связные списки - это две базовые структуры данных, представляющие два способа хранения данных в памяти компьютера: хранение в непрерывном пространстве и хранение в разрозненном пространстве. Их свойства во многом взаимно дополняют друг друга.</li>
|
||||
<li>Массив поддерживает произвольный доступ и занимает меньше памяти; однако вставка и удаление элементов в нем неэффективны, а длина после инициализации фиксирована.</li>
|
||||
<li>Связный список позволяет эффективно вставлять и удалять узлы путем изменения ссылок (указателей), а также гибко менять длину; однако доступ к узлам менее эффективен, а памяти он занимает больше. Распространенные типы списков включают односвязные, циклические и двусвязные списки.</li>
|
||||
<li>Массив поддерживает произвольный доступ и занимает меньше памяти. Однако вставка и удаление элементов в нем неэффективны, а длина после инициализации фиксирована.</li>
|
||||
<li>Связный список позволяет эффективно вставлять и удалять узлы путем изменения ссылок (указателей), а также гибко менять длину. Однако доступ к узлам менее эффективен, а памяти он занимает больше. Распространенные типы списков включают односвязные, циклические и двусвязные списки.</li>
|
||||
<li>Список - это упорядоченная коллекция элементов, поддерживающая добавление, удаление, поиск и изменение, и обычно реализуемая на основе динамического массива. Он сохраняет преимущества массива и при этом может гибко менять длину.</li>
|
||||
<li>Появление списка значительно повысило практическую ценность массива, хотя это и может приводить к потере части памяти.</li>
|
||||
<li>Во время работы программы данные в основном хранятся в оперативной памяти. Массив обеспечивает более высокую эффективность использования пространства памяти, а связный список дает большую гибкость в использовании памяти.</li>
|
||||
@@ -4372,13 +4372,13 @@
|
||||
<p><strong>Q</strong>: Влияет ли хранение массива в стеке или в куче на временную и пространственную эффективность?</p>
|
||||
<p>Массивы, расположенные и в стеке, и в куче, все равно хранятся в непрерывной области памяти, поэтому эффективность операций с данными у них в целом одинакова. Однако у стека и кучи есть собственные особенности, из-за которых возникают следующие различия.</p>
|
||||
<ol>
|
||||
<li>Эффективность выделения и освобождения: стек представляет собой относительно небольшой участок памяти, а выделение в нем обычно выполняется автоматически компилятором; куча же обычно больше, может выделяться динамически из кода и легче фрагментируется. Поэтому выделение и освобождение памяти в куче обычно медленнее, чем в стеке.</li>
|
||||
<li>Эффективность выделения и освобождения: стек представляет собой относительно небольшой участок памяти, а выделение в нем обычно выполняется автоматически компилятором. Куча же обычно больше, может выделяться динамически из кода и легче фрагментируется. Поэтому выделение и освобождение памяти в куче обычно медленнее, чем в стеке.</li>
|
||||
<li>Ограничение размера: объем стека относительно невелик, а размер кучи обычно ограничивается доступной памятью. Поэтому куча лучше подходит для хранения больших массивов.</li>
|
||||
<li>Гибкость: размер массива в стеке должен быть известен во время компиляции, а размер массива в куче может определяться динамически во время выполнения.</li>
|
||||
</ol>
|
||||
<p><strong>Q</strong>: Почему для массива требуется, чтобы все элементы были одного типа, а для связного списка это не подчеркивается?</p>
|
||||
<p>Связный список состоит из узлов, а узлы соединяются между собой через ссылки (указатели), поэтому каждый узел в принципе может хранить данные разного типа, например <code>int</code> , <code>double</code> , <code>string</code> , <code>object</code> и т.д.</p>
|
||||
<p>Напротив, элементы массива должны быть одного типа, иначе нельзя будет вычислять адрес элемента через смещение. Например, если массив одновременно содержит <code>int</code> и <code>long</code> , один элемент занимает 4 байта, а другой - 8 байт ; в этом случае формула ниже уже не позволит вычислить смещение, потому что в массиве будут присутствовать элементы разной длины.</p>
|
||||
<p>Напротив, элементы массива должны быть одного типа, иначе нельзя будет вычислять адрес элемента через смещение. Например, если массив одновременно содержит <code>int</code> и <code>long</code> , один элемент занимает 4 байта, а другой - 8 байт. В этом случае формула ниже уже не позволит вычислить смещение, потому что в массиве будут присутствовать элементы разной длины.</p>
|
||||
<div class="highlight"><pre><span></span><code><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="c1"># Адрес элемента в памяти = адрес массива в памяти (адрес первого элемента) + длина элемента * индекс элемента</span>
|
||||
</code></pre></div>
|
||||
<p><strong>Q</strong>: После удаления узла <code>P</code> нужно ли присваивать <code>P.next = None</code> ?</p>
|
||||
@@ -4386,16 +4386,16 @@
|
||||
<p>С точки зрения задач по структурам данных и алгоритмам, отсутствие такого разрыва обычно не критично, если логика программы остается корректной. Но с точки зрения стандартной библиотеки разорвать связь безопаснее и логичнее. Если этого не сделать и удаленный узел не будет нормально собран, он может мешать освобождению памяти последующих узлов.</p>
|
||||
<p><strong>Q</strong>: Временная сложность вставки и удаления в связном списке равна <span class="arithmatex">\(O(1)\)</span> . Но до вставки или удаления обычно еще нужно потратить <span class="arithmatex">\(O(n)\)</span> на поиск элемента. Почему тогда общая сложность не <span class="arithmatex">\(O(n)\)</span> ?</p>
|
||||
<p>Если сначала искать элемент, а потом удалять его, то временная сложность действительно будет <span class="arithmatex">\(O(n)\)</span> . Однако преимущество связного списка с <span class="arithmatex">\(O(1)\)</span> вставкой и удалением проявляется в других сценариях. Например, двустороннюю очередь удобно реализовывать именно на связном списке: мы поддерживаем указатели на голову и хвост, и тогда каждая операция вставки или удаления остается <span class="arithmatex">\(O(1)\)</span> .</p>
|
||||
<p><strong>Q</strong>: На рисунке "Определение связного списка и способ хранения" светло-голубой блок с указателем узла - это отдельный адрес памяти? Или он делит память пополам со значением узла?</p>
|
||||
<p>Этот рисунок дает только качественное представление; количественно все зависит от конкретных условий.</p>
|
||||
<p><strong>Q</strong>: На рисунке «Определение связного списка и способ хранения» светло-голубой блок с указателем узла - это отдельный адрес памяти? Или он делит память пополам со значением узла?</p>
|
||||
<p>Этот рисунок дает только качественное представление. Количественно все зависит от конкретных условий.</p>
|
||||
<ul>
|
||||
<li>Значения узлов разных типов занимают разный объем памяти, например <code>int</code> , <code>long</code> , <code>double</code> и объекты-экземпляры.</li>
|
||||
<li>Размер памяти, занимаемой переменной-указателем, зависит от операционной системы и среды компиляции и обычно составляет 8 байт или 4 байта.</li>
|
||||
</ul>
|
||||
<p><strong>Q</strong>: Всегда ли добавление элемента в конец списка имеет сложность <span class="arithmatex">\(O(1)\)</span> ?</p>
|
||||
<p>Если при добавлении элемента длина списка превышается, то сначала приходится расширять список, а уже затем добавлять новый элемент. Система выделяет новый участок памяти и переносит туда все элементы исходного списка, и в этот момент временная сложность становится <span class="arithmatex">\(O(n)\)</span> .</p>
|
||||
<p><strong>Q</strong>: В утверждении "появление списка сильно повысило практическую полезность массива, но может приводить к потере части памяти" под потерями памяти имеется в виду дополнительная память под такие переменные, как емкость, длина и коэффициент расширения?</p>
|
||||
<p>Потери памяти здесь в основном имеют два значения: во-первых, список обычно имеет некоторую начальную емкость, которая может быть нам не нужна целиком; во-вторых, чтобы избежать слишком частых расширений, емкость при расширении обычно умножается на некоторый коэффициент, например <span class="arithmatex">\(\times 1.5\)</span> . Из-за этого появляется много пустых слотов, которые обычно нельзя полностью заполнить.</p>
|
||||
<p><strong>Q</strong>: В утверждении «появление списка сильно повысило практическую полезность массива, но может приводить к потере части памяти» под потерями памяти имеется в виду дополнительная память под такие переменные, как емкость, длина и коэффициент расширения?</p>
|
||||
<p>Потери памяти здесь в основном имеют два значения: во-первых, список обычно имеет некоторую начальную емкость, которая может быть нам не нужна целиком. Во-вторых, чтобы избежать слишком частых расширений, емкость при расширении обычно умножается на некоторый коэффициент, например <span class="arithmatex">\(\times 1.5\)</span> . Из-за этого появляется много пустых слотов, которые обычно нельзя полностью заполнить.</p>
|
||||
<p><strong>Q</strong>: В Python после инициализации <code>n = [1, 2, 3]</code> адреса этих трех элементов выглядят непрерывными, но после <code>m = [2, 1, 3]</code> можно заметить, что <code>id</code> элементов не идут подряд, а совпадают с одинаковыми числами из <code>n</code> . Если адреса элементов не непрерывны, остается ли <code>m</code> массивом?</p>
|
||||
<p>Предположим, что элементами списка являются узлы <code>n = [n1, n2, n3, n4, n5]</code> . Обычно эти 5 объектов-узлов тоже будут храниться в разных местах памяти. Однако, имея индекс списка, мы по-прежнему можем за <span class="arithmatex">\(O(1)\)</span> получить адрес памяти соответствующего узла и обратиться к нему. Это связано с тем, что в массиве хранятся ссылки на узлы, а не сами узлы.</p>
|
||||
<p>В отличие от многих других языков, в Python даже числа обернуты в объекты, и в списке хранятся не сами числа, а ссылки на них. Поэтому мы и наблюдаем, что одинаковые числа в двух массивах имеют один и тот же <code>id</code> , а адреса этих чисел не обязаны быть непрерывными.</p>
|
||||
@@ -4411,8 +4411,8 @@
|
||||
<p>Если нужно, чтобы каждый <code>[0]</code> был независимым, можно использовать <code>res = [[0] for _ in range(n)]</code> . В этом варианте создаются <span class="arithmatex">\(n\)</span> независимых объектов-списков <code>[0]</code> .</p>
|
||||
<p><strong>Q</strong>: Операция <code>res = [0] * n</code> создает список. Каждый целочисленный <code>0</code> в нем независим?</p>
|
||||
<p>В этом списке все целые числа <code>0</code> являются ссылками на один и тот же объект. Это связано с тем, что Python использует механизм кэш-пула для маленьких целых чисел (обычно от -5 до 256), чтобы максимально переиспользовать объекты и повысить производительность.</p>
|
||||
<p>Хотя все элементы указывают на один и тот же объект, мы все равно можем независимо изменять элементы списка, потому что целые числа в Python - это "неизменяемые объекты". Когда мы изменяем некоторый элемент, на самом деле происходит переключение ссылки на другой объект, а не изменение исходного объекта.</p>
|
||||
<p>Однако если элементами списка являются "изменяемые объекты" (например списки, словари или экземпляры классов), то изменение одного элемента прямо меняет сам объект, и все элементы, ссылающиеся на него, увидят одно и то же изменение.</p>
|
||||
<p>Хотя все элементы указывают на один и тот же объект, мы все равно можем независимо изменять элементы списка, потому что целые числа в Python - это «неизменяемые объекты». Когда мы изменяем некоторый элемент, на самом деле происходит переключение ссылки на другой объект, а не изменение исходного объекта.</p>
|
||||
<p>Однако если элементами списка являются «изменяемые объекты» (например списки, словари или экземпляры классов), то изменение одного элемента прямо меняет сам объект, и все элементы, ссылающиеся на него, увидят одно и то же изменение.</p>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
|
||||
Reference in New Issue
Block a user