mirror of
https://github.com/krahets/hello-algo.git
synced 2026-08-16 05:30:59 +00:00
deploy
This commit is contained in:
@@ -5950,13 +5950,13 @@
|
||||
</details>
|
||||
<p>Следует отметить, что когда связный список становится очень длинным, эффективность поиска <span class="arithmatex">\(O(n)\)</span> оказывается низкой. <strong>В этом случае список можно преобразовать в AVL-дерево или красно-черное дерево</strong> , чтобы оптимизировать временную сложность поиска до <span class="arithmatex">\(O(\log n)\)</span> .</p>
|
||||
<h2 id="622">6.2.2 Открытая адресация<a class="headerlink" href="#622" title="Permanent link">¶</a></h2>
|
||||
<p><u>Открытая адресация (open addressing)</u> не вводит дополнительных структур данных, а обрабатывает хеш-коллизии с помощью многократного пробирования; основные варианты пробирования включают линейное пробирование, квадратичное пробирование и повторное хеширование.</p>
|
||||
<p><u>Открытая адресация (open addressing)</u> не вводит дополнительных структур данных, а обрабатывает хеш-коллизии с помощью многократного пробирования. Основные варианты пробирования включают линейное пробирование, квадратичное пробирование и повторное хеширование.</p>
|
||||
<p>Ниже на примере линейного пробирования рассмотрим механизм работы хеш-таблицы с открытой адресацией.</p>
|
||||
<h3 id="1">1. Линейное пробирование<a class="headerlink" href="#1" title="Permanent link">¶</a></h3>
|
||||
<p>Линейное пробирование использует линейный поиск с фиксированным шагом. Его методы работы отличаются от обычной хеш-таблицы.</p>
|
||||
<ul>
|
||||
<li><strong>Вставка элемента</strong>: по хеш-функции вычисляется индекс бакета; если бакет уже занят, то от места конфликта выполняется линейный обход вперед (шаг обычно равен <span class="arithmatex">\(1\)</span> ), пока не будет найден пустой бакет, после чего элемент вставляется туда.</li>
|
||||
<li><strong>Поиск элемента</strong>: если возник конфликт, то с тем же шагом продолжается линейный обход вперед, пока не будет найден целевой элемент и возвращено <code>value</code> ; если встречается пустой бакет, это означает, что искомого элемента в хеш-таблице нет, и возвращается <code>None</code> .</li>
|
||||
<li><strong>Вставка элемента</strong>: по хеш-функции вычисляется индекс бакета. Если бакет уже занят, то от места конфликта выполняется линейный обход вперед (шаг обычно равен <span class="arithmatex">\(1\)</span> ), пока не будет найден пустой бакет, после чего элемент вставляется туда.</li>
|
||||
<li><strong>Поиск элемента</strong>: если возник конфликт, то с тем же шагом продолжается линейный обход вперед, пока не будет найден целевой элемент и возвращено <code>value</code>. Если встречается пустой бакет, это означает, что искомого элемента в хеш-таблице нет, и возвращается <code>None</code> .</li>
|
||||
</ul>
|
||||
<p>На рисунке 6-6 показано распределение пар ключ-значение в хеш-таблице с открытой адресацией (линейное пробирование). Для этой хеш-функции все <code>key</code> с одинаковыми двумя последними цифрами отображаются в один и тот же бакет. Благодаря линейному пробированию они по очереди сохраняются в этом бакете и в следующих за ним бакетах.</p>
|
||||
<p><img alt="Распределение пар ключ-значение в хеш-таблице с открытой адресацией (линейное пробирование)" class="animation-figure" src="../hash_collision.assets/hash_table_linear_probing.png" /></p>
|
||||
@@ -5968,7 +5968,7 @@
|
||||
<p align="center"> Рисунок 6-7 Проблема поиска после удаления элемента в открытой адресации </p>
|
||||
|
||||
<p>Чтобы решить эту проблему, можно использовать механизм <u>ленивого удаления (lazy deletion)</u>: он не удаляет элемент из хеш-таблицы напрямую, **а помечает этот бакет специальной константой <code>TOMBSTONE</code> **. В этом механизме и <code>None</code> , и <code>TOMBSTONE</code> означают пустой бакет, и оба могут быть использованы для размещения пары ключ-значение. Но есть важное различие: при линейном пробировании, встретив <code>TOMBSTONE</code> , нужно продолжать обход, потому что ниже него все еще могут существовать пары ключ-значение.</p>
|
||||
<p>Однако <strong>ленивое удаление может ускорять деградацию производительности хеш-таблицы</strong>. Это связано с тем, что каждая операция удаления создает новую метку удаления; по мере роста числа <code>TOMBSTONE</code> время поиска тоже увеличивается, потому что линейное пробирование может быть вынуждено перескакивать через множество <code>TOMBSTONE</code> , прежде чем найдет целевой элемент.</p>
|
||||
<p>Однако <strong>ленивое удаление может ускорять деградацию производительности хеш-таблицы</strong>. Это связано с тем, что каждая операция удаления создает новую метку удаления. По мере роста числа <code>TOMBSTONE</code> время поиска тоже увеличивается, потому что линейное пробирование может быть вынуждено перескакивать через множество <code>TOMBSTONE</code> , прежде чем найдет целевой элемент.</p>
|
||||
<p>Поэтому имеет смысл при линейном пробировании запоминать индекс первого встреченного <code>TOMBSTONE</code> и затем менять найденный целевой элемент местами с этим <code>TOMBSTONE</code> . Преимущество такого подхода в том, что при каждом поиске или добавлении элемент будет перемещаться в бакет, расположенный ближе к его идеальной позиции (начальной точке пробирования), а значит, эффективность поиска улучшится.</p>
|
||||
<p>Ниже приведена реализация хеш-таблицы с открытой адресацией, то есть с линейным пробированием, включающая ленивое удаление. Чтобы пространство хеш-таблицы использовалось более полно, мы рассматриваем ее как кольцевой массив: когда обход выходит за конец массива, он возвращается к началу и продолжается.</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="2:13"><input checked="checked" id="__tabbed_2_1" name="__tabbed_2" type="radio" /><input id="__tabbed_2_2" name="__tabbed_2" type="radio" /><input id="__tabbed_2_3" name="__tabbed_2" type="radio" /><input id="__tabbed_2_4" name="__tabbed_2" type="radio" /><input id="__tabbed_2_5" name="__tabbed_2" type="radio" /><input id="__tabbed_2_6" name="__tabbed_2" type="radio" /><input id="__tabbed_2_7" name="__tabbed_2" type="radio" /><input id="__tabbed_2_8" name="__tabbed_2" type="radio" /><input id="__tabbed_2_9" name="__tabbed_2" type="radio" /><input id="__tabbed_2_10" name="__tabbed_2" type="radio" /><input id="__tabbed_2_11" name="__tabbed_2" type="radio" /><input id="__tabbed_2_12" name="__tabbed_2" type="radio" /><input id="__tabbed_2_13" name="__tabbed_2" type="radio" /><div class="tabbed-labels"><label for="__tabbed_2_1">Python</label><label for="__tabbed_2_2">C++</label><label for="__tabbed_2_3">Java</label><label for="__tabbed_2_4">C#</label><label for="__tabbed_2_5">Go</label><label for="__tabbed_2_6">Swift</label><label for="__tabbed_2_7">JS</label><label for="__tabbed_2_8">TS</label><label for="__tabbed_2_9">Dart</label><label for="__tabbed_2_10">Rust</label><label for="__tabbed_2_11">C</label><label for="__tabbed_2_12">Kotlin</label><label for="__tabbed_2_13">Ruby</label></div>
|
||||
@@ -7673,7 +7673,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<h3 id="2">2. Квадратичное пробирование<a class="headerlink" href="#2" title="Permanent link">¶</a></h3>
|
||||
<p>Квадратичное пробирование похоже на линейное пробирование и тоже является одной из распространенных стратегий открытой адресации. При возникновении конфликта оно не пропускает фиксированное число шагов, а переходит на расстояние, равное "квадрату числа попыток", то есть на <span class="arithmatex">\(1, 4, 9, \dots\)</span> шагов.</p>
|
||||
<p>Квадратичное пробирование похоже на линейное пробирование и тоже является одной из распространенных стратегий открытой адресации. При возникновении конфликта оно не пропускает фиксированное число шагов, а переходит на расстояние, равное «квадрату числа попыток», то есть на <span class="arithmatex">\(1, 4, 9, \dots\)</span> шагов.</p>
|
||||
<p>Квадратичное пробирование имеет следующие основные преимущества.</p>
|
||||
<ul>
|
||||
<li>Квадратичное пробирование пытается смягчить эффект кластеризации линейного пробирования, так как пропускает расстояния, равные квадрату номера попытки.</li>
|
||||
@@ -7688,7 +7688,7 @@
|
||||
<p>Как видно из названия, метод повторного хеширования использует для пробирования несколько хеш-функций <span class="arithmatex">\(f_1(x)\)</span>, <span class="arithmatex">\(f_2(x)\)</span>, <span class="arithmatex">\(f_3(x)\)</span>, <span class="arithmatex">\(\dots\)</span> .</p>
|
||||
<ul>
|
||||
<li><strong>Вставка элемента</strong>: если хеш-функция <span class="arithmatex">\(f_1(x)\)</span> вызывает конфликт, то пробуем <span class="arithmatex">\(f_2(x)\)</span> , и так далее, пока не будет найдено пустое место для вставки элемента.</li>
|
||||
<li><strong>Поиск элемента</strong>: поиск идет в том же порядке хеш-функций, пока не будет найден целевой элемент; если встречается пустая позиция или уже были опробованы все хеш-функции, это означает, что элемента в хеш-таблице нет, и возвращается <code>None</code> .</li>
|
||||
<li><strong>Поиск элемента</strong>: поиск идет в том же порядке хеш-функций, пока не будет найден целевой элемент. Если встречается пустая позиция или уже были опробованы все хеш-функции, это означает, что элемента в хеш-таблице нет, и возвращается <code>None</code> .</li>
|
||||
</ul>
|
||||
<p>По сравнению с линейным пробированием метод повторного хеширования меньше подвержен кластеризации, но несколько хеш-функций приносят дополнительные вычислительные затраты.</p>
|
||||
<div class="admonition tip">
|
||||
@@ -7700,7 +7700,7 @@
|
||||
<ul>
|
||||
<li>Python использует открытую адресацию. В словаре <code>dict</code> для пробирования применяются псевдослучайные числа.</li>
|
||||
<li>Java использует метод цепочек. Начиная с JDK 1.8, когда длина массива внутри <code>HashMap</code> достигает 64, а длина списка достигает 8, этот список преобразуется в красно-черное дерево для повышения производительности поиска.</li>
|
||||
<li>Go использует метод цепочек. В Go установлено, что каждый бакет может хранить не более 8 пар ключ-значение; при переполнении подключается overflow-бакет, а когда таких бакетов становится слишком много, выполняется специальное расширение того же масштаба, чтобы сохранить производительность.</li>
|
||||
<li>Go использует метод цепочек. В Go установлено, что каждый бакет может хранить не более 8 пар ключ-значение. При переполнении подключается overflow-бакет, а когда таких бакетов становится слишком много, выполняется специальное расширение того же масштаба, чтобы сохранить производительность.</li>
|
||||
</ul>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
Reference in New Issue
Block a user