mirror of
https://github.com/krahets/hello-algo.git
synced 2026-07-12 15:36:05 +00:00
deploy
This commit is contained in:
@@ -4380,7 +4380,7 @@
|
||||
<!-- Page content -->
|
||||
<h1 id="113">11.3 Сортировка пузырьком<a class="headerlink" href="#113" title="Permanent link">¶</a></h1>
|
||||
<p><u>Сортировка пузырьком (bubble sort)</u> реализует сортировку путем последовательного сравнения и обмена соседних элементов. Этот процесс напоминает всплытие пузырьков снизу вверх, откуда и произошло название алгоритма.</p>
|
||||
<p>Как показано на рисунке 11-4, процесс "всплытия" можно смоделировать через операцию обмена элементов: начиная от левого края массива и двигаясь вправо, мы последовательно сравниваем соседние элементы и, если "левый элемент > правый элемент", меняем их местами. После завершения прохода максимальный элемент будет перемещен в самый правый конец массива.</p>
|
||||
<p>Как показано на рисунке 11-4, процесс «всплытия» можно смоделировать через операцию обмена элементов: начиная от левого края массива и двигаясь вправо, мы последовательно сравниваем соседние элементы и, если «левый элемент > правый элемент», меняем их местами. После завершения прохода максимальный элемент будет перемещен в самый правый конец массива.</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="1:7"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1"><1></label><label for="__tabbed_1_2"><2></label><label for="__tabbed_1_3"><3></label><label for="__tabbed_1_4"><4></label><label for="__tabbed_1_5"><5></label><label for="__tabbed_1_6"><6></label><label for="__tabbed_1_7"><7></label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -4409,11 +4409,11 @@
|
||||
<p align="center"> Рисунок 11-4 Моделирование пузырька через обмен элементов </p>
|
||||
|
||||
<h2 id="1131">11.3.1 Алгоритм<a class="headerlink" href="#1131" title="Permanent link">¶</a></h2>
|
||||
<p>Пусть длина массива равна <span class="arithmatex">\(n\)</span> ; тогда шаги сортировки пузырьком показаны на рисунке 11-5.</p>
|
||||
<p>Пусть длина массива равна <span class="arithmatex">\(n\)</span>. Тогда шаги сортировки пузырьком показаны на рисунке 11-5.</p>
|
||||
<ol>
|
||||
<li>Сначала выполнить один проход "всплытия" по <span class="arithmatex">\(n\)</span> элементам, <strong>переместив максимальный элемент массива на правильную позицию</strong>.</li>
|
||||
<li>Затем выполнить "всплытие" по оставшимся <span class="arithmatex">\(n - 1\)</span> элементам, <strong>переместив второй по величине элемент на правильную позицию</strong>.</li>
|
||||
<li>Продолжать по аналогии; после <span class="arithmatex">\(n - 1\)</span> раундов "всплытия" <strong>первые <span class="arithmatex">\(n - 1\)</span> по величине элементы окажутся на правильных позициях</strong>.</li>
|
||||
<li>Сначала выполнить один проход «всплытия» по <span class="arithmatex">\(n\)</span> элементам, <strong>переместив максимальный элемент массива на правильную позицию</strong>.</li>
|
||||
<li>Затем выполнить «всплытие» по оставшимся <span class="arithmatex">\(n - 1\)</span> элементам, <strong>переместив второй по величине элемент на правильную позицию</strong>.</li>
|
||||
<li>Продолжать по аналогии. После <span class="arithmatex">\(n - 1\)</span> раундов «всплытия» <strong>первые <span class="arithmatex">\(n - 1\)</span> по величине элементы окажутся на правильных позициях</strong>.</li>
|
||||
<li>Оставшийся единственный элемент обязательно является минимальным, сортировать его уже не нужно, поэтому сортировка завершена.</li>
|
||||
</ol>
|
||||
<p><img alt="Процесс сортировки пузырьком" class="animation-figure" src="../bubble_sort.assets/bubble_sort_overview.png" /></p>
|
||||
@@ -4648,8 +4648,8 @@
|
||||
<div style="margin-top: 5px;"><a href="https://pythontutor.com/iframe-embed.html#code=def%20bubble_sort%28nums%3A%20list%5Bint%5D%29%3A%0A%20%20%20%20%22%22%22%D0%9F%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%B0%D1%8F%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B0%22%22%22%0A%20%20%20%20n%20%3D%20len%28nums%29%0A%20%20%20%20%23%20%D0%92%D0%BD%D0%B5%D1%88%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D1%8B%D0%B9%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%20%5B0%2C%20i%5D%0A%20%20%20%20for%20i%20in%20range%28n%20-%201%2C%200%2C%20-1%29%3A%0A%20%20%20%20%20%20%20%20%23%20%D0%92%D0%BD%D1%83%D1%82%D1%80%D0%B5%D0%BD%D0%BD%D0%B8%D0%B9%20%D1%86%D0%B8%D0%BA%D0%BB%3A%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D1%81%D1%82%D0%B8%D1%82%D1%8C%20%D0%BC%D0%B0%D0%BA%D1%81%D0%B8%D0%BC%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9%20%D1%8D%D0%BB%D0%B5%D0%BC%D0%B5%D0%BD%D1%82%20%D0%BD%D0%B5%D0%BE%D1%82%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B3%D0%BE%20%D0%B4%D0%B8%D0%B0%D0%BF%D0%B0%D0%B7%D0%BE%D0%BD%D0%B0%20%5B0%2C%20i%5D%20%D0%B2%20%D0%B5%D0%B3%D0%BE%20%D0%BF%D1%80%D0%B0%D0%B2%D1%8B%D0%B9%20%D0%BA%D0%BE%D0%BD%D0%B5%D1%86%0A%20%20%20%20%20%20%20%20for%20j%20in%20range%28i%29%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20nums%5Bj%5D%20%3E%20nums%5Bj%20%2B%201%5D%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%23%20%D0%9F%D0%BE%D0%BC%D0%B5%D0%BD%D1%8F%D1%82%D1%8C%20%D0%BC%D0%B5%D1%81%D1%82%D0%B0%D0%BC%D0%B8%20nums%5Bj%5D%20%D0%B8%20nums%5Bj%20%2B%201%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20nums%5Bj%5D%2C%20nums%5Bj%20%2B%201%5D%20%3D%20nums%5Bj%20%2B%201%5D%2C%20nums%5Bj%5D%0A%0A%22%22%22Driver%20Code%22%22%22%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20nums%20%3D%20%5B4%2C%201%2C%203%2C%201%2C%205%2C%202%5D%0A%20%20%20%20bubble_sort%28nums%29%0A%20%20%20%20print%28%22%D0%9F%D0%BE%D1%81%D0%BB%D0%B5%20%D0%BF%D1%83%D0%B7%D1%8B%D1%80%D1%8C%D0%BA%D0%BE%D0%B2%D0%BE%D0%B9%20%D1%81%D0%BE%D1%80%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%BA%D0%B8%20nums%20%3D%22%2C%20nums%29&codeDivHeight=800&codeDivWidth=600&cumulative=false&curInstr=4&heapPrimitives=nevernest&origin=opt-frontend.js&py=311&rawInputLstJSON=%5B%5D&textReferences=false" target="_blank" rel="noopener noreferrer">Во весь экран ></a></div></p>
|
||||
</details>
|
||||
<h2 id="1132">11.3.2 Оптимизация эффективности<a class="headerlink" href="#1132" title="Permanent link">¶</a></h2>
|
||||
<p>Если в каком-либо раунде "всплытия" не произошло ни одного обмена, значит, массив уже отсортирован и можно сразу вернуть результат. Поэтому можно добавить флаг <code>flag</code> для отслеживания этой ситуации и немедленного выхода.</p>
|
||||
<p>После такой оптимизации худшая и средняя временные сложности сортировки пузырьком по-прежнему равны <span class="arithmatex">\(O(n^2)\)</span> ; однако если входной массив уже полностью упорядочен, достигается лучшая временная сложность <span class="arithmatex">\(O(n)\)</span> .</p>
|
||||
<p>Если в каком-либо раунде «всплытия» не произошло ни одного обмена, значит, массив уже отсортирован и можно сразу вернуть результат. Поэтому можно добавить флаг <code>flag</code> для отслеживания этой ситуации и немедленного выхода.</p>
|
||||
<p>После такой оптимизации худшая и средняя временные сложности сортировки пузырьком по-прежнему равны <span class="arithmatex">\(O(n^2)\)</span>. Однако если входной массив уже полностью упорядочен, достигается лучшая временная сложность <span class="arithmatex">\(O(n)\)</span> .</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="3:13"><input checked="checked" id="__tabbed_3_1" name="__tabbed_3" type="radio" /><input id="__tabbed_3_2" name="__tabbed_3" type="radio" /><input id="__tabbed_3_3" name="__tabbed_3" type="radio" /><input id="__tabbed_3_4" name="__tabbed_3" type="radio" /><input id="__tabbed_3_5" name="__tabbed_3" type="radio" /><input id="__tabbed_3_6" name="__tabbed_3" type="radio" /><input id="__tabbed_3_7" name="__tabbed_3" type="radio" /><input id="__tabbed_3_8" name="__tabbed_3" type="radio" /><input id="__tabbed_3_9" name="__tabbed_3" type="radio" /><input id="__tabbed_3_10" name="__tabbed_3" type="radio" /><input id="__tabbed_3_11" name="__tabbed_3" type="radio" /><input id="__tabbed_3_12" name="__tabbed_3" type="radio" /><input id="__tabbed_3_13" name="__tabbed_3" type="radio" /><div class="tabbed-labels"><label for="__tabbed_3_1">Python</label><label for="__tabbed_3_2">C++</label><label for="__tabbed_3_3">Java</label><label for="__tabbed_3_4">C#</label><label for="__tabbed_3_5">Go</label><label for="__tabbed_3_6">Swift</label><label for="__tabbed_3_7">JS</label><label for="__tabbed_3_8">TS</label><label for="__tabbed_3_9">Dart</label><label for="__tabbed_3_10">Rust</label><label for="__tabbed_3_11">C</label><label for="__tabbed_3_12">Kotlin</label><label for="__tabbed_3_13">Ruby</label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -4944,9 +4944,9 @@
|
||||
</details>
|
||||
<h2 id="1133">11.3.3 Характеристики алгоритма<a class="headerlink" href="#1133" title="Permanent link">¶</a></h2>
|
||||
<ul>
|
||||
<li><strong>Временная сложность равна <span class="arithmatex">\(O(n^2)\)</span>, алгоритм адаптивен</strong>: длины диапазонов, проходящих "всплытие" в разных раундах, последовательно равны <span class="arithmatex">\(n - 1\)</span>, <span class="arithmatex">\(n - 2\)</span>, <span class="arithmatex">\(\dots\)</span>, <span class="arithmatex">\(2\)</span>, <span class="arithmatex">\(1\)</span> , а их сумма равна <span class="arithmatex">\((n - 1) n / 2\)</span> . После добавления оптимизации с <code>flag</code> лучшая временная сложность может достигать <span class="arithmatex">\(O(n)\)</span> .</li>
|
||||
<li><strong>Временная сложность равна <span class="arithmatex">\(O(n^2)\)</span>, алгоритм адаптивен</strong>: длины диапазонов, проходящих «всплытие» в разных раундах, последовательно равны <span class="arithmatex">\(n - 1\)</span>, <span class="arithmatex">\(n - 2\)</span>, <span class="arithmatex">\(\dots\)</span>, <span class="arithmatex">\(2\)</span>, <span class="arithmatex">\(1\)</span> , а их сумма равна <span class="arithmatex">\((n - 1) n / 2\)</span> . После добавления оптимизации с <code>flag</code> лучшая временная сложность может достигать <span class="arithmatex">\(O(n)\)</span> .</li>
|
||||
<li><strong>Пространственная сложность равна <span class="arithmatex">\(O(1)\)</span>, сортировка выполняется на месте</strong>: указатели <span class="arithmatex">\(i\)</span> и <span class="arithmatex">\(j\)</span> используют константный объем дополнительной памяти.</li>
|
||||
<li><strong>Стабильная сортировка</strong>: поскольку при "всплытии" равные элементы не обмениваются местами.</li>
|
||||
<li><strong>Стабильная сортировка</strong>: поскольку при «всплытии» равные элементы не обмениваются местами.</li>
|
||||
</ul>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
@@ -4379,8 +4379,8 @@
|
||||
|
||||
<!-- Page content -->
|
||||
<h1 id="118">11.8 Блочная сортировка<a class="headerlink" href="#118" title="Permanent link">¶</a></h1>
|
||||
<p>Рассмотренные выше алгоритмы сортировки относятся к "сортировкам на основе сравнений": они упорядочивают данные, сравнивая элементы друг с другом. Временная сложность таких алгоритмов не может быть лучше <span class="arithmatex">\(O(n \log n)\)</span> . Далее мы рассмотрим несколько "сортировок без сравнений", чья временная сложность может достигать линейного порядка.</p>
|
||||
<p><u>Блочная сортировка (bucket sort)</u> является типичным применением стратегии "разделяй и властвуй". Она создает набор упорядоченных по величине блоков, где каждый блок соответствует определенному диапазону данных; затем элементы равномерно распределяются по этим блокам, внутри каждого блока отдельно выполняется сортировка, а в конце результаты объединяются в порядке блоков.</p>
|
||||
<p>Рассмотренные выше алгоритмы сортировки относятся к «сортировкам на основе сравнений»: они упорядочивают данные, сравнивая элементы друг с другом. Временная сложность таких алгоритмов не может быть лучше <span class="arithmatex">\(O(n \log n)\)</span> . Далее мы рассмотрим несколько «сортировок без сравнений», чья временная сложность может достигать линейного порядка.</p>
|
||||
<p><u>Блочная сортировка (bucket sort)</u> является типичным применением стратегии «разделяй и властвуй». Она создает набор упорядоченных по величине блоков, где каждый блок соответствует определенному диапазону данных. Затем элементы равномерно распределяются по этим блокам, внутри каждого блока отдельно выполняется сортировка, а в конце результаты объединяются в порядке блоков.</p>
|
||||
<h2 id="1181">11.8.1 Алгоритм<a class="headerlink" href="#1181" title="Permanent link">¶</a></h2>
|
||||
<p>Рассмотрим массив длины <span class="arithmatex">\(n\)</span>, элементы которого являются числами с плавающей запятой из диапазона <span class="arithmatex">\([0, 1)\)</span> . Процесс блочной сортировки показан на рисунке 11-13.</p>
|
||||
<ol>
|
||||
@@ -4798,9 +4798,9 @@
|
||||
<li>Является ли блочная сортировка стабильной, зависит от того, стабилен ли алгоритм сортировки внутри каждого блока.</li>
|
||||
</ul>
|
||||
<h2 id="1183">11.8.3 Как добиться равномерного распределения<a class="headerlink" href="#1183" title="Permanent link">¶</a></h2>
|
||||
<p>Теоретически временная сложность блочной сортировки может достигать <span class="arithmatex">\(O(n)\)</span> ; <strong>ключ к этому - как можно более равномерно распределить элементы по блокам</strong>. На практике данные часто распределены неравномерно. Например, если нужно распределить все товары на маркетплейсе по 10 ценовым блокам, количество товаров дешевле 100 рублей может быть очень большим, а товаров дороже 1000 рублей - очень маленьким. Если просто разбить диапазон цен на 10 равных частей, число товаров в каждом блоке будет сильно различаться.</p>
|
||||
<p>Теоретически временная сложность блочной сортировки может достигать <span class="arithmatex">\(O(n)\)</span>. <strong>Ключ к этому - как можно более равномерно распределить элементы по блокам</strong>. На практике данные часто распределены неравномерно. Например, если нужно распределить все товары на маркетплейсе по 10 ценовым блокам, количество товаров дешевле 100 рублей может быть очень большим, а товаров дороже 1000 рублей - очень маленьким. Если просто разбить диапазон цен на 10 равных частей, число товаров в каждом блоке будет сильно различаться.</p>
|
||||
<p>Чтобы добиться более равномерного распределения, можно сначала задать грубую линию раздела и приблизительно распределить данные по 3 блокам. <strong>После этого блоки с большим числом товаров можно снова делить на 3 блока и продолжать процесс до тех пор, пока число элементов в каждом блоке не станет примерно одинаковым</strong>.</p>
|
||||
<p>Как показано на рисунке 11-14, по сути этот метод строит рекурсивное дерево, цель которого - сделать значения в листьях как можно более равномерными. Конечно, совсем не обязательно каждый раз делить данные именно на 3 блока; конкретную схему разбиения можно выбирать в зависимости от свойств данных.</p>
|
||||
<p>Как показано на рисунке 11-14, по сути этот метод строит рекурсивное дерево, цель которого - сделать значения в листьях как можно более равномерными. Конечно, совсем не обязательно каждый раз делить данные именно на 3 блока. Конкретную схему разбиения можно выбирать в зависимости от свойств данных.</p>
|
||||
<p><img alt="Рекурсивное разбиение по блокам" class="animation-figure" src="../bucket_sort.assets/scatter_in_buckets_recursively.png" /></p>
|
||||
<p align="center"> Рисунок 11-14 Рекурсивное разбиение по блокам </p>
|
||||
|
||||
|
||||
@@ -4403,10 +4403,10 @@
|
||||
<h1 id="119">11.9 Сортировка подсчетом<a class="headerlink" href="#119" title="Permanent link">¶</a></h1>
|
||||
<p><u>Сортировка подсчетом (counting sort)</u> реализует сортировку за счет подсчета количества вхождений элементов и обычно используется для массивов целых чисел.</p>
|
||||
<h2 id="1191">11.9.1 Простая реализация<a class="headerlink" href="#1191" title="Permanent link">¶</a></h2>
|
||||
<p>Сначала рассмотрим простой пример. Дан массив <code>nums</code> длины <span class="arithmatex">\(n\)</span> , элементы которого являются "неотрицательными целыми числами". Общий процесс сортировки подсчетом показан на рисунке 11-16.</p>
|
||||
<p>Сначала рассмотрим простой пример. Дан массив <code>nums</code> длины <span class="arithmatex">\(n\)</span> , элементы которого являются «неотрицательными целыми числами». Общий процесс сортировки подсчетом показан на рисунке 11-16.</p>
|
||||
<ol>
|
||||
<li>Пройти по массиву, найти в нем максимальное число, обозначить его как <span class="arithmatex">\(m\)</span> , а затем создать вспомогательный массив <code>counter</code> длины <span class="arithmatex">\(m + 1\)</span> .</li>
|
||||
<li><strong>С помощью <code>counter</code> подсчитать, сколько раз каждое число встречается в <code>nums</code></strong>; при этом <code>counter[num]</code> хранит число вхождений значения <code>num</code> . Делается это просто: достаточно пройти по <code>nums</code> (пусть текущее число равно <code>num</code> ) и на каждом шаге увеличить <code>counter[num]</code> на <span class="arithmatex">\(1\)</span> .</li>
|
||||
<li><strong>С помощью <code>counter</code> подсчитать, сколько раз каждое число встречается в <code>nums</code></strong>. При этом <code>counter[num]</code> хранит число вхождений значения <code>num</code> . Делается это просто: достаточно пройти по <code>nums</code> (пусть текущее число равно <code>num</code> ) и на каждом шаге увеличить <code>counter[num]</code> на <span class="arithmatex">\(1\)</span> .</li>
|
||||
<li><strong>Поскольку индексы массива <code>counter</code> изначально упорядочены, можно считать, что все числа уже отсортированы</strong>. Далее остается пройти по <code>counter</code> и в соответствии с числом вхождений записать значения обратно в <code>nums</code> в порядке возрастания.</li>
|
||||
</ol>
|
||||
<p><img alt="Процесс сортировки подсчетом" class="animation-figure" src="../counting_sort.assets/counting_sort_overview.png" /></p>
|
||||
@@ -4743,7 +4743,7 @@
|
||||
</div>
|
||||
<h2 id="1192">11.9.2 Полная реализация<a class="headerlink" href="#1192" title="Permanent link">¶</a></h2>
|
||||
<p>Внимательный читатель мог заметить, что <strong>если входные данные представлены объектами, то описанный выше шаг <code>3.</code> перестает работать</strong>. Например, если входными данными являются объекты товаров и мы хотим отсортировать их по цене (полю класса), то описанный алгоритм сможет выдать только отсортированный ряд цен, но не исходные объекты в нужном порядке.</p>
|
||||
<p>Как же получить корректный порядок исходных данных? Сначала вычислим "префиксную сумму" массива <code>counter</code> . Как следует из названия, префиксная сумма в индексе <code>i</code> , обозначаемая как <code>prefix[i]</code> , равна сумме первых <code>i</code> элементов массива:</p>
|
||||
<p>Как же получить корректный порядок исходных данных? Сначала вычислим «префиксную сумму» массива <code>counter</code> . Как следует из названия, префиксная сумма в индексе <code>i</code> , обозначаемая как <code>prefix[i]</code> , равна сумме первых <code>i</code> элементов массива:</p>
|
||||
<div class="arithmatex">\[
|
||||
\text{prefix}[i] = \sum_{j=0}^i \text{counter[j]}
|
||||
\]</div>
|
||||
@@ -4752,7 +4752,7 @@
|
||||
<li>Записать <code>num</code> в массив <code>res</code> по индексу <code>prefix[num] - 1</code> .</li>
|
||||
<li>Уменьшить префиксную сумму <code>prefix[num]</code> на <span class="arithmatex">\(1\)</span> , чтобы получить индекс следующего размещения элемента <code>num</code> .</li>
|
||||
</ol>
|
||||
<p>После завершения прохода массив <code>res</code> будет содержать отсортированный результат; остается только переписать <code>res</code> обратно в <code>nums</code> . Полный процесс сортировки подсчетом показан на рисунке 11-17.</p>
|
||||
<p>После завершения прохода массив <code>res</code> будет содержать отсортированный результат. Остается только переписать <code>res</code> обратно в <code>nums</code> . Полный процесс сортировки подсчетом показан на рисунке 11-17.</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="2:8"><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" /><div class="tabbed-labels"><label for="__tabbed_2_1"><1></label><label for="__tabbed_2_2"><2></label><label for="__tabbed_2_3"><3></label><label for="__tabbed_2_4"><4></label><label for="__tabbed_2_5"><5></label><label for="__tabbed_2_6"><6></label><label for="__tabbed_2_7"><7></label><label for="__tabbed_2_8"><8></label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -5233,7 +5233,7 @@
|
||||
<ul>
|
||||
<li><strong>Временная сложность равна <span class="arithmatex">\(O(n + m)\)</span>, алгоритм не является адаптивным</strong> : необходимо пройти по <code>nums</code> и по <code>counter</code> , а оба этих прохода занимают линейное время. Обычно выполняется <span class="arithmatex">\(n \gg m\)</span> , поэтому временная сложность стремится к <span class="arithmatex">\(O(n)\)</span> .</li>
|
||||
<li><strong>Пространственная сложность равна <span class="arithmatex">\(O(n + m)\)</span>, сортировка не выполняется на месте</strong>: используются массивы <code>res</code> и <code>counter</code> длины <span class="arithmatex">\(n\)</span> и <span class="arithmatex">\(m\)</span> соответственно.</li>
|
||||
<li><strong>Стабильная сортировка</strong>: порядок заполнения <code>res</code> идет "справа налево", поэтому обратный проход по <code>nums</code> позволяет сохранить относительный порядок равных элементов и тем самым реализовать стабильную сортировку. Вообще говоря, прямой проход по <code>nums</code> тоже даст правильный результат сортировки, но он будет нестабильным.</li>
|
||||
<li><strong>Стабильная сортировка</strong>: порядок заполнения <code>res</code> идет «справа налево», поэтому обратный проход по <code>nums</code> позволяет сохранить относительный порядок равных элементов и тем самым реализовать стабильную сортировку. Вообще говоря, прямой проход по <code>nums</code> тоже даст правильный результат сортировки, но он будет нестабильным.</li>
|
||||
</ul>
|
||||
<h2 id="1194">11.9.4 Ограничения<a class="headerlink" href="#1194" title="Permanent link">¶</a></h2>
|
||||
<p>На этом этапе сортировка подсчетом может показаться очень изящной: она позволяет эффективно сортировать данные, опираясь только на подсчет числа вхождений. Однако условия ее применения довольно строгие.</p>
|
||||
|
||||
@@ -4359,16 +4359,16 @@
|
||||
<h1 id="117">11.7 Пирамидальная сортировка<a class="headerlink" href="#117" title="Permanent link">¶</a></h1>
|
||||
<div class="admonition tip">
|
||||
<p class="admonition-title">Tip</p>
|
||||
<p>Перед чтением этого раздела убедитесь, что вы уже изучили главу "Куча".</p>
|
||||
<p>Перед чтением этого раздела убедитесь, что вы уже изучили главу «Куча».</p>
|
||||
</div>
|
||||
<p><u>Пирамидальная сортировка (heap sort)</u> - это эффективный алгоритм сортировки, основанный на структуре данных "куча". Для его реализации можно использовать уже изученные нами "построение кучи" и "извлечение элементов из кучи".</p>
|
||||
<p><u>Пирамидальная сортировка (heap sort)</u> - это эффективный алгоритм сортировки, основанный на структуре данных «куча». Для его реализации можно использовать уже изученные нами «построение кучи» и «извлечение элементов из кучи».</p>
|
||||
<ol>
|
||||
<li>Подать на вход массив и построить из него мин-кучу; в этот момент минимальный элемент будет находиться в вершине кучи.</li>
|
||||
<li>Подать на вход массив и построить из него мин-кучу. В этот момент минимальный элемент будет находиться в вершине кучи.</li>
|
||||
<li>Непрерывно выполнять извлечение из кучи и по порядку записывать извлеченные элементы - так получится последовательность, отсортированная по возрастанию.</li>
|
||||
</ol>
|
||||
<p>Хотя этот метод и работоспособен, он требует дополнительного массива для хранения извлеченных элементов и потому расходует лишнюю память. На практике обычно используют более изящную реализацию.</p>
|
||||
<h2 id="1171">11.7.1 Алгоритм<a class="headerlink" href="#1171" title="Permanent link">¶</a></h2>
|
||||
<p>Пусть длина массива равна <span class="arithmatex">\(n\)</span> ; тогда процесс пирамидальной сортировки показан на рисунке 11-12.</p>
|
||||
<p>Пусть длина массива равна <span class="arithmatex">\(n\)</span>. Тогда процесс пирамидальной сортировки показан на рисунке 11-12.</p>
|
||||
<ol>
|
||||
<li>Подать на вход массив и построить из него макс-кучу. После этого максимальный элемент окажется в вершине кучи.</li>
|
||||
<li>Обменять элемент в вершине кучи (первый элемент) с элементом внизу кучи (последний элемент). После обмена длина кучи уменьшается на <span class="arithmatex">\(1\)</span> , а число уже отсортированных элементов увеличивается на <span class="arithmatex">\(1\)</span> .</li>
|
||||
@@ -4421,7 +4421,7 @@
|
||||
</div>
|
||||
<p align="center"> Рисунок 11-12 Шаги пирамидальной сортировки </p>
|
||||
|
||||
<p>В коде используется та же функция просеивания сверху вниз <code>sift_down()</code>, что и в главе "Куча". Важно помнить, что длина кучи уменьшается по мере извлечения максимального элемента, поэтому функции <code>sift_down()</code> нужно передавать параметр длины <span class="arithmatex">\(n\)</span> , чтобы указать текущую действительную длину кучи. Код приведен ниже:</p>
|
||||
<p>В коде используется та же функция просеивания сверху вниз <code>sift_down()</code>, что и в главе «Куча». Важно помнить, что длина кучи уменьшается по мере извлечения максимального элемента, поэтому функции <code>sift_down()</code> нужно передавать параметр длины <span class="arithmatex">\(n\)</span> , чтобы указать текущую действительную длину кучи. Код приведен ниже:</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>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
|
||||
@@ -4381,7 +4381,7 @@
|
||||
<h1 id="114">11.4 Сортировка вставками<a class="headerlink" href="#114" title="Permanent link">¶</a></h1>
|
||||
<p><u>Сортировка вставками (insertion sort)</u> - это простой алгоритм сортировки, принцип которого очень похож на ручную сортировку карт в колоде.</p>
|
||||
<p>Точнее говоря, в неотсортированном диапазоне выбирается опорный элемент, после чего он сравнивается с элементами слева в уже отсортированном диапазоне и вставляется в правильную позицию.</p>
|
||||
<p>На рисунке 11-6 показан процесс вставки элемента в массив. Пусть опорный элемент обозначен как <code>base</code> ; нам нужно сдвинуть все элементы от целевого индекса до <code>base</code> на одну позицию вправо, а затем записать <code>base</code> в целевой индекс.</p>
|
||||
<p>На рисунке 11-6 показан процесс вставки элемента в массив. Пусть опорный элемент обозначен как <code>base</code>. Нам нужно сдвинуть все элементы от целевого индекса до <code>base</code> на одну позицию вправо, а затем записать <code>base</code> в целевой индекс.</p>
|
||||
<p><img alt="Одна операция вставки" class="animation-figure" src="../insertion_sort.assets/insertion_operation.png" /></p>
|
||||
<p align="center"> Рисунок 11-6 Одна операция вставки </p>
|
||||
|
||||
@@ -4389,9 +4389,9 @@
|
||||
<p>Общий процесс сортировки вставками показан на рисунке 11-7.</p>
|
||||
<ol>
|
||||
<li>В начальном состоянии отсортирован только первый элемент массива.</li>
|
||||
<li>Выбрать второй элемент массива как <code>base</code> ; после вставки в правильную позицию <strong>первые два элемента массива окажутся отсортированными</strong>.</li>
|
||||
<li>Выбрать третий элемент как <code>base</code> ; после вставки в правильную позицию <strong>первые три элемента массива окажутся отсортированными</strong>.</li>
|
||||
<li>Продолжать по аналогии; в последнем раунде в качестве <code>base</code> берется последний элемент, и после его вставки <strong>все элементы массива будут отсортированы</strong>.</li>
|
||||
<li>Выбрать второй элемент массива как <code>base</code>. После вставки в правильную позицию <strong>первые два элемента массива окажутся отсортированными</strong>.</li>
|
||||
<li>Выбрать третий элемент как <code>base</code>. После вставки в правильную позицию <strong>первые три элемента массива окажутся отсортированными</strong>.</li>
|
||||
<li>Продолжать по аналогии. В последнем раунде в качестве <code>base</code> берется последний элемент, и после его вставки <strong>все элементы массива будут отсортированы</strong>.</li>
|
||||
</ol>
|
||||
<p><img alt="Процесс сортировки вставками" class="animation-figure" src="../insertion_sort.assets/insertion_sort_overview.png" /></p>
|
||||
<p align="center"> Рисунок 11-7 Процесс сортировки вставками </p>
|
||||
@@ -4629,11 +4629,11 @@
|
||||
</ul>
|
||||
<h2 id="1143">11.4.3 Преимущества сортировки вставками<a class="headerlink" href="#1143" title="Permanent link">¶</a></h2>
|
||||
<p>Временная сложность сортировки вставками равна <span class="arithmatex">\(O(n^2)\)</span> , а у быстрой сортировки, которую мы скоро изучим, временная сложность равна <span class="arithmatex">\(O(n \log n)\)</span> . Несмотря на более высокую асимптотическую сложность, <strong>на малых объемах данных сортировка вставками обычно работает быстрее</strong>.</p>
|
||||
<p>Этот вывод похож на сравнение линейного и двоичного поиска. Алгоритмы уровня <span class="arithmatex">\(O(n \log n)\)</span> , такие как быстрая сортировка, относятся к алгоритмам на основе стратегии "разделяй и властвуй" и обычно включают больше элементарных вычислений. Когда объем данных мал, значения <span class="arithmatex">\(n^2\)</span> и <span class="arithmatex">\(n \log n\)</span> близки друг к другу, поэтому асимптотика не доминирует, а решающим становится число элементарных операций в каждом раунде.</p>
|
||||
<p>На практике встроенные функции сортировки во многих языках программирования (например, в Java) используют сортировку вставками. Общая идея такова: для длинных массивов применять алгоритмы сортировки на основе стратегии "разделяй и властвуй", например быструю сортировку; для коротких массивов сразу использовать сортировку вставками.</p>
|
||||
<p>Этот вывод похож на сравнение линейного и двоичного поиска. Алгоритмы уровня <span class="arithmatex">\(O(n \log n)\)</span> , такие как быстрая сортировка, относятся к алгоритмам на основе стратегии «разделяй и властвуй» и обычно включают больше элементарных вычислений. Когда объем данных мал, значения <span class="arithmatex">\(n^2\)</span> и <span class="arithmatex">\(n \log n\)</span> близки друг к другу, поэтому асимптотика не доминирует, а решающим становится число элементарных операций в каждом раунде.</p>
|
||||
<p>На практике встроенные функции сортировки во многих языках программирования (например, в Java) используют сортировку вставками. Общая идея такова: для длинных массивов применять алгоритмы сортировки на основе стратегии «разделяй и властвуй», например быструю сортировку. Для коротких массивов сразу использовать сортировку вставками.</p>
|
||||
<p>Хотя сортировка пузырьком, выбором и вставками имеют одинаковую временную сложность <span class="arithmatex">\(O(n^2)\)</span> , в реальных задачах <strong>сортировка вставками используется заметно чаще, чем сортировка пузырьком и сортировка выбором</strong>. Основные причины таковы.</p>
|
||||
<ul>
|
||||
<li>Сортировка пузырьком основана на обмене элементов, для чего нужна временная переменная и суммарно выполняются 3 элементарные операции; сортировка вставками основана на присваивании элементов и требует всего 1 элементарной операции. Поэтому <strong>вычислительные затраты сортировки пузырьком обычно выше, чем у сортировки вставками</strong>.</li>
|
||||
<li>Сортировка пузырьком основана на обмене элементов, для чего нужна временная переменная и суммарно выполняются 3 элементарные операции. Сортировка вставками основана на присваивании элементов и требует всего 1 элементарной операции. Поэтому <strong>вычислительные затраты сортировки пузырьком обычно выше, чем у сортировки вставками</strong>.</li>
|
||||
<li>Временная сложность сортировки выбором в любом случае равна <span class="arithmatex">\(O(n^2)\)</span> . <strong>Если входные данные уже частично упорядочены, сортировка вставками обычно эффективнее сортировки выбором</strong>.</li>
|
||||
<li>Сортировка выбором нестабильна, поэтому ее нельзя использовать для многоуровневой сортировки.</li>
|
||||
</ul>
|
||||
|
||||
@@ -4379,21 +4379,21 @@
|
||||
|
||||
<!-- Page content -->
|
||||
<h1 id="116">11.6 Сортировка слиянием<a class="headerlink" href="#116" title="Permanent link">¶</a></h1>
|
||||
<p><u>Сортировка слиянием (merge sort)</u> - это алгоритм сортировки, основанный на стратегии "разделяй и властвуй", который включает этапы "разделения" и "слияния", показанные на рисунке 11-10.</p>
|
||||
<p><u>Сортировка слиянием (merge sort)</u> - это алгоритм сортировки, основанный на стратегии «разделяй и властвуй», который включает этапы «разделения» и «слияния», показанные на рисунке 11-10.</p>
|
||||
<ol>
|
||||
<li><strong>Этап разделения</strong>: массив рекурсивно делится пополам, и задача сортировки длинного массива превращается в задачи сортировки более коротких массивов.</li>
|
||||
<li><strong>Этап слияния</strong>: когда длина подмассива становится равной 1, разделение завершается и начинается слияние; два коротких упорядоченных массива непрерывно объединяются в один более длинный упорядоченный массив, пока процесс не завершится.</li>
|
||||
<li><strong>Этап слияния</strong>: когда длина подмассива становится равной 1, разделение завершается и начинается слияние. Два коротких упорядоченных массива непрерывно объединяются в один более длинный упорядоченный массив, пока процесс не завершится.</li>
|
||||
</ol>
|
||||
<p><img alt="Этапы разделения и слияния в сортировке слиянием" class="animation-figure" src="../merge_sort.assets/merge_sort_overview.png" /></p>
|
||||
<p align="center"> Рисунок 11-10 Этапы разделения и слияния в сортировке слиянием </p>
|
||||
|
||||
<h2 id="1161">11.6.1 Алгоритм<a class="headerlink" href="#1161" title="Permanent link">¶</a></h2>
|
||||
<p>Как показано на рисунке 11-11, на этапе "разделения" массив рекурсивно разбивается сверху вниз по середине на два подмассива.</p>
|
||||
<p>Как показано на рисунке 11-11, на этапе «разделения» массив рекурсивно разбивается сверху вниз по середине на два подмассива.</p>
|
||||
<ol>
|
||||
<li>Вычислить середину массива <code>mid</code> и рекурсивно разделить левый подмассив (интервал <code>[left, mid]</code> ) и правый подмассив (интервал <code>[mid + 1, right]</code> ).</li>
|
||||
<li>Рекурсивно повторять шаг <code>1.</code> , пока длина подмассива не станет равной 1.</li>
|
||||
</ol>
|
||||
<p>Этап "слияния" снизу вверх объединяет левый и правый подмассивы в один упорядоченный массив. Следует заметить, что начиная с подмассивов длины 1, каждый подмассив в фазе слияния уже является упорядоченным.</p>
|
||||
<p>Этап «слияния» снизу вверх объединяет левый и правый подмассивы в один упорядоченный массив. Следует заметить, что начиная с подмассивов длины 1, каждый подмассив в фазе слияния уже является упорядоченным.</p>
|
||||
<div class="tabbed-set tabbed-alternate" data-tabs="1:10"><input checked="checked" id="__tabbed_1_1" name="__tabbed_1" type="radio" /><input id="__tabbed_1_2" name="__tabbed_1" type="radio" /><input id="__tabbed_1_3" name="__tabbed_1" type="radio" /><input id="__tabbed_1_4" name="__tabbed_1" type="radio" /><input id="__tabbed_1_5" name="__tabbed_1" type="radio" /><input id="__tabbed_1_6" name="__tabbed_1" type="radio" /><input id="__tabbed_1_7" name="__tabbed_1" type="radio" /><input id="__tabbed_1_8" name="__tabbed_1" type="radio" /><input id="__tabbed_1_9" name="__tabbed_1" type="radio" /><input id="__tabbed_1_10" name="__tabbed_1" type="radio" /><div class="tabbed-labels"><label for="__tabbed_1_1"><1></label><label for="__tabbed_1_2"><2></label><label for="__tabbed_1_3"><3></label><label for="__tabbed_1_4"><4></label><label for="__tabbed_1_5"><5></label><label for="__tabbed_1_6"><6></label><label for="__tabbed_1_7"><7></label><label for="__tabbed_1_8"><8></label><label for="__tabbed_1_9"><9></label><label for="__tabbed_1_10"><10></label></div>
|
||||
<div class="tabbed-content">
|
||||
<div class="tabbed-block">
|
||||
@@ -5046,10 +5046,10 @@
|
||||
<h2 id="1163">11.6.3 Сортировка связного списка<a class="headerlink" href="#1163" title="Permanent link">¶</a></h2>
|
||||
<p>Для связных списков сортировка слиянием имеет заметное преимущество перед другими алгоритмами сортировки: <strong>пространственную сложность задачи сортировки списка можно оптимизировать до <span class="arithmatex">\(O(1)\)</span></strong>.</p>
|
||||
<ul>
|
||||
<li><strong>Этап разделения</strong>: работу по разбиению списка можно реализовать с помощью "итерации" вместо "рекурсии", тем самым устранив расход памяти на стек вызовов.</li>
|
||||
<li><strong>Этап разделения</strong>: работу по разбиению списка можно реализовать с помощью «итерации» вместо «рекурсии», тем самым устранив расход памяти на стек вызовов.</li>
|
||||
<li><strong>Этап слияния</strong>: в связном списке добавление и удаление узлов требует только изменения ссылок (указателей), поэтому при слиянии двух коротких упорядоченных списков в один длинный упорядоченный список не нужно создавать дополнительный список.</li>
|
||||
</ul>
|
||||
<p>Детали реализации достаточно сложны; заинтересованные читатели могут обратиться к соответствующим материалам самостоятельно.</p>
|
||||
<p>Детали реализации достаточно сложны. Заинтересованные читатели могут обратиться к соответствующим материалам самостоятельно.</p>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4357,13 +4357,13 @@
|
||||
|
||||
<!-- Page content -->
|
||||
<h1 id="1110">11.10 Поразрядная сортировка<a class="headerlink" href="#1110" title="Permanent link">¶</a></h1>
|
||||
<p>В предыдущем разделе была рассмотрена сортировка подсчетом: она хорошо подходит для случаев, когда объем данных <span class="arithmatex">\(n\)</span> велик, а диапазон значений <span class="arithmatex">\(m\)</span> сравнительно мал. Предположим теперь, что нужно отсортировать <span class="arithmatex">\(n = 10^6\)</span> номеров студентов, причем каждый номер представляет собой <span class="arithmatex">\(8\)</span>-значное число. Тогда диапазон данных <span class="arithmatex">\(m = 10^8\)</span> оказывается очень большим; сортировка подсчетом потребует огромного объема памяти, а поразрядная сортировка позволяет этого избежать.</p>
|
||||
<p>В предыдущем разделе была рассмотрена сортировка подсчетом: она хорошо подходит для случаев, когда объем данных <span class="arithmatex">\(n\)</span> велик, а диапазон значений <span class="arithmatex">\(m\)</span> сравнительно мал. Предположим теперь, что нужно отсортировать <span class="arithmatex">\(n = 10^6\)</span> номеров студентов, причем каждый номер представляет собой <span class="arithmatex">\(8\)</span>-значное число. Тогда диапазон данных <span class="arithmatex">\(m = 10^8\)</span> оказывается очень большим. Сортировка подсчетом потребует огромного объема памяти, а поразрядная сортировка позволяет этого избежать.</p>
|
||||
<p><u>Поразрядная сортировка (radix sort)</u> по своей основной идее совпадает с сортировкой подсчетом и тоже реализует сортировку через подсчет количества. При этом поразрядная сортировка использует соотношение между разрядами числа и последовательно сортирует данные по каждому разряду, получая итоговый упорядоченный результат.</p>
|
||||
<h2 id="11101">11.10.1 Алгоритм<a class="headerlink" href="#11101" title="Permanent link">¶</a></h2>
|
||||
<p>Рассмотрим пример со студенческими номерами: будем считать, что младший разряд имеет номер <span class="arithmatex">\(1\)</span> , а старший - номер <span class="arithmatex">\(8\)</span> . Тогда процесс поразрядной сортировки показан на рисунке 11-18.</p>
|
||||
<ol>
|
||||
<li>Инициализировать номер разряда <span class="arithmatex">\(k = 1\)</span> .</li>
|
||||
<li>Выполнить "сортировку подсчетом" по <span class="arithmatex">\(k\)</span>-му разряду студенческого номера. После этого данные будут упорядочены по <span class="arithmatex">\(k\)</span>-му разряду по возрастанию.</li>
|
||||
<li>Выполнить «сортировку подсчетом» по <span class="arithmatex">\(k\)</span>-му разряду студенческого номера. После этого данные будут упорядочены по <span class="arithmatex">\(k\)</span>-му разряду по возрастанию.</li>
|
||||
<li>Увеличить <span class="arithmatex">\(k\)</span> на <span class="arithmatex">\(1\)</span> и вернуться к шагу <code>2.</code> , продолжая процесс, пока сортировка не будет выполнена для всех разрядов.</li>
|
||||
</ol>
|
||||
<p><img alt="Процесс поразрядной сортировки" class="animation-figure" src="../radix_sort.assets/radix_sort_overview.png" /></p>
|
||||
@@ -5059,7 +5059,7 @@ x_k = \lfloor\frac{x}{d^{k-1}}\rfloor \bmod d
|
||||
<ul>
|
||||
<li><strong>Временная сложность равна <span class="arithmatex">\(O(nk)\)</span>, алгоритм не является адаптивным</strong>: пусть объем данных равен <span class="arithmatex">\(n\)</span> , числа записаны в системе счисления с основанием <span class="arithmatex">\(d\)</span> , а максимальное число разрядов равно <span class="arithmatex">\(k\)</span> . Тогда выполнение сортировки подсчетом для одного разряда требует <span class="arithmatex">\(O(n + d)\)</span> времени, а сортировка по всем <span class="arithmatex">\(k\)</span> разрядам требует <span class="arithmatex">\(O((n + d)k)\)</span> времени. Обычно <span class="arithmatex">\(d\)</span> и <span class="arithmatex">\(k\)</span> сравнительно малы, поэтому временная сложность стремится к <span class="arithmatex">\(O(n)\)</span> .</li>
|
||||
<li><strong>Пространственная сложность равна <span class="arithmatex">\(O(n + d)\)</span>, сортировка не выполняется на месте</strong>: как и в сортировке подсчетом, здесь требуются массивы <code>res</code> и <code>counter</code> длины <span class="arithmatex">\(n\)</span> и <span class="arithmatex">\(d\)</span> .</li>
|
||||
<li><strong>Стабильная сортировка</strong>: если сортировка подсчетом стабильна, то и поразрядная сортировка стабильна; если же сортировка подсчетом нестабильна, поразрядная сортировка не может гарантировать корректный результат.</li>
|
||||
<li><strong>Стабильная сортировка</strong>: если сортировка подсчетом стабильна, то и поразрядная сортировка стабильна. Если же сортировка подсчетом нестабильна, поразрядная сортировка не может гарантировать корректный результат.</li>
|
||||
</ul>
|
||||
|
||||
<!-- Source file information -->
|
||||
|
||||
@@ -4336,7 +4336,7 @@
|
||||
<!-- Page content -->
|
||||
<h1 id="112">11.2 Сортировка выбором<a class="headerlink" href="#112" title="Permanent link">¶</a></h1>
|
||||
<p><u>Сортировка выбором (selection sort)</u> работает очень просто: запускается цикл, и на каждом шаге из неотсортированного диапазона выбирается минимальный элемент, после чего он переносится в конец уже отсортированного диапазона.</p>
|
||||
<p>Пусть длина массива равна <span class="arithmatex">\(n\)</span> ; тогда процесс сортировки выбором выглядит так, как показано на рисунке 11-2.</p>
|
||||
<p>Пусть длина массива равна <span class="arithmatex">\(n\)</span>. Тогда процесс сортировки выбором выглядит так, как показано на рисунке 11-2.</p>
|
||||
<ol>
|
||||
<li>В начальном состоянии все элементы не отсортированы, то есть неотсортированный диапазон индексов равен <span class="arithmatex">\([0, n-1]\)</span> .</li>
|
||||
<li>Выбрать минимальный элемент из диапазона <span class="arithmatex">\([0, n-1]\)</span> и поменять его местами с элементом в позиции <span class="arithmatex">\(0\)</span> . После этого первый элемент массива отсортирован.</li>
|
||||
@@ -4642,7 +4642,7 @@
|
||||
</details>
|
||||
<h2 id="1121">11.2.1 Характеристики алгоритма<a class="headerlink" href="#1121" title="Permanent link">¶</a></h2>
|
||||
<ul>
|
||||
<li><strong>Временная сложность равна <span class="arithmatex">\(O(n^2)\)</span>, сортировка не является адаптивной</strong>: внешний цикл выполняется <span class="arithmatex">\(n - 1\)</span> раз; в первом раунде длина неотсортированного диапазона равна <span class="arithmatex">\(n\)</span> , а в последнем - <span class="arithmatex">\(2\)</span> , то есть отдельные раунды содержат <span class="arithmatex">\(n\)</span>, <span class="arithmatex">\(n - 1\)</span>, <span class="arithmatex">\(\dots\)</span>, <span class="arithmatex">\(3\)</span>, <span class="arithmatex">\(2\)</span> проходов внутреннего цикла, их сумма равна <span class="arithmatex">\(\frac{(n - 1)(n + 2)}{2}\)</span> .</li>
|
||||
<li><strong>Временная сложность равна <span class="arithmatex">\(O(n^2)\)</span>, сортировка не является адаптивной</strong>: внешний цикл выполняется <span class="arithmatex">\(n - 1\)</span> раз. В первом раунде длина неотсортированного диапазона равна <span class="arithmatex">\(n\)</span> , а в последнем - <span class="arithmatex">\(2\)</span> , то есть отдельные раунды содержат <span class="arithmatex">\(n\)</span>, <span class="arithmatex">\(n - 1\)</span>, <span class="arithmatex">\(\dots\)</span>, <span class="arithmatex">\(3\)</span>, <span class="arithmatex">\(2\)</span> проходов внутреннего цикла, их сумма равна <span class="arithmatex">\(\frac{(n - 1)(n + 2)}{2}\)</span> .</li>
|
||||
<li><strong>Пространственная сложность равна <span class="arithmatex">\(O(1)\)</span>, сортировка выполняется на месте</strong>: указатели <span class="arithmatex">\(i\)</span> и <span class="arithmatex">\(j\)</span> используют константный объем дополнительной памяти.</li>
|
||||
<li><strong>Нестабильная сортировка</strong>: как показано на рисунке 11-3, элемент <code>nums[i]</code> может быть переставлен вправо от другого равного ему элемента, из-за чего их относительный порядок изменится.</li>
|
||||
</ul>
|
||||
|
||||
@@ -4385,7 +4385,7 @@
|
||||
<a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a><span class="w"> </span><span class="o">(</span><span class="s1">'E'</span>,<span class="w"> </span><span class="m">23</span><span class="o">)</span>
|
||||
</code></pre></div>
|
||||
<p><strong>Адаптивность</strong>: <u>адаптивная сортировка</u> умеет использовать уже существующий порядок входных данных, чтобы сократить вычисления и добиться лучшей эффективности. Лучшая временная сложность адаптивных алгоритмов обычно лучше их средней временной сложности.</p>
|
||||
<p><strong>Основанность на сравнении</strong>: <u>сортировка на основе сравнений</u> использует операторы сравнения (<span class="arithmatex">\(<\)</span>, <span class="arithmatex">\(=\)</span>, <span class="arithmatex">\(>\)</span>), чтобы определить относительный порядок элементов и отсортировать массив; ее теоретически лучшая временная сложность равна <span class="arithmatex">\(O(n \log n)\)</span> . А вот <u>сортировка без сравнений</u> не опирается на операторы сравнения, поэтому может достигать <span class="arithmatex">\(O(n)\)</span> , но универсальность у нее ниже.</p>
|
||||
<p><strong>Основанность на сравнении</strong>: <u>сортировка на основе сравнений</u> использует операторы сравнения (<span class="arithmatex">\(<\)</span>, <span class="arithmatex">\(=\)</span>, <span class="arithmatex">\(>\)</span>), чтобы определить относительный порядок элементов и отсортировать массив. Ее теоретически лучшая временная сложность равна <span class="arithmatex">\(O(n \log n)\)</span> . А вот <u>сортировка без сравнений</u> не опирается на операторы сравнения, поэтому может достигать <span class="arithmatex">\(O(n)\)</span> , но универсальность у нее ниже.</p>
|
||||
<h2 id="1112">11.1.2 Идеальный алгоритм сортировки<a class="headerlink" href="#1112" title="Permanent link">¶</a></h2>
|
||||
<p><strong>Быстрый, выполняющийся на месте, стабильный, адаптивный и универсальный</strong>. Очевидно, что на сегодняшний день не существует алгоритма сортировки, который одновременно обладал бы всеми этими свойствами. Поэтому при выборе алгоритма сортировки нужно исходить из конкретных особенностей данных и требований задачи.</p>
|
||||
<p>Далее мы последовательно изучим разные алгоритмы сортировки и на основании приведенных выше критериев разберем их преимущества и недостатки.</p>
|
||||
|
||||
@@ -4362,9 +4362,9 @@
|
||||
<li>Сортировка пузырьком выполняет сортировку за счет обмена соседних элементов. Если добавить флаг для досрочного выхода, лучшую временную сложность пузырьковой сортировки можно оптимизировать до <span class="arithmatex">\(O(n)\)</span> .</li>
|
||||
<li>Сортировка вставками на каждом раунде вставляет элемент из неотсортированного диапазона в правильную позицию внутри отсортированного диапазона. Хотя ее временная сложность равна <span class="arithmatex">\(O(n^2)\)</span> , она очень популярна для задач сортировки небольших массивов, поскольку число элементарных операций у нее сравнительно невелико.</li>
|
||||
<li>Быстрая сортировка основана на операции разделения с опорным элементом. При неудачном выборе опорного элемента на каждом раунде ее временная сложность может деградировать до <span class="arithmatex">\(O(n^2)\)</span> . Использование медианы трех элементов или случайного опорного элемента уменьшает вероятность этой деградации. Если всегда рекурсивно обрабатывать более короткий поддиапазон первым, можно эффективно уменьшить глубину рекурсии и оптимизировать пространственную сложность до <span class="arithmatex">\(O(\log n)\)</span> .</li>
|
||||
<li>Сортировка слиянием включает этапы разделения и слияния и служит типичным проявлением стратегии "разделяй и властвуй". Для сортировки массива ей требуется вспомогательный массив, поэтому пространственная сложность равна <span class="arithmatex">\(O(n)\)</span> ; однако при сортировке связного списка пространственную сложность можно оптимизировать до <span class="arithmatex">\(O(1)\)</span> .</li>
|
||||
<li>Блочная сортировка включает три этапа: распределение данных по блокам, сортировку внутри блоков и объединение результатов. Она тоже отражает стратегию "разделяй и властвуй" и подходит для очень больших объемов данных. Ключ к эффективности блочной сортировки - равномерное распределение данных.</li>
|
||||
<li>Сортировка подсчетом является частным случаем блочной сортировки; она реализует сортировку через подсчет числа вхождений данных. Сортировка подсчетом подходит для случаев, когда объем данных велик, но диапазон значений ограничен, и при этом данные можно преобразовать в положительные целые числа.</li>
|
||||
<li>Сортировка слиянием включает этапы разделения и слияния и служит типичным проявлением стратегии «разделяй и властвуй». Для сортировки массива ей требуется вспомогательный массив, поэтому пространственная сложность равна <span class="arithmatex">\(O(n)\)</span>. Однако при сортировке связного списка пространственную сложность можно оптимизировать до <span class="arithmatex">\(O(1)\)</span> .</li>
|
||||
<li>Блочная сортировка включает три этапа: распределение данных по блокам, сортировку внутри блоков и объединение результатов. Она тоже отражает стратегию «разделяй и властвуй» и подходит для очень больших объемов данных. Ключ к эффективности блочной сортировки - равномерное распределение данных.</li>
|
||||
<li>Сортировка подсчетом является частным случаем блочной сортировки. Она реализует сортировку через подсчет числа вхождений данных. Сортировка подсчетом подходит для случаев, когда объем данных велик, но диапазон значений ограничен, и при этом данные можно преобразовать в положительные целые числа.</li>
|
||||
<li>Поразрядная сортировка выполняет сортировку данных путем последовательной сортировки по каждому разряду и требует, чтобы данные можно было представить в виде чисел фиксированной разрядности.</li>
|
||||
<li>В общем случае нам хотелось бы найти алгоритм сортировки, который одновременно обладал бы высокой эффективностью, стабильностью, выполнением на месте и адаптивностью. Но, как и в других разделах алгоритмов и структур данных, не существует одного алгоритма сортировки, способного удовлетворить всем этим требованиям одновременно. На практике приходится выбирать подходящий алгоритм в зависимости от свойств данных.</li>
|
||||
<li>На рисунке 11-19 сравниваются эффективность, стабильность, выполнение на месте и адаптивность основных алгоритмов сортировки.</li>
|
||||
@@ -4376,14 +4376,14 @@
|
||||
<p><strong>В</strong>: В каких случаях стабильность алгоритма сортировки является обязательной?</p>
|
||||
<p>В реальных задачах нам может понадобиться сортировать объекты по некоторому атрибуту. Например, у студентов есть два атрибута: имя и рост. Мы хотим выполнить многоуровневую сортировку: сначала отсортировать по имени и получить <code>(A, 180) (B, 185) (C, 170) (D, 170)</code> , а затем отсортировать по росту. Если используемый алгоритм сортировки нестабилен, то мы можем получить <code>(D, 170) (C, 170) (A, 180) (B, 185)</code> .</p>
|
||||
<p>Нетрудно увидеть, что в этом случае студенты D и C поменялись местами, порядок по имени разрушился, а именно этого мы и не хотим.</p>
|
||||
<p><strong>В</strong>: Можно ли поменять местами порядок "поиска справа налево" и "поиска слева направо" в разделении с опорным элементом?</p>
|
||||
<p>Нет. Если в качестве опорного элемента выбирается самый левый элемент, необходимо сначала выполнять "поиск справа налево", а уже затем - "поиск слева направо". Этот вывод кажется немного неочевидным, поэтому разберем его подробнее.</p>
|
||||
<p>Последний шаг <code>partition()</code> - это обмен <code>nums[left]</code> и <code>nums[i]</code> . После обмена все элементы слева от опорного должны быть <code><=</code> опорного, <strong>а значит, перед этим обменом должно выполняться условие <code>nums[left] >= nums[i]</code></strong>. Если сначала выполнять "поиск слева направо", то в случае, когда не удается найти элемент больше опорного, <strong>цикл завершится в состоянии <code>i == j</code> , и при этом может оказаться, что <code>nums[j] == nums[i] > nums[left]</code></strong>. Иными словами, на последнем шаге обмена элемент, больший опорного, будет помещен в начало массива, из-за чего разделение завершится неверно.</p>
|
||||
<p>Например, для массива <code>[0, 0, 0, 0, 1]</code> , если сначала выполнять "поиск слева направо", после разделения получится <code>[1, 0, 0, 0, 0]</code> , а это неправильный результат.</p>
|
||||
<p>Если же выбрать <code>nums[right]</code> в качестве опорного элемента, то ситуация станет противоположной, и тогда сначала нужно выполнять "поиск слева направо".</p>
|
||||
<p><strong>В</strong>: Можно ли поменять местами порядок «поиска справа налево» и «поиска слева направо» в разделении с опорным элементом?</p>
|
||||
<p>Нет. Если в качестве опорного элемента выбирается самый левый элемент, необходимо сначала выполнять «поиск справа налево», а уже затем - «поиск слева направо». Этот вывод кажется немного неочевидным, поэтому разберем его подробнее.</p>
|
||||
<p>Последний шаг <code>partition()</code> - это обмен <code>nums[left]</code> и <code>nums[i]</code> . После обмена все элементы слева от опорного должны быть <code><=</code> опорного, <strong>а значит, перед этим обменом должно выполняться условие <code>nums[left] >= nums[i]</code></strong>. Если сначала выполнять «поиск слева направо», то в случае, когда не удается найти элемент больше опорного, <strong>цикл завершится в состоянии <code>i == j</code> , и при этом может оказаться, что <code>nums[j] == nums[i] > nums[left]</code></strong>. Иными словами, на последнем шаге обмена элемент, больший опорного, будет помещен в начало массива, из-за чего разделение завершится неверно.</p>
|
||||
<p>Например, для массива <code>[0, 0, 0, 0, 1]</code> , если сначала выполнять «поиск слева направо», после разделения получится <code>[1, 0, 0, 0, 0]</code> , а это неправильный результат.</p>
|
||||
<p>Если же выбрать <code>nums[right]</code> в качестве опорного элемента, то ситуация станет противоположной, и тогда сначала нужно выполнять «поиск слева направо».</p>
|
||||
<p><strong>В</strong>: Почему при оптимизации глубины рекурсии в быстрой сортировке выбор короткого массива гарантирует, что глубина рекурсии не превысит <span class="arithmatex">\(\log n\)</span> ?</p>
|
||||
<p>Глубина рекурсии - это число текущих рекурсивных вызовов, которые еще не завершились. На каждом раунде разделения исходный массив разбивается на два подмассива. После оптимизации глубины рекурсии длина подмассива, в который мы продолжаем рекурсивный спуск, не превышает половины длины исходного массива. Если рассматривать худший случай, когда длина каждый раз становится ровно вдвое меньше, итоговая глубина рекурсии и будет равна <span class="arithmatex">\(\log n\)</span> .</p>
|
||||
<p>В исходной версии быстрой сортировки может происходить последовательный рекурсивный вызов для более длинных массивов; в худшем случае это будут длины <span class="arithmatex">\(n\)</span> , <span class="arithmatex">\(n - 1\)</span> , <span class="arithmatex">\(\dots\)</span> , <span class="arithmatex">\(2\)</span> , <span class="arithmatex">\(1\)</span> , а глубина рекурсии окажется равной <span class="arithmatex">\(n\)</span> . Оптимизация глубины рекурсии как раз и позволяет избежать такого сценария.</p>
|
||||
<p>В исходной версии быстрой сортировки может происходить последовательный рекурсивный вызов для более длинных массивов. В худшем случае это будут длины <span class="arithmatex">\(n\)</span> , <span class="arithmatex">\(n - 1\)</span> , <span class="arithmatex">\(\dots\)</span> , <span class="arithmatex">\(2\)</span> , <span class="arithmatex">\(1\)</span> , а глубина рекурсии окажется равной <span class="arithmatex">\(n\)</span> . Оптимизация глубины рекурсии как раз и позволяет избежать такого сценария.</p>
|
||||
<p><strong>В</strong>: Если все элементы массива равны, будет ли временная сложность быстрой сортировки равна <span class="arithmatex">\(O(n^2)\)</span> ? Как справиться с таким вырождением?</p>
|
||||
<p>Да. Для этого случая можно рассмотреть разделение массива на три части: элементы меньше опорного, равные опорному и большие опорного. Рекурсию нужно продолжать только для частей меньше и больше опорного. При таком подходе массив, целиком состоящий из одинаковых элементов, будет отсортирован всего за один раунд разделения.</p>
|
||||
<p><strong>В</strong>: Почему худшая временная сложность блочной сортировки равна <span class="arithmatex">\(O(n^2)\)</span> ?</p>
|
||||
|
||||
Reference in New Issue
Block a user