Files
hello-algo/ru/docs/chapter_heap/top_k.md
T
2026-01-20 15:08:42 +08:00

73 lines
4.7 KiB
Markdown

# Задача Top-k
!!! question
Дан неупорядоченный массив `nums` длиной $n$. Необходимо вернуть $k$ наибольших элементов массива.
Для решения этой задачи сначала рассмотрим два метода с более прямолинейным подходом, а затем более эффективное решение с использованием кучи.
## Метод 1: обход с выбором
Можно выполнить $k$ раундов обхода, как показано на рисунке ниже, извлекая в каждом раунде 1-й, 2-й, $\dots$, $k$-й по величине элемент. Временная сложность составляет $O(nk)$.
Этот метод подходит только для случаев, когда $k \ll n$, поскольку при $k$, близком к $n$, временная сложность приближается к $O(n^2)$, что очень затратно по времени.
![Обход для поиска k наибольших элементов](../assets/top_k_traversal.png)
!!! tip
При $k = n$ мы получаем полную упорядоченную последовательность, что эквивалентно алгоритму «сортировка выбором».
## Метод 2: сортировка
Как показано на рисунке ниже, можно сначала отсортировать массив `nums`, а затем вернуть $k$ крайних правых элементов. Временная сложность составляет $O(n \log n)$.
Очевидно, что этот метод выполняет задачу «с избытком», поскольку нам нужно найти только $k$ наибольших элементов, а не сортировать остальные элементы.
![Сортировка для поиска k наибольших элементов](../assets/top_k_sorting.png)
## Метод 3: куча
Задачу Top-k можно решить более эффективно с использованием кучи. Процесс показан на рисунке ниже.
1. Инициализируется минимальная куча, элемент на вершине которой является наименьшим.
2. Сначала первые $k$ элементов массива последовательно добавляются в кучу.
3. Начиная с элемента $k + 1$, если текущий элемент больше элемента на вершине кучи, элемент с вершины извлекается, а текущий элемент добавляется в кучу.
4. После завершения обхода в куче сохраняются $k$ наибольших элементов.
=== "<1>"
![Поиск k наибольших элементов с использованием кучи](../assets/top_k_heap_step1.png)
=== "<2>"
![top_k_heap_step2](../assets/top_k_heap_step2.png)
=== "<3>"
![top_k_heap_step3](../assets/top_k_heap_step3.png)
=== "<4>"
![top_k_heap_step4](../assets/top_k_heap_step4.png)
=== "<5>"
![top_k_heap_step5](../assets/top_k_heap_step5.png)
=== "<6>"
![top_k_heap_step6](../assets/top_k_heap_step6.png)
=== "<7>"
![top_k_heap_step7](../assets/top_k_heap_step7.png)
=== "<8>"
![top_k_heap_step8](../assets/top_k_heap_step8.png)
=== "<9>"
![top_k_heap_step9](../assets/top_k_heap_step9.png)
Пример кода приведен ниже:
```src
[file]{top_k}-[class]{}-[func]{top_k_heap}
```
Всего выполняется $n$ раундов добавления и извлечения элементов, максимальная длина кучи равна $k$, поэтому временная сложность составляет $O(n \log k)$. Этот метод очень эффективен: при малых $k$ временная сложность стремится к $O(n)$; при больших $k$ временная сложность не превышает $O(n \log n)$.
Кроме того, этот метод подходит для сценариев использования с динамическими потоками данных. При непрерывном добавлении данных можно постоянно поддерживать элементы в куче, обеспечивая динамическое обновление $k$ наибольших элементов.