Как сделать сортировку массива

Обновлено: 07.07.2024

Задача

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

В консоль программа выведет неотсортированный и отсортированный массивы.

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

  1. Находим минимальный элемент в массиве.
  2. Меняем местами минимальный и первый элемент местами.
  3. Ищем минимальный элемент в неотсортированной части массива, т.е., начиная уже со второго элемента массива.
  4. Меняем местами второй элемент массива и найденный минимальный.
  5. Ищем минимальный элемент в массиве, начиная с третьего, меняем местами третий и минимальный элементы.
  6. Продолжаем алгоритм до тех пор пока не дойдем то конца массива.

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

Собираем массив целых чисел

В первой строке мы предлагаем пользователю ввести целые числа. После того, как пользователь введет любое количество чисел и нажмет Enter, программа переходит к следующему шагу — разделит полученную от пользователя строку на массив строк:

Метод Console.ReadLine() возвращает нам последнюю строку из консоли.

Метод Split(new char[] < ',' >) — это метод для типа string , который возвращает массив строк, созданный из исходной строки. В качестве аргумента этот метод принимает массив символов по которым будет делиться строка. Так как у нас, по условиям задачи, разделитель всего один — запятая, то и массив разделителей в методе Split() содержит всего один элемент.

На следующем шаге мы создаем массив целых чисел размер которого совпадает с размером массива nums .

Далее, в цикле for мы наполняем наш созданный массив числами, используя метод Parse() у типа данных int , который возвращает нам целое число из строки.

Выводим неотсортированный массив в консоль

Вывести неотсортированный массив пользователю проще простого, если воспользоваться циклом foreach :

Для этого используем реализацию алгоритма, которая представленная выше. Чтобы вывести отсортированный массив пользователю снова используем цикл foreach .

Результат работы программы

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

Неотсортированный массив: 0 100 999 345 -100 1 0 9 7 6 5 4 3 2 1 67 88

Отсортированный массив: -100 0 0 1 1 2 3 4 5 6 7 9 67 88 100 345 999

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

Исходный код программы

Также, Вы можете загрузить исходный код приложения из нашего репозитория на Github.

Итого

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

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

Сортировка массива по значению

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

Для сортировки значений массива от низкого к высокому вы можете использовать функцию sort(&$array, $sort_flags) . Однако, при сортировке массива он не будет поддерживать никаких ассоциаций ключевых значений. Вместо простой перестановки новые ключи назначаются отсортированным элементам. Необязательный второй параметр указывает порядок сортировки элементов и может иметь шесть разных значений:

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

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

Сортировка ассоциативных массивов

Ассоциации ключевых значений важны, в случае если вы имеете дело с ассоциативными массивами. Рассмотрим следующий пример, где ассоциативный массив используется для хранения имен разных людей и их любимых фруктов. Если вы хотите отсортировать список в алфавитном порядке по названию фруктов, использование функции sort() из предыдущего раздела приведет к потере ассоциативных ключей.

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

Чтобы легко решить эту проблему, в PHP есть две разные функции, поддерживающие взаимосвязь между ассоциациями ключевых значений, сортируя массивы по их значениям. Этими двумя функциями являются asort() и arsort() . Следующий фрагмент кода сортирует тот же массив $fruit_preferences , но использует функцию asort() .

Как видно из приведенного выше примера, значение Apple движется к верху, сохраняя при этом свою связь с Patricia. Названия фруктов можно так же легко сортировать и в обратном порядке, используя функцию arsort() .

Обе эти функции принимают те же значения сортировки, как и значения необязательного второго параметра: sort() и rsort() .

Сортировка элементов массива по значению с помощью пользовательских функций

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

Допустим, у вас есть массив случайных слов, которые нужно отсортировать по алфавиту. Но перед этим вы ещё хотите их отсортировать по длине. Например, в традиционной алфавитной сортировке zoo будет следовать за apple. С другой стороны, если вы хотите показать сначала короткие слова, а потом длинные, то zoo появится перед apple. В том же наборе букв, ape будет перед zoo из-за алфавитного порядка.

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

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

Используйте функцию usort() для сортировки значений массива в обычных массивах. Точно так же можно использовать функцию uasort() для сортировки значений в ассоциативных массивах, сохраняя при этом ассоциаций ключевых значений.

Этот фрагмент кода показывает один из способов выполнения данной сортировки.

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

Поскольку нашим основным критерием сортировки была длина строки, мы напрямую возвращаем -1, если первое слово короче второго. Точно так же мы возвращаем 1, если первое слово длиннее второго. Когда два слова имеют одинаковую длину, мы сравниваем их в алфавитном порядке с помощью функции strcmp() и возвращаем их значение.

Как вы видите, в результате наша пользовательская функция сортировки переставляет слова в точности так, как мы хотели.

Сортировка массива по ключу

Сортировка массива на основе его ключей как всегда полезна, когда вы имеете дело с ассоциативными массивами. Например, у вас может быть массив с информацией общего количества аэропортов в разных странах. Предполагая, что названия разных стран являются ключами, а количество аэропортов - значениями, вы можете отсортировать названия стран по алфавиту. Это очень легко сделать с помощью функций ksort() и krsort() . Функция ksort() сортирует ключи от низкого к высокому, а krsort() сортирует ключи от высокого к низкому.

Вот основной пример сортировки:

Вместо собственной пользовательской функции для сортировки ключей массива вы можете использовать стандартную PHP функцию uksort() . На ряду usort() , функция обратного вызова в uksort() также требует возврата целого числа меньше 0, если первый ключ считается меньше второго, и целого числа больше 0, если первый ключ больше второго. Эта функция также поддерживает ассоциации ключевых значений для элементов массива.

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

Сортировка многомерных массивов в PHP

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

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

Если вы хотите отсортировать значения в многомерном массиве на основе определенного поля, вы можете просто использовать функцию usort() . Приведенный ниже пример поможет вам лучше понять эту технику.

В приведенном выше примере информация о каждом здании хранится в своем собственном массиве внутри основного массива $tallest_buildings . Функция storey_sort() просто вычитает количество этажей во втором здании, начиная с первого, чтобы определить, какое по нашим критериям здание меньше. Не следует беспокоиться о возврате определенного отрицательного или положительного значения, потому что все отрицательные значения означают меньшее, а все положительные значения означают большее.

В конце мы просто перебираем основной массив и выводим информацию о каждом здании.

Послесловие

В этом уроке я показал вам несколько разных функций PHP, которые используются для сортировки массивов либо по их ключам, либо по их значениям. Мы также узнали, как сортировать массив только по его ключам или только по значениям, используя наши собственные критерии сортировки с помощью функций uksrot() и uasort() . В последнем разделе обсуждается, как отсортировать все значения в многомерном массиве, используя только определенное поле.

Я надеюсь, что в этом уроке вы узнали что-то новое. Если у вас есть какие-либо вопросы или предложения, пожалуйста, сообщите в комментариях. Лучший способ научиться - это попробовать создать собственные примеры сортировки массивов на основе вышеуказанных функций.

Сортировка массива по ключу

ksort() и krsort() – сортирует массив по ключу.

Результат:

Сортировка массива по значению

Функции sort() и rsort() сортирует массив по значению, при этом не сохраняя ключи.

Результат:

Чтобы сохранить ключи применяется функции asort() и arsort() .

Результат:

Естественная сортировка

Выше приведенные функции по умолчанию используют алгоритм Quicksort (быстрая сортировка). Чтобы изменить алгоритм нужно вторым аргументом указать флаг:

SORT_REGULAR Обычное сравнение элементов (без изменения типов)
SORT_NUMERIC Числовое сравнение элементов
SORT_STRING Строковое сравнение элементов
SORT_LOCALE_STRING Сравнивает элементы как строки с учетом текущей локали.
SORT_NATURAL Также как natsort()
SORT_FLAG_CASE Может быть объединен с SORT_STRING или SORT_NATURAL для сортировки строк без учета регистра.

Привычную для человека сортировку делают функции natsort() , natcasesort() или флаг SORT_NATURAL .
natcasesort() сортирует массив без учета регистра символов.

Разницу можно увидеть в примере:

Результат:

У natsort() нет возможности изменить направление сортировки, поэтому можно применить функцию array_reverse() .

Сортировка - одна из самых распространённых подзадач, часто встречающаяся в задачах всех уровней сложности. Существует множество алгоритмов, решающих её - алгоритмов сортировки. В стандартных библиотеках большинства современных языков программирования уже содержится эффективный алгоритм сортировки, и при решении задач вам вряд ли придётся реализовывать свой самостоятельно. Несмотря на это, полезно знать принцип работы и сложность различных алгоритмов, и уметь реализовывать хотя бы один эффективный алгоритм “на всякий случай”.

Постановка задачи

Простым примером сортировки является упорядочивание набора чисел по убыванию или возрастанию. Точное определение сортировки более обобщено: сортировка упорядочивает заданный набор объектов согласно заданному отношению между ними. Например, объектами могут быть числа, строки, точки в пространстве, а отношением - операция сравнения целых объектов или их отдельных частей (например точки на плоскости можно сортировать по любой из их координат).

Сортировка вставками (Сложность: \(O(N^2)\))

Один из самых простых алгоритмов сортировки - сортировка вставками (англ. insertion sort). Его идея заключается в поддерживании в левой части массива отсортированного отрезка и постепенном его расширении вправо за счёт перемещения очередного соседнего элемента в соответствующую ему позицию.

В качестве иллюстрации можно посмотреть видео:

Легко заметить, что сложность этого алгоритма - \(O(N^2)\): в худшем случае (массив упорядочен наоборот) потребуется \(i\) замен для перемещения элемента с индексом \(i\) на своё место (крайнее левое). Для всего алгоритма потребуется \(\sum_^ i \in O(N^2)\) замен.

Реализация алгоритма достаточно прямолинейна:

Сортировка слиянием (Сложность: \(O(N \log N)\))

Можно доказать, что не существует алгоритма сортировки, основанного на сравнениях, сложность которого меньше \(O(N \log N)\). Поэтому каждый алгоритм сравнения с такой сложностью можно назвать “оптимальным”.

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

  • Пирамидальная сортировка (англ. heapsort)
  • Сортировка слиянием (англ. merge sort)
  • Быстрая сортировка (англ. quicksort)

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

Идея сортировки слиянием заключается в следующем: допустим мы знаем, что массив можно разбить на две примерно равные части, и каждая из них уже будет отсортирована. Нам нужно лишь “слить” эти две части, и мы получим отсортированный массив.

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

Осталось лишь понять, каким образом мы можем получить массив, две части которого уже отсортированы, ведь массив на входе алгоритма - произвольный. Сортировка слиянием - классический пример рекурсивного алгоритма: он использует самого себя для сортировки частей массива. Это может быть не совсем интуитивно, но важно заметить простой факт: если на входе алгоритма массив длиной 1, то никаких операций выполнять не нужно. Вызывая самого себя рекурсивно, алгоритм всегда будет “упираться” в этот крайний случай, и поэтому он не будет работать бесконечно.

Для того, чтобы понять общую идею рекурсивных алгоритмов, лучше всего внимательно проанализировать код, приведённый ниже, или очередное иллюстративное видео:

Чтобы показать, что сложность алгоритма действительно равна \(O(N \log N)\), разобьём все “слияния”, выполненные в процессе сортировки массива на “слои”. Слияние двух частей изначального массива - первый слой, слияния частей каждой из этих двух частей (четвертей оригинального массива) - второй слой, и т.д. Заметим несколько фактов:

  1. Слияние двух частей отрезка, длина которого равна \(K\), выполняется за \(O(K)\)
  2. Сумма длин всех отрезков сортируемых слиянием на каждом слое равна \(N\)
  3. Количество слоёв равно \(\lceil \log_2 N \rceil\)

Из фактов 1 и 2 следует что на каждом слое все слияния выполняются за \(O(N)\). Используя факт 3 видим, что сложность всего алгоритма равна \(O(N \log N)\).

И наконец, реализация алгоритма:

Стандартные алгоритмы сортировки

Стандартная библиотека любого современного языка программирования включает в себя реализацию эффективного алгоритма сортировки. Чаще всего вместо одного канонического алгоритма используется адаптивная комбинация нескольких алгоритмов: например, одна из самых распространённых реализация стандартной библиотеки C++ (libstdc++) использует комбинацию heapsort, merge sort, и insertion sort.

Для сортировки контейнеров, предоставляющих итераторы random-access ( std::vector , std::deque , и т.д.) чаще всего используются методы begin и end :

Важно помнить, что как и все алгоритмы стандартной библиотеки, std::sort принимает, что итераторы обозначают начало включительно, а конец - не включительно.

Для сортировки “старых” массивов используется автоматическое преобразование указателя в итератор:

Сортировка сложных типов. Собственные компараторы

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

Компаратор в C++ - логическая функция, которая принимает два объекта одинакового типа, и возвращает true , если первый из них “меньше” второго и false в противном случае. В этом контексте “меньше” значит, что в отсортированном массиве этот элемент обязательно должен стоять раньше.

Для использования собственного компаратора нам потребуется перегрузка функции std::sort , которая принимает дополнительный третий параметр - компаратор, по которому будет вестить сортировка.

Приведём простой пример. Давайте сортировать результаты команд в соревновании по правилам ACM в порядке мест. То есть, место команды выше места другой команды, если она решила больше задач, или у неё меньше штраф.

Эта программа выпишет

Важно понимать, что любой компаратор \(\prec\) по сути является математическим объектом, и должен обладать несколькими важными свойствами, большинство из которых тривиальны, кроме одного: транзитивности. Компаратор является транзитивным, если из \(a \prec b\) и \(b \prec c\) следует, что \(a \prec c\). Это может казаться очевидным, но можно легко определить функцию, которая не транзитивна, и попытаться использовать её как компаратор. Математически это не имеет смысла, практически поведение std::sort в такой ситуации не определено и может стать причиной неожиданных и странных ошибок.

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

Стоит также отдельно упомянуть стандартный тип std::pair . Для него так же как и для чисел определён оператор следующим образом: если первые элементы пар отличаются, то сравниваются они, иначе сравниваются вторые элементы. Другими словами, сортировка пар без компаратора происходит по возрастанию первого элемента, а при равенстве первых - по возрастанию второго.

Понятие стабильной сортировки

Иногда от сортировки нам требуется определённое свойство: она не должна менять местами “неотличимые” элементы. Это может быть важно при сортировке сложных объектов собственными компараторами, например, в предыдущем разделе при сравнении объектов team не учитывалось поле name : две команды с одинаковым результатом считались “равными” даже если их названия не совпадали.

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

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