У меня есть несортированный массив . У меня есть запросы, в которых я даю диапазон, а затем должно быть возвращено максимальное значение из этого диапазона. Например:
array[]={23,17,9,45,78,2,4,6,90,1};
query(both inclusive): 2 6
answer: 78
Какой алгоритм или структуру данных я создаю, чтобы быстро извлечь максимальное значение из любого диапазона. (Запросов много)
РЕДАКТИРОВАТЬ: Это действительно простая версия актуальной проблемы. Я могу иметь размер массива до 100000 и количество запросов до 100000. Поэтому мне определенно требуется некоторая предварительная обработка, которая облегчит быстрый ответ на запрос.
algorithms
array
sudeepdino008
источник
источник
Ответы:
Я думаю, что вы могли бы построить какое-то двоичное дерево, где каждый узел представляет максимальное значение своих детей:
Тогда вам нужно только найти способ определить, какие узлы вам необходимо минимально проверить, чтобы найти максимальное значение в запрашиваемом диапазоне. В этом примере, чтобы получить максимальное значение в диапазоне индекса
[2, 6]
(включительно), вы должныmax(45, 78, 4)
вместоmax(9, 45, 78, 2, 4)
. По мере роста дерева выгода будет больше.источник
78
(и пропустить2
), потому что он знает, что индекс6
находится в этом поддереве.Чтобы дополнить ответ ngoaho91.
Лучший способ решить эту проблему - использовать структуру данных дерева сегментов. Это позволяет вам отвечать на такие запросы в O (log (n)), что будет означать, что общая сложность вашего алгоритма будет O (Q logn), где Q - количество запросов. Если бы вы использовали простой алгоритм, общая сложность была бы O (Q n), что явно медленнее.
Однако существует недостаток использования деревьев сегментов. Это занимает много памяти, но во многих случаях вам важнее память, чем скорость.
Я кратко опишу алгоритмы, используемые этим DS:
Дерево сегментов - это особый случай дерева двоичного поиска, где каждый узел содержит значение диапазона, которому он назначен. Корневому узлу присваивается диапазон [0, n]. Левый ребенок получает диапазон [0, (0 + n) / 2], а правый ребенок [(0 + n) / 2 + 1, n]. Таким образом, дерево будет построено.
Создать дерево :
Дерево запросов
Если вам нужны дальнейшие объяснения, просто дайте мне знать.
Кстати, Segment Tree также поддерживает обновление одного элемента или диапазона элементов в O (log n)
источник
O(log(n))
чтобы каждый элемент был добавлен в дерево. Таким образом, общая сложностьO(nlog(n))
Лучший алгоритм будет за O (n) время, как показано ниже, пусть start, end будет индексом границ диапазона
источник
max
чтобыa[i]
и начатьfor
цикл вi+1
.)start
, остановиться наend
). И я согласен, это является лучшим для единовременного поиска в. Ответ @ ThijsvanDien будет лучше только в том случае, если поиск будет выполняться несколько раз, поскольку для первоначальной настройки требуется больше времени.Решения, основанные на бинарном дереве / сегментном дереве, действительно указывают в правильном направлении. Однако можно возразить, что им требуется много дополнительной памяти. Есть два решения этих проблем:
Во-первых, поскольку дерево очень структурировано, вы можете использовать структуру, подобную куче, для неявного определения дерева, вместо того, чтобы представлять дерево с помощью узлов, левых и правых указателей, интервалов и т. Д. Это экономит много памяти по существу нет снижения производительности - вам нужно выполнить немного больше арифметики указателя.
Второй момент заключается в том, что за счет немного больше работы во время оценки вы можете использовать M-арное дерево, а не двоичное дерево. Например, если вы используете 3-арное дерево, вы будете вычислять максимум 3 элемента за раз, затем 9 элементов за один раз, затем 27 и т. Д. Тогда потребуется дополнительная память N / (M-1) - вы можете докажите, используя формулу геометрического ряда. Например, если вы выберете M = 11, вам потребуется 1/10 хранения метода двоичного дерева.
Вы можете проверить, что эти наивные и оптимизированные реализации в Python дают одинаковые результаты:
против
источник
попробуйте структуру данных "дерево сегментов"
есть два шага
build_tree () O (n)
запрос (int min, int max) O (nlogn)
http://en.wikipedia.org/wiki/Segment_tree
редактировать:
ребята, вы просто не читаете вики, которую я отправил!
Этот алгоритм:
- Вы проходите массив 1 раз, чтобы построить дерево. O (n)
- следующие 100000000+ раз, когда вы хотите узнать максимум какой-либо части массива, просто вызовите функцию запроса. O (logn) для каждого запроса
- c ++ реализует здесь geeksforgeeks.org/segment-tree-set-1-range-minimum-query/
старый алгоритм:
каждый запрос, просто пройти по выбранной области и найти.
так что, если вы собираетесь использовать этот алгоритм для обработки один раз, хорошо, он медленнее, чем старый. но если вы собираетесь обрабатывать огромное количество запросов (млрд), это очень эффективно вы можете создать текстовый файл , как это, для тестовой
линии 1: 50000 случайное число из 0-1000000, расщепленный на «(пробел)» (это массив)
линия 2: 2 случайное число от 1 до 50000, разделенное на '(пробел)' (это запрос)
...
строка 200000: нравится строка 2, это тоже случайный запрос
это примерная проблема, извините, но это на вьетнамском языке
http://vn.spoj.com/problems/NKLINEUP/,
если вы решите ее по-старому, вы никогда не пропустите.
источник
O(n)
поиск в массиве, как описано в ответе tarun_telang. Первый инстинкт заключается в том, чтоO(log n + k)
это быстрее, чемO(n)
, ноO(log n + k)
это просто извлечение подмассива - эквивалентноO(1)
доступу к массиву с учетом начальной и конечной точек. Вам все равно придется пройти через него, чтобы найти максимум.Вы можете получить O (1) на запрос (с конструкцией O (n log n)), используя структуру данных, называемую разреженной таблицей. Для каждой степени 2 давайте сохраним максимум для каждого сегмента этой длины. Теперь для данного сегмента [l, r) вы получите максимум максимумов на [l + 2 ^ k) и [r-2 ^ k, r) для соответствующего k. Они перекрываются но все нормально
источник