Перейти до основного вмісту

KK-та порядкова статистика за O(N)O(N)

Маємо масив AA розміру NN і число KK. Задача полягає в тому, щоб знайти KK-те за величиною число в масиві, тобто KK-ту порядкову статистику.

Базова ідея — використати ідею алгоритму швидкого сортування. Насправді сам алгоритм простий, складніше довести, що він працює в середньому за O(N)O(N), на відміну від швидкого сортування.

Коли підходить цей алгоритм?
  • Чи потрібен лише один (KK-й) елемент за порядком, а не повне сортування масиву?
  • Чи допустимо переставляти елементи масиву на місці (часткове впорядкування навколо бар'єра)?
  • Чи влаштовує середній час O(N)O(N) — або потрібна гарантія в найгіршому випадку (тоді бери «медіану медіан»)?

Реалізація (нерекурсивна)

template <class T>
T order_statistics (std::vector<T> a, unsigned n, unsigned k)
{
using std::swap;
for (unsigned l=1, r=n; ; )
{
if (r <= l+1)
{
// розмір поточної частини дорівнює 1 або 2, тож відповідь легко знайти
if (r == l+1 && a[r] < a[l])
swap (a[l], a[r]);
return a[k];
}

// впорядковуємо a[l], a[l+1], a[r]
unsigned mid = (l + r) >> 1;
swap (a[mid], a[l+1]);
if (a[l] > a[r])
swap (a[l], a[r]);
if (a[l+1] > a[r])
swap (a[l+1], a[r]);
if (a[l] > a[l+1])
swap (a[l], a[l+1]);

// виконуємо розбиття
// бар'єром є a[l + 1], тобто медіана серед a[l], a[l + 1], a[r]
unsigned
i = l+1,
j = r;
const T
cur = a[l+1];
for (;;)
{
while (a[++i] < cur) ;
while (a[--j] > cur) ;
if (i > j)
break;
swap (a[i], a[j]);
}

// вставляємо бар'єр
a[l+1] = a[j];
a[j] = cur;

// продовжуємо працювати в тій частині, яка має містити шуканий елемент
if (j >= k)
r = j-1;
if (j <= k)
l = i;
}
}

Зауваження

  • Наведений вище рандомізований алгоритм називається швидкий вибір (quickselect). Перед його викликом слід випадково перемішати AA або використовувати випадковий елемент як бар'єр, щоб він працював коректно. Існують також детерміновані алгоритми, які розв'язують зазначену задачу за лінійний час, наприклад медіана медіан.
  • std::nth_element розв'язує це в C++, але реалізація в gcc працює в найгіршому випадку за час O(nlogn)O(n \log n ).
  • Пошук KK найменших елементів можна звести до пошуку KK-го елемента з лінійними додатковими витратами, оскільки це саме ті елементи, що менші за KK-й.

Задачі для практики

Відеоматеріали