Допустим, у вас есть самолет, и у него мало топлива. Если самолет не сбросит 3000 фунтов веса пассажира, он не сможет добраться до следующего аэропорта. Чтобы сохранить максимальное количество жизней, мы хотели бы сначала сбросить с самолета самых тяжелых людей.
И о, да, в самолете миллионы людей, и мы хотели бы найти оптимальный алгоритм поиска самых тяжелых пассажиров без необходимости сортировки всего списка.
Это проблема прокси для чего-то, что я пытаюсь кодировать на C ++. Я хотел бы сделать «part_sort» для пассажирского манифеста по весу, но я не знаю, сколько элементов мне понадобится. Я мог бы реализовать свой собственный алгоритм "component_sort" ("partal_sort_accumulate_until "), но мне интересно, есть ли какой-нибудь более простой способ сделать это, используя стандартный STL.
Ответы:
Один из способов - использовать минимальную кучу (
std::priority_queue
в C ++). Вот как ты это сделаешь, если у тебя естьMinHeap
класс. (Да, мой пример на C #. Я думаю, вы поняли идею.)Согласно стандартным ссылкам, время выполнения должно быть пропорционально тому
n log k
, гдеn
находится количество пассажиров иk
максимальное количество элементов в куче. Если предположить, что вес пассажиров обычно составляет 100 фунтов или более, то маловероятно, что куча будет содержать более 30 предметов в любой момент.В худшем случае пассажиры будут представлены в порядке от самого низкого веса до самого высокого. Для этого потребуется, чтобы каждый пассажир был добавлен в кучу, а каждый пассажир был удален из кучи. Тем не менее, имея миллион пассажиров и предполагая, что самый легкий весит 100 фунтов,
n log k
получается достаточно мало.Если вы получаете веса пассажиров случайным образом, производительность намного лучше. Я использую что-то вроде этого для механизма рекомендаций (я выбираю лучшие 200 пунктов из списка из нескольких миллионов). Я обычно получаю только 50 000 или 70 000 предметов, добавленных в кучу.
Я подозреваю, что вы увидите нечто очень похожее: большинство ваших кандидатов будут отклонены, потому что они легче, чем самый легкий человек, уже находящийся в куче. И
Peek
этоO(1)
операция.Для получения дополнительной информации о производительности динамического выбора и быстрого выбора см. Раздел «Когда теория встречается с практикой» . Краткая версия: если вы выбираете менее 1% от общего количества предметов, то выбор кучи - явный победитель быстрого выбора. Более 1%, затем используйте быстрый выбор или вариант типа Introselect .
источник
std::priority_queue
Это не поможет для вашей проблемы с прокси:
Чтобы 1 000 000 пассажиров сбросили 3000 фунтов веса, каждый пассажир должен потерять (3000/1000000) = 0,003 фунта на человека. Этого можно достичь, сбросив каждую рубашку, обувь или, возможно, даже вырезанные ногти, чтобы спасти всех. Это предполагает эффективный сбор и сброс до того, как потеря веса увеличилась, поскольку самолет использовал больше топлива.
На самом деле, они больше не допускают кусачки для ногтей на борту, так что это не так.
источник
Ниже приведена довольно простая реализация простого решения. Я не думаю, что есть более быстрый способ, который на 100% правильный.
Это работает, заполняя набор «мертвых людей», пока не достигнет порога. Как только порог достигнут, мы продолжаем просматривать список пассажиров, пытаясь найти тех, кто тяжелее самого легкого мертвого человека. Найдя его, мы добавляем его в список, а затем начинаем «Сохранять» самых легких людей из списка, пока мы не можем больше сохранять.
В худшем случае это будет работать примерно так же, как и весь список. Но в лучшем случае («мертвый список» правильно заполняется первыми X людьми) он будет работать
O(n)
.источник
total
рядом с «continue;
Кроме этого», это ответ, который я собирался опубликовать. Супер быстрое решениеПредполагая, что все пассажиры будут сотрудничать: Используйте сеть параллельной сортировки . (см. также это )
Вот живая демонстрацияОбновление: альтернативное видео (переход к 1:00)
Запрашивать пары людей для сравнения-обмена - вы не можете получить быстрее, чем это.
источник
n
процессоры, не соответствует действительности.@Blastfurnace был на правильном пути. Вы используете быстрый выбор, где стержни являются порогами веса. Каждый раздел разбивает один набор людей на наборы и возвращает общий вес для каждого набора людей. Вы продолжаете разбивать соответствующее ведро до тех пор, пока ваши ведра, соответствующие людям с наибольшим весом, не превысят 3000 фунтов, а ваше самое низкое ведро, которое находится в этом наборе, имеет 1 человека (то есть его нельзя разделить дальше)
Этот алгоритм амортизируется по линейному времени, но наихудший случай - квадратичный. Я думаю, что это единственный линейный алгоритм времени .
Вот решение Python, которое иллюстрирует этот алгоритм:
Вывод:
источник
Предполагая, что, подобно весам людей, у вас есть хорошее представление о том, какие максимальные и минимальные значения могут использоваться для сортировки по осям, чтобы отсортировать их по O (n). Тогда просто работайте от самого тяжелого конца списка к самому легкому. Общее время работы: O (n). К сожалению, в STL нет реализации радикальной сортировки, но написать ее довольно просто.
источник
Почему бы вам не использовать частичную быструю сортировку с правилом прерывания, отличным от «отсортированного». Вы можете запустить его, а затем использовать только верхнюю половину и продолжать до тех пор, пока вес в этой верхней половине не будет содержать вес, который по крайней мере должен быть отброшен больше, чем вы вернетесь на один шаг в рекурсии и отсортируете список. После этого вы можете начать выбрасывать людей из верхней части этого отсортированного списка.
источник
Массивно Параллельный Турнир Сортировать: -
Предполагая три стандартных места на каждой стороне эйлса:
Попросите пассажиров на сиденье у окна переместиться на среднее сиденье, если они тяжелее человека на сиденье у окна.
Попросите пассажиров на среднем сиденье поменяться местами с пассажиром, если они тяжелее.
Попросите пассажира на левом месте прохода поменяться с пассажиром на правом месте, если он тяжелее.
Пузырьки рассортируют пассажиров в правом месте у прохода. (Делает n шагов для n строк). - попросите пассажиров в правом месте у прохода поменяться с лицом вперед n -1 раз.
5 Выбивайте их из двери, пока не наберете 3000 фунтов.
3 шага + n шагов плюс 30 шагов, если у вас действительно тощий пассажирский груз.
Для самолета с двумя проходами - инструкции более сложные, но производительность примерно одинакова.
источник
Я бы, вероятно, использовал
std::nth_element
для разделения 20 самых тяжелых людей за линейное время. Затем используйте более сложный метод, чтобы найти и отбить самые тяжелые из них.источник
Вы можете сделать один проход по списку, чтобы получить среднее значение и стандартное отклонение, а затем использовать его для приблизительного числа людей, которые должны идти. Используйте part_sort для создания списка на основе этого числа. Если предположение было низким, снова используйте part_sort для остатка с новым предположением.
источник
У @James есть ответ в комментариях: a,
std::priority_queue
если вы можете использовать любой контейнер, или комбинациюstd::make_heap
иstd::pop_heap
(иstd::push_heap
), если вы хотите использовать что-то вроде astd::vector
.источник
Вот решение на основе кучи с использованием встроенного в Python модуля heapq. Он написан на Python, поэтому не отвечает на первоначальный вопрос, но он чище (IMHO), чем другое опубликованное решение Python.
Если k = количество пассажиров, которое нужно подбросить, а N = количество пассажиров, то лучшим вариантом для этого алгоритма является O (N), а наихудшим случаем для этого алгоритма является Nlog (N). Наихудший случай возникает, если k находится вблизи N в течение длительного времени. Вот пример худшего состава:
Однако в этом случае (выбрасывая людей из самолета (я полагаю, с парашютом)) тогда k должно быть меньше 3000, что составляет «миллионы людей». Следовательно, среднее время выполнения должно быть около Nlog (k), которое является линейным по отношению к количеству людей.
источник