Как сделать сортировку стабильной

Добавил пользователь Евгений Кузнецов
Обновлено: 04.10.2024

В предыдущей части мы познакомились с тем, что из себя представляет универсальный список List , научились создавать такие списки и выводить значения различных элементов списка в консоль. Сегодня разберемся с тем, как сортировать списки List , содержащие различные типы элементов.

Как мы уже знаем, в классе List предусмотрен метод Sort() , который производит сортировку элементов списка с использованием компаратора по умолчанию. Что это означает рассмотрим на примере.

Пример использования метода Sort() для простых типов данных

Создадим список строк и попробуем его отсортировать:

Результат выполнения программы будет следующим:

То есть мы получили ровно то, что и ожидали — все строки с именами отсортировались по алфавиту. Аналогичным образом, метод Sort() сработает и на числах. Однако, если мы попробуем выполнить вот такой код:

то гарантированно получим исключение следующего содержания:

Пример использования метода Sort() с компаратором по умолчанию для объектов

Попробуем переписать наш предыдущий пример следующим образом:

Здесь наш класс Person реализует интерфейс IComparable и, соответственно, содержит компаратор по умолчанию — метод CompareTo , который возвращает целочисленное значение: -1, 0 или 1. Смысл работы этого метода точно такой же, как и метода Compare у строк. Теперь, когда мы реализовали в классе компаратор по умолчанию, метод Sort у списка будет работать без вызова исключений, а результат будет такой же, как и при сортировке строк.

В методе CompareTo можно реализовывать любую логику сравнения двух объектов — сравнивать объекты не по одному, а по нескольким полям, делать сортировку по возрастанию или убыванию и т.д.

Пример использования перегруженного метода Sort()

Несмотря на то, что реализация интерфейса IComparable классом позволяет создавать любую логику сравнения элементов списка, иногда бывает необходимым производить сортировку иным образом, чем это реализовано в компараторе по умолчанию. И тогда нам на помощь может прийти перегруженный метод Sort() , который в качестве параметра принимает метод реализующий функцию сравнения двух элементов списка. Например, перепишем нашу программы таким образом, чтобы у класса Person появилось поле возраста человека и отсортируем список по этому полю:

При сортировке списка по возрасту мы передали в метод Sort() анонимный метод в котором и произвели сортировку элементов списка по необходимому нам полю, не переписывая при этом компаратор по умолчанию, реализованный в самом классе Person . Результат работы программы будет следующий:

Несортированный список
Вася
Петя
Ваня
Гоша
Таня
Список отсортированный по имени
Ваня возраст 6
Вася возраст 10
Гоша возраст 18
Петя возраст 3
Таня возраст 7
Список отсортированный по возрасту
Петя возраст 3
Ваня возраст 6
Таня возраст 7
Вася возраст 10
Гоша возраст 18

Итого

Сегодня мы научились производить сортировку списков, содержащих различные типы элементов. Реализовали интерфейс IComparable в своем классе и создали компаратор по умолчанию, а также отсортировали список List , используя анонимный метод в качестве параметра метода Sort .

Сортировка сортировки может быть реализована как устойчивая сортировка. Если вместо замены на шаге 2 минимальное значение вставляется в первую позицию (то есть все перемещаемые элементы перемещаются вниз), алгоритм является стабильным. Однако для этой модификации требуется структура данных, которая поддерживает эффективные вставки или удаления, такие как связанный список, или приводит к выполнению записи Θ (n2).

Таким образом, в основном у вас есть стабильная сортировка, так как вы всегда меняете значение, которое меньше минимального значения (в отличие от менее или равно EQUAL к значению).

Ваша реализация нестабильна. На каждой итерации вы берете элемент в позиции i в массиве и помещаете его в произвольное положение в оставшемся массиве, поэтому вы испортите первоначальный порядок.

Если вам нужен стабильный вид, вам необходимо:

    Выберите минимальный элемент для оставшегося списка, как вы уже Вставьте его в место i без замены. Это означает перемещение остальных элементов вправо, чтобы сделать пространство для i го элемента, который вы размещаете.

Это гарантирует, что элементы с одинаковым значением помещаются в том же порядке в отсортированном массиве и в исходном массиве.

Сортировка и фильтрация данных в Excel

Простая и пользовательская сортировка, как настроить и убрать фильтр, “умные таблицы”

Таблицы с огромным количеством числовой и текстовой информации часто встречаются во внутренних документах крупных и мелких компаний. Среди множества строк легко потерять важную информацию из виду. Разработчики из компании Microsoft понимают это, поэтому в программе Microsoft Excel присутствуют опции сортировки и фильтрации данных. Разобраться в них без подсказок достаточно сложно. Попробуем понять, как правильно настраивать сортировку и фильтры в обычных и сводных таблицах.

Обычная (простая) сортировка

Эта опция называется простой, потому что ее несложно использовать даже новичкам. В результате сортировки информация автоматически организуется в установленном порядке. Например, можно составить строки таблицы по алфавиту или упорядочить числовые данные от мелких к крупным.

Пользовательская сортировка данных

Иногда необходимо построить строки таблицы в определенном порядке, который установлен условиями задачи. Например, возникает нужда в сортировке по нескольким параметрам, а не только по одному. В таком случае стоит обратить внимание на настраиваемую сортировку в Excel.

Дополнительная информация! В случае, если таблица начинается с шапки, нужно поставить галочку в графе, расположенной в правом верхнем углу экрана.


Настройки можно изменить, выбрав одну из ячеек рассортированного диапазона и открыв окно пользовательской/настраиваемой сортировки.

Как настроить фильтр в таблице

Фильтры в Excel позволяют временно скрыть часть информации с листа и оставить только самое необходимое. Информация не пропадает навсегда – изменение настроек вернет ее на лист. Разберемся, как фильтровать строки электронной таблицы.

Как убрать фильтр в таблице

Не обязательно расставлять галочки обратно в меню, чтобы восстановить прежний вид таблицы. Воспользуемся инструментами Microsoft Excel, чтобы отменить результаты фильтрации – есть два способа сделать это.

Как сделать фильтр в Excel по столбцам

Таблицы Excel фильтруются только по строкам. В меню, которое появляется после нажатия на кнопку со стрелкой в шапке столбца, нельзя убрать все галочки, то есть нельзя скрыть целый столбец. Вся информация в диапазоне ячеек важна при фильтрации и сортировке, поэтому отфильтровать один или несколько столбцов не получится.

Сортировка по нескольким столбцам в Excel

Когда говорят о сортировке по нескольким столбцам, подразумевается, что для упорядочивания таблицы применяют усложненные настройки с указанием двух или более столбцов.

Важно! Количество уровней для сортировки ограничено только количеством столбцов или строк в таблице.

Автофильтр

Автоматическая фильтрация строк таблицы возможна с помощью меню фильтров. Эта функция позволяет установить более сложные настройки и создать уникальный фильтр. Набор автоматических фильтров меняется в зависимости от формата ячеек. Применяются текстовые и числовые фильтры.

Стоит обратить внимание на пункты И/ИЛИ в окне настройки автофильтра. От них зависит то, как будут применены настройки – вместе или частично.

Срезы

Программа Microsoft Excel позволяет прикрепить к таблицам интерактивные элементы для сортировки и фильтрации – срезы. После выхода версии 2013-о года появилась возможность подключать срезы к обычным таблицам, а не только к сводным отчетам. Разберемся, как создать и настроить эти опции.

Создание срезов

Обратите внимание! Если ваша версия Microsoft Excel старше 2013-го года, составить срез для обычной таблицы будет невозможно. Функция применима только к отчетам в формате сводных таблиц.

Срезы выглядят, как диалоговые окна со списками кнопок. Названия пунктов зависят от того, какие элементы таблицы были выбраны при создании среза. Чтобы отфильтровать данные, нужно кликнуть по кнопке в одном из списков. Фильтрация по нескольким диапазонам данных возможна, если нажать кнопки в нескольких срезах.

Форматирование срезов

Редактирование внешнего вида срезов и их взаимодействия с другими элементами возможно с помощью специальных инструментов. Попробуем изменить цветовую схему.

Цифровая сортировка (англ. radix sort) — один из алгоритмов сортировки, использующих внутреннюю структуру сортируемых объектов.

Содержание



Имеем множество последовательностей одинаковой длины, состоящих из элементов, на которых задано отношение линейного порядка. Требуется отсортировать эти последовательности в лексикографическом порядке.

По аналогии с разрядами чисел будем называть элементы, из которых состоят сортируемые объекты, разрядами. Сам алгоритм состоит в последовательной сортировке объектов какой-либо устойчивой сортировкой по каждому разряду, в порядке от младшего разряда к старшему, после чего последовательности будут расположены в требуемом порядке.

Примерами объектов, которые удобно разбивать на разряды и сортировать по ним, являются числа и строки.

  • Для чисел уже существует понятие разряда, поэтому будем представлять числа как последовательности разрядов. Конечно, в разных системах счисления разряды одного и того же числа отличаются, поэтому перед сортировкой представим числа в удобной для нас системе счисления.
  • Строки представляют из себя последовательности символов, поэтому в качестве разрядов в данном случае выступают отдельные символы, сравнение которых обычно происходит по соответствующим им кодам из таблицы кодировок. Для такого разбиения самый младший разряд — последний символ строки.

Для вышеперечисленных объектов наиболее часто в качестве устойчивой сортировки применяют сортировку подсчетом.

Такой подход к алгоритму называют LSD-сортировкой (Least Significant Digit radix sort). Существует модификация алгоритма цифровой сортировки, анализирующая значения разрядов, начиная слева, с наиболее значащих разрядов. Данный алгоритм известен, как MSD-сортировка (Most Significant Digit radix sort).

Докажем, что данный алгоритм работает верно, используя метод математической индукции по номеру разряда. Пусть [math] n [/math] — количество разрядов в сортируемых объектах.

База: [math] n = 1 [/math] . Очевидно, что алгоритм работает верно, потому что в таком случае мы просто сортируем младшие разряды какой-то заранее выбранной устойчивой сортировкой.

Переход: Пусть для [math] n = k [/math] алгоритм правильно отсортировал последовательности по [math] k [/math] младшим разрядам. Покажем, что в таком случае, при сортировке по [math] (k + 1) [/math] -му разряду, последовательности также будут отсортированы в правильном порядке.

Вспомогательная сортировка разобьет все объекты на группы, в которых [math] (k + 1) [/math] -й разряд объектов одинаковый. Рассмотрим такие группы. Для сортировки по отдельным разрядам мы используем устойчивую сортировку, следовательно порядок объектов с одинаковым [math] (k + 1) [/math] -м разрядом не изменился. Но по предположению индукции по предыдущим [math] k [/math] разрядам последовательности были отсортированы правильно, и поэтому в каждой такой группе они будут отсортированы верно. Также верно, что сами группы находятся в правильном относительно друг друга порядке, а, следовательно, и все объекты отсортированы правильно по [math] (k + 1) [/math] -м младшим разрядам.

В качестве примера рассмотрим сортировку чисел. Как говорилось выше, в такой ситуации в качестве устойчивой сортировки применяют сортировку подсчетом, так как обычно количество различных значений разрядов не превосходит количества сортируемых элементов. Ниже приведен псевдокод цифровой сортировки, которой подается массив [math] A [/math] размера [math] n [/math] [math] m [/math] -разрядных чисел . Сам по себе алгоритм представляет собой цикл по номеру разряда, на каждой итерации которого элементы массива [math] A [/math] размещаются в нужном порядке во вспомогательном массиве [math] B [/math] . Для подсчета количества объектов, [math] i [/math] -й разряд которых одинаковый, а затем и для определения положения объектов в массиве [math] B [/math] используется вспомогательный массив [math] C [/math] . Функция [math] \mathrm [/math] возвращает [math] i [/math] -й разряд числа [math] x [/math] . Также считаем, что значения разрядов меньше [math] k [/math] .

Будем считать, что у всех элементов одинаковое число разрядов. Если это не так, то положим на более старших разрядах элементы с самым маленьким значением — для чисел это [math]0[/math] . Сначала исходный массив делится на [math]k[/math] частей, где [math]k[/math] — основание, выбранное для представления сортируемых объектов. Эти части принято называть "корзинами" или "карманами". В первую корзину попадают элементы, у которых старший разряд с номером [math]d = 0[/math] имеет значение [math]0[/math] . Во вторую корзину попадают элементы, у которых старший разряд с номером [math]d = 0[/math] имеет значение [math]1[/math] и так далее. Затем элементы, попавшие в разные корзины, подвергаются рекурсивному разделению по следующему разряду с номером [math]d = 1[/math] . Рекурсивный процесс разделения продолжается, пока не будут перебраны все разряды сортируемых объектов и пока размер корзины больше единицы. То есть останавливаемся когда [math]d \gt m[/math] или [math]l \geqslant r[/math] , где m — максимальное число разрядов в сортируемых объектах, [math]l[/math] , [math]r[/math] — левая и правая границы отрезка массива [math]A[/math] .

В основу распределения элементов по корзинам положен метод распределяющего подсчета элементов с одинаковыми значениями в сортируемом разряде. Для этого выполняется просмотр массива и подсчет количества элементов с различными значениями в сортируемом разряде. Эти счетчики фиксируются во вспомогательном массиве счетчиков [math]cnt[/math] . Затем счетчики используются для вычисления размеров корзин и определения границ разделения массива. В соответствии с этими границами сортируемые объекты переносятся во вспомогательный массив [math]c[/math] , в котором размещены корзины. После того как корзины сформированы, содержимое вспомогательного массива [math]c[/math] переносится обратно в исходный массив [math]A[/math] и выполняется рекурсивное разделение новых частей по следующему разряду в пределах границ корзин, полученных на предыдущем шаге.

Изначально запускаем функцию так [math]\mathrm[/math]

Пусть [math] m [/math] — количество разрядов, [math] n [/math] — количество объектов, которые нужно отсортировать, [math] T(n) [/math] — время работы устойчивой сортировки. Цифровая сортировка выполняет [math] k [/math] итераций, на каждой из которой выполняется устойчивая сортировка и не более [math] O(1) [/math] других операций. Следовательно время работы цифровой сортировки — [math] O(k T(n)) [/math] .

Рассмотрим отдельно случай сортировки чисел. Пусть в качестве аргумента сортировке передается массив, в котором содержатся [math] n [/math] [math] m [/math] -значных чисел, и каждая цифра может принимать значения от [math] 0 [/math] до [math] k - 1 [/math] . Тогда цифровая сортировка позволяет отсортировать данный массив за время [math] O(m (n + k)) [/math] , если устойчивая сортировка имеет время работы [math] O(n + k) [/math] . Если [math] k [/math] небольшое, то оптимально выбирать в качестве устойчивой сортировки сортировку подсчетом.

Если количество разрядов — константа, а [math] k = O(n) [/math] , то сложность цифровой сортировки составляет [math] O(n) [/math] , то есть она линейно зависит от количества сортируемых чисел.

Пусть значения разрядов меньше [math]b[/math] , а количество разрядов — [math]k[/math] . При сортировке массива из одинаковых элементов MSD-сортировкой на каждом шаге все элементы будут находится в неубывающей по размеру корзине, а так как цикл идет по всем элементам массива, то получим, что время работы MSD-сортировки оценивается величиной [math]O(nk)[/math] , причем это время нельзя улучшить. Хорошим случаем для данной сортировки будет массив, при котором на каждом шаге каждая корзина будет делиться на [math]b[/math] частей. Как только размер корзины станет равен [math]1[/math] , сортировка перестанет рекурсивно запускаться в этой корзине. Таким образом, асимптотика будет [math]\Omega(n\log_b)[/math] . Это хорошо тем, что не зависит от числа разрядов.

Существует также модификация MSD-сортировки, при которой рекурсивный процесс останавливается при небольших размерах текущего кармана, и вызывается более быстрая сортировка, основанная на сравнениях (например, сортировка вставками).

Читайте также: