STL __merge_without_buffer алгоритм?

Где я могу получить достойное высокоуровневое описание алгоритма, используемого в __merge_without_buffer() в C++ STL? Я пытаюсь переопределить этот код на языке программирования D с некоторыми улучшениями. Я не могу понять, что он делает на алгоритмическом уровне, просто прочитав исходный код STL, потому что слишком много низкоуровневых деталей скрывают его. Кроме того, я не хочу просто слепо переводить код, потому что тогда, если бы это не сработало, я бы не знал, почему, и я не смог бы добавить свои улучшения.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
576
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

__merge_without_buffer() выполняет слияние на месте как этап слияния сортировки слиянием на месте. Он принимает в качестве входных данных два диапазона данных [first, middle) и [middle, last), которые, как предполагается, уже отсортированы. Параметры len1 и len2 равны длинам двух входных диапазонов, а именно (middle - first) и (last - middle) соответственно.

Сначала он выбирает элемент вращаться. Затем он перестраивает данные в A1 B1 A2 B2 порядке, где A1 есть множество элементов в [first, middle), которые меньше, чем стержень, A2 представляет собой набор элементов в [first, middle) больше или равно к оси поворота, B1 есть множество элементов в [middle, last) меньше точки поворота, а B2 - это набор элементов в [middle, last), больший или равный оси поворота. Обратите внимание, что данные изначально находятся в порядке A1 A2 B1 B2, поэтому все, что нам нужно сделать, это превратить A2 B1 в B1 A2. Это при вызове std::rotate(), который делает именно это.

Теперь мы отделили элементы, которые меньше точки поворота (A1 и B1), от тех, которые больше или равны оси поворота (A2 и B2), поэтому теперь мы можем рекурсивно объединить два поддиапазона A1 A2 и B1 B2.

Как выбрать точку опоры? В рассматриваемой мной реализации он выбирает медианный элемент из большего поддиапазона (т.е. если [first, middle) имеет больше элементов, чем [middle, last), он выбирает медианное значение [first, middle); в противном случае он выбирает медианное значение [middle, last)). Поскольку поддиапазоны уже отсортированы, выбор медианы тривиален. Этот выбор поворота гарантирует, что при рекурсивном объединении двух поддиапазонов, каждая подзадача является не более чем 3/4 от размера текущей проблемы, потому что в худшем случае, по крайней мере 1/4 элементов больше или меньше, чем стержень .

Какое время работы у этого? Вызов std::rotate() занимает O (N) времени, и мы делаем два рекурсивных вызова себе. Это соответствует времени работы O (N log N). Однако обратите внимание, что это только один шаг в сортировке слиянием: помните, что в сортировке слиянием вы сначала рекурсивно сортируете обе половины, а затем объединяете. Таким образом, рекуррентное соотношение для времени выполнения сортировки слиянием теперь выглядит следующим образом:

T(N) = 2T(N/2) + O(N log N)

Подключите это к Основная теорема, и вы получите, что сортировка слиянием теперь выполняется за время O (N log2 N)!

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

  1. На месте
  2. Стабильный
  3. Работает за O (N log N) раз

Обычно вы можете получить только 2 из них одновременно - быстрая сортировка дает вам (1) и (3), слияние дает вам (2) и (3), а сортировка слияния на месте дает вам (1) и (2). Сортировки, не основанные на сравнении, такие как сортировка по счетчику, могут обеспечить все 3, но эти сортировки могут сортировать только определенные типы данных. Возможно, существует сортировка на основе сравнения, которая выполняет все три, но если есть, я не знаю о ее существовании, и это почти наверняка намного сложнее.

Означает ли O (N log2 N) N (log N) ^ 2? Я был сбит с толку, потому что думал, что log2 означает log_2, но база здесь не имеет значения. Кроме того, является ли последняя часть «вы можете получить только 2 из них одновременно» строго верной, или просто алгоритмы, которые получают все три, являются сложными? Думаю, второе?

A. Rex 13.01.2009 08:42

Я отредактировал, чтобы ответить на ваши вопросы. Я испортил надстрочный тег HTML. log ^ 2 N означает (log N) ^ 2. Хотя подразумевается основание 2, фактическое основание не имеет значения, поскольку это постоянный коэффициент, который съедается нотацией большого O.

Adam Rosenfield 13.01.2009 08:51

В пункте 3, я думаю, вы имеете в виду, что поддиапазоны A1 и B1 рекурсивно объединяются, а поддиапазоны A2 и B2 рекурсивно объединяются, верно? После вращения поддиапазона A1 A2 больше нет.

Rob Kennedy 13.01.2009 08:56

Спасибо, но теперь, когда я знаю, что это O (N log N), а не O (N), я даже не уверен, что хочу больше беспокоиться о его реализации, хотя я думаю, что это действительно крутой алгоритм, поэтому я мог бы просто посмотреть, насколько медленно это на практике.

dsimcha 13.01.2009 17:51

Обратите внимание, что он вызывается только тогда, когда у вас мало памяти. Когда вызывающие абоненты могут выделить временный буфер, они не вызывают версию на месте. Вместо этого они называют O (n) «адаптивной» версией. Когда он находится между «медленным» и «неудачным», выбирайте первое.

Rob Kennedy 13.01.2009 18:05

Стандартная быстрая сортировка не на месте, она требует непостоянного дополнительного стека. Существует модифицированная быстрая сортировка, которая работает в постоянной дополнительной памяти (сохраняя свой стек в массиве с помощью хитрых приемов), но она довольно неэффективна, хотя по-прежнему O (n * log (n)).

Rafał Dowgird 13.01.2009 19:52

Верно, но для большинства случаев использования в реальном мире стек O (log N) можно считать "почти" установленным. Он не создает активности кучи и имеет минимальный риск переполнения стека.

dsimcha 13.01.2009 21:35

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

dsimcha 14.01.2009 07:53

Между прочим, я отправил сообщение Адаму Розенфилду за пределы сайта и подумал, что некоторые из вас хотели бы знать. С точки зрения теории, действительно существуют алгоритмы, обладающие «всеми тремя хорошими качествами». См. portal.acm.org/citation.cfm?id=892042 и springerlink.com/content/bx6d9xv8p6w3d3p4.

A. Rex 14.01.2009 23:00

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

dsimcha 15.01.2009 03:45

Другие вопросы по теме