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

Пошук найближчої пари точок

Постановка задачі

Задано nn точок на площині. Кожна точка pip_i задана своїми координатами (xi,yi)(x_i,y_i). Потрібно знайти серед них дві такі точки, щоб відстань між ними була мінімальною:

mini,j=0n1,ijρ(pi,pj).\min_{\scriptstyle i, j=0 \ldots n-1,\atop \scriptstyle i \neq j } \rho (p_i, p_j).

Беремо звичайні евклідові відстані:

ρ(pi,pj)=(xixj)2+(yiyj)2.\rho (p_i,p_j) = \sqrt{(x_i-x_j)^2 + (y_i-y_j)^2} .

Тривіальний алгоритм — перебір усіх пар і обчислення відстані для кожної — працює за O(n2)O(n^2).

Нижче описано алгоритм, що працює за час O(nlogn)O(n \log n). Цей алгоритм запропонували Shamos і Hoey у 1975 році. (Джерело: Ch. 5 Notes of Algorithm Design by Kleinberg & Tardos, також див. тут) Preparata і Shamos також показали, що цей алгоритм є оптимальним у моделі дерева розв'язків.

Коли підходить цей алгоритм?
  • Вам потрібна саме найближча пара серед nn точок (одна мінімальна відстань), а не всі попарні відстані?
  • Відстань вимірюється евклідово? (якщо це метрика L1L_1 → див. трюки в Мангеттенська відстань)
  • Тривіальний перебір усіх пар за O(n2)O(n^2) занадто повільний для вашого nn, тож потрібен підхід «розділяй і володарюй» за O(nlogn)O(n \log n)?

Алгоритм

Будуємо алгоритм за загальною схемою алгоритмів «розділяй і володарюй»: алгоритм оформлений як рекурсивна функція, якій ми передаємо множину точок; ця рекурсивна функція розбиває цю множину навпіл, рекурсивно викликає себе на кожній половині, а потім виконує деякі операції, щоб об'єднати відповіді. Операція об'єднання полягає у виявленні випадків, коли одна точка оптимального розв'язку потрапила в одну половину, а інша — у другу (у цьому випадку рекурсивні виклики з кожної з половин не зможуть виявити цю пару окремо). Основна складність, як завжди у випадку алгоритмів «розділяй і володарюй», полягає в ефективній реалізації етапу злиття. Якщо рекурсивній функції передається множина з nn точок, то етап злиття має працювати не довше ніж O(n)O(n), тоді асимптотику всього алгоритму T(n)T(n) буде знайдено з рівняння:

T(n)=2T(n/2)+O(n).T(n) = 2T(n/2) + O(n).

Розв'язком цього рівняння, як відомо, є T(n)=O(nlogn).T(n) = O(n \log n).

Отже, переходимо до побудови алгоритму. Щоб у майбутньому дійти до ефективної реалізації етапу злиття, ми розділимо множину точок на дві підмножини за їхніми xx-координатами: фактично ми проводимо деяку вертикальну пряму, що ділить множину точок на дві підмножини приблизно однакового розміру. Таке розбиття зручно зробити так: сортуємо точки стандартним чином як пари чисел, тобто:

pi<pj(xi<xj)((xi=xj)(yi<yj))p_i < p_j \Longleftrightarrow (x_i < x_j) \lor \Big(\left(x_i = x_j\right) \wedge \left(y_i < y_j \right) \Big)

Потім беремо середню точку після сортування pm(m=n/2)p_m (m = \lfloor n/2 \rfloor), і всі точки до неї та саму pmp_m відносимо до першої половини, а всі точки після неї — до другої половини:

A1={pi  i=0m}A_1 = \{p_i \ | \ i = 0 \ldots m \} A2={pi  i=m+1n1}.A_2 = \{p_i \ | \ i = m + 1 \ldots n-1 \}.

Тепер, рекурсивно викликаючись на кожній з множин A1A_1 і A2A_2, ми знайдемо відповіді h1h_1 і h2h_2 для кожної з половин. І візьмемо кращу з них: h=min(h1,h2)h = \min(h_1, h_2).

Тепер нам потрібно виконати етап злиття, тобто ми намагаємося знайти такі пари точок, відстань між якими менша за hh і одна точка лежить в A1A_1, а інша — в A2A_2. Очевидно, що достатньо розглянути лише ті точки, які відокремлені від вертикальної прямої на відстань, меншу за hh, тобто множина BB точок, що розглядаються на цьому етапі, дорівнює:

B={pi  xixm <h}.B = \{ p_i\ | \ | x_i - x_m\ | < h \}.

Для кожної точки з множини BB ми намагаємося знайти точки, ближчі до неї, ніж hh. Наприклад, достатньо розглянути лише ті точки, yy-координата яких відрізняється не більш ніж на hh. Більше того, немає сенсу розглядати ті точки, yy-координата яких більша за yy-координату поточної точки. Таким чином, для кожної точки pip_i ми визначаємо множину розглядуваних точок C(pi)C(p_i) так:

C(pi)={pj  pjB,  yih<yjyi}.C(p_i) = \{ p_j\ |\ p_j \in B,\ \ y_i - h < y_j \le y_i \}.

Якщо ми відсортуємо точки множини BB за yy-координатою, то знайти C(pi)C(p_i) буде дуже легко: це кілька точок поспіль перед точкою pip_i.

Отже, у нових позначеннях етап злиття виглядає так: будуємо множину BB, сортуємо точки в ній за yy-координатою, потім для кожної точки piBp_i \in B розглядаємо всі точки pjC(pi)p_j \in C(p_i), і для кожної пари (pi,pj)(p_i,p_j) обчислюємо відстань і порівнюємо з поточною найкращою відстанню.

На перший погляд це все ще неоптимальний алгоритм: здається, що розміри множин C(pi)C(p_i) будуть порядку nn, і потрібна асимптотика не вийде. Однак, на диво, можна довести, що розмір кожної з множин C(pi)C(p_i) є величиною O(1)O(1), тобто не перевищує деякої малої сталої незалежно від самих точок. Доведення цього факту наведено в наступному розділі.

Нарешті, звернемо увагу на сортування, які містить наведений вище алгоритм: по-перше, сортування за парами (x,y)(x, y), а по-друге, сортування елементів множини BB за yy. Насправді обидва ці сортування всередині рекурсивної функції можна усунути (інакше ми не досягли б оцінки O(n)O(n) для етапу злиття, і загальна асимптотика алгоритму була б O(nlog2n)O(n \log^2 n)). Позбутися першого сортування легко — достатньо виконати це сортування перед початком рекурсії: адже самі елементи всередині рекурсії не змінюються, тому немає потреби сортувати знову. З другим сортуванням трохи складніше, виконати його заздалегідь не вийде. Але, згадавши сортування злиттям, яке теж працює за принципом «розділяй і володарюй», ми можемо просто вбудувати це сортування в нашу рекурсію. Нехай рекурсія, отримуючи деяку множину точок (як ми пам'ятаємо, упорядковану за парами (x,y)(x, y)), повертає ту саму множину, але відсортовану за yy-координатою. Для цього достатньо злити (за O(n)O(n)) два результати, повернені рекурсивними викликами. Це дасть множину, відсортовану за yy-координатою.

Оцінка асимптотики

Щоб показати, що наведений вище алгоритм насправді виконується за O(nlogn)O(n \log n), нам потрібно довести такий факт: C(pi)=O(1)|C(p_i)| = O(1).

Отже, розглянемо деяку точку pip_i; нагадаємо, що множина C(pi)C(p_i) — це множина точок, yy-координата яких лежить у відрізку [yih;yi][y_i-h; y_i], і, до того ж, за координатою xx сама точка pip_i та всі точки множини C(pi)C(p_i) лежать у смузі шириною 2h2h. Інакше кажучи, розглядувані нами точки pip_i та C(pi)C(p_i) лежать у прямокутнику розміром 2h×h2h \times h.

Наше завдання — оцінити максимальну кількість точок, які можуть лежати в цьому прямокутнику 2h×h2h \times h; так ми оцінимо максимальний розмір множини C(pi)C(p_i). При цьому, оцінюючи, ми не повинні забувати, що можуть бути повторювані точки.

Згадаймо, що hh було отримано з результатів двох рекурсивних викликів — на множинах A1A_1 та A2A_2, причому A1A_1 містить точки ліворуч від прямої розбиття та частково на ній, A2A_2 містить решту точок прямої розбиття та точки праворуч від неї. Для будь-якої пари точок з A1A_1, як і з A2A_2, відстань не може бути меншою за hh — інакше це означало б некоректну роботу рекурсивної функції.

Щоб оцінити максимальну кількість точок у прямокутнику 2h×h2h \times h, поділимо його на два квадрати h×hh \times h, перший квадрат включає всі точки C(pi)A1C(p_i) \cap A_1, а другий містить усі інші, тобто C(pi)A2C(p_i) \cap A_2. З наведених вище міркувань випливає, що в кожному з цих квадратів відстань між будь-якими двома точками щонайменше hh.

Покажемо, що в кожному квадраті є не більше чотирьох точок. Наприклад, це можна зробити так: поділимо квадрат на 44 підквадрати зі сторонами h/2h/2. Тоді в кожному з цих підквадратів не може бути більше однієї точки (оскільки навіть діагональ дорівнює h/2h / \sqrt{2}, що менше за hh). Отже, у всьому квадраті не може бути більше ніж 44 точки.

Отже, ми довели, що в прямокутнику 2h×h2h \times h не може бути більше ніж 42=84 \cdot 2 = 8 точок, і, отже, розмір множини C(pi)C(p_i) не може перевищувати 77, що й вимагалося.

Реалізація

Уведемо структуру даних для зберігання точки (її координат і номера) та оператори порівняння, потрібні для двох типів сортування:

struct pt {
int x, y, id;
};

struct cmp_x {
bool operator()(const pt & a, const pt & b) const {
return a.x < b.x || (a.x == b.x && a.y < b.y);
}
};

struct cmp_y {
bool operator()(const pt & a, const pt & b) const {
return a.y < b.y;
}
};

int n;
vector<pt> a;

Для зручної реалізації рекурсії вводимо допоміжну функцію upd_ans(), яка обчислюватиме відстань між двома точками й перевірятиме, чи вона краща за поточну відповідь:

double mindist;
pair<int, int> best_pair;

void upd_ans(const pt & a, const pt & b) {
double dist = sqrt((a.x - b.x)*(a.x - b.x) + (a.y - b.y)*(a.y - b.y));
if (dist < mindist) {
mindist = dist;
best_pair = {a.id, b.id};
}
}

Нарешті, реалізація самої рекурсії. Припускається, що перед її викликом масив a[]a[] уже відсортований за xx-координатою. У рекурсію ми передаємо лише два вказівники l,rl, r, які вказують, що вона має шукати відповідь для a[lr)a[l \ldots r). Якщо відстань між rr і ll замала, рекурсію треба зупинити та виконати тривіальний алгоритм для пошуку найближчої пари, а потім відсортувати підмасив за yy-координатою.

Щоб злити дві множини точок, отримані з рекурсивних викликів, в одну (упорядковану за yy-координатою), ми використовуємо стандартну функцію STL merge()merge() і створюємо допоміжний буфер t[]t[] (один для всіх рекурсивних викликів). (Використовувати inplace_merge() недоцільно, бо вона загалом не працює за лінійний час.)

Нарешті, множина BB зберігається в тому самому масиві tt.

vector<pt> t;

void rec(int l, int r) {
if (r - l <= 3) {
for (int i = l; i < r; ++i) {
for (int j = i + 1; j < r; ++j) {
upd_ans(a[i], a[j]);
}
}
sort(a.begin() + l, a.begin() + r, cmp_y());
return;
}

int m = (l + r) >> 1;
int midx = a[m].x;
rec(l, m);
rec(m, r);

merge(a.begin() + l, a.begin() + m, a.begin() + m, a.begin() + r, t.begin(), cmp_y());
copy(t.begin(), t.begin() + r - l, a.begin() + l);

int tsz = 0;
for (int i = l; i < r; ++i) {
if (abs(a[i].x - midx) < mindist) {
for (int j = tsz - 1; j >= 0 && a[i].y - t[j].y < mindist; --j)
upd_ans(a[i], t[j]);
t[tsz++] = a[i];
}
}
}

До речі, якщо всі координати цілі, то під час рекурсії можна не переходити до дробових значень і зберігати в mindistmindist квадрат мінімальної відстані.

У головній програмі рекурсію слід викликати так:

t.resize(n);
sort(a.begin(), a.end(), cmp_x());
mindist = 1E20;
rec(0, n);

Рандомізовані алгоритми з лінійним часом

Рандомізований алгоритм з лінійним сподіваним часом

Альтернативний метод, спочатку запропонований Rabin у 1976 році, виникає з дуже простої ідеї евристично покращити час роботи: ми можемо поділити площину на сітку квадратів d×dd \times d, тоді потрібно лише перевіряти відстані між точками одного блока або сусідніх блоків (хіба що всі квадрати від'єднані один від одного, але ми уникнемо цього за побудовою), оскільки будь-яка інша пара має більшу відстань, ніж дві точки в одному квадраті.

Example of the squares strategy

Ми розглядатимемо лише квадрати, що містять хоча б одну точку. Позначимо через n1,n2,,nkn_1, n_2, \dots, n_k кількість точок у кожному з kk решти квадратів. Припускаючи, що принаймні дві точки лежать в одному або в сусідніх квадратах і що немає дубльованих точок, часова складність дорівнює Θ ⁣(i=1kni2)\Theta\!\left(\sum\limits_{i=1}^k n_i^2\right). Ми можемо шукати дубльовані точки за сподіваний лінійний час за допомогою хеш-таблиці, і в разі їх наявності відповіддю є ця пара.

Доведення

Для ii-го квадрата, що містить nin_i точок, кількість пар усередині дорівнює Θ(ni2)\Theta(n_i^2). Якщо ii-й квадрат сусідить із jj-м квадратом, то ми також виконуємо ninjmax(ni,nj)2ni2+nj2n_i n_j \le \max(n_i, n_j)^2 \le n_i^2 + n_j^2 порівнянь відстаней. Зауважимо, що кожен квадрат має щонайбільше 88 сусідніх квадратів, тому суму всіх порівнянь можна обмежити величиною Θ(i=1kni2)\Theta(\sum_{i=1}^{k} n_i^2). \quad \blacksquare

Тепер нам потрібно визначитися з тим, як задати dd, щоб воно мінімізувало Θ ⁣(i=1kni2)\Theta\!\left(\sum\limits_{i=1}^k n_i^2\right).

Вибір d

Нам потрібно, щоб dd було наближенням мінімальної відстані dd. Richard Lipton запропонував випадково взяти вибірку з nn відстаней і обрати dd як найменшу з цих відстаней як наближення для dd. Тепер ми доведемо, що сподіваний час роботи алгоритму є лінійним.

Доведення

Уявімо розташування точок у квадратах за певного вибору dd, скажімо xx. Розглянемо dd як випадкову величину, що виникає внаслідок нашої вибірки відстаней. Визначмо C(x):=i=1k(x)ni(x)2C(x) := \sum_{i=1}^{k(x)} n_i(x)^2 як оцінку вартості для конкретного розташування, коли ми обираємо d=xd=x. Тепер визначмо λ(x)\lambda(x) так, що C(x)=λ(x)nC(x) = \lambda(x) \, n. Яка ймовірність того, що такий вибір xx переживе вибірку з nn незалежних відстаней? Якщо хоча б одна пара серед обраних має відстань, меншу за xx, це розташування буде замінено на менше dd. Усередині квадрата приблизно 1/161/16 пар дали б меншу відстань (уявімо чотири підквадрати в кожному квадраті; за принципом Діріхле, принаймні один підквадрат має ni/4n_i/4 точок), тож ми маємо приблизно i=1k(ni/42)i=1k116(ni2)\sum_{i=1}^{k} {n_i/4 \choose 2} \approx \sum_{i=1}^{k} \frac{1}{16} {n_i \choose 2} пар, що дають менше підсумкове dd. Це, приблизно, 132i=1kni2=132λ(x)n\frac{1}{32} \sum_{i=1}^{k} n_i^2 = \frac{1}{32} \lambda(x) n. З іншого боку, є приблизно 12n2\frac{1}{2} n^2 пар, які можуть бути обрані у вибірку. Маємо, що ймовірність обрати у вибірку пару з відстанню, меншою за xx, щонайменше (приблизно)

λ(x)n/32n2/2=λ(x)/16n\frac{\lambda(x) \, n / 32}{n^2 / 2} = \frac{\lambda(x)/16}{n}

тож імовірність того, що хоча б одну таку пару буде обрано протягом nn раундів (і тому буде знайдено менше dd), дорівнює

1(1λ(x)/16n)n1eλ(x)/161 - \left(1 - \frac{\lambda(x)/16}{n}\right)^n \ge 1 - e^{-\lambda(x)/16}

(ми скористалися тим, що (1+x)nexn(1 + x)^n \le e^{xn} для будь-якого дійсного числа xx, див. нерівності Бернуллі).
Зауважимо, що це прямує до 11 експоненційно зі зростанням λ(x)\lambda(x). Це натякає, що λ\lambda буде малим для погано обраного dd.

Ми показали, що Pr(dx)1eλ(x)/16\Pr(d \le x) \ge 1 - e^{-\lambda(x)/16}, або, що рівносильно, Pr(dx)eλ(x)/16\Pr(d \ge x) \le e^{-\lambda(x)/16}. Нам потрібно знати Pr(λ(d)something)\Pr(\lambda(d) \ge \text{something}), щоб мати змогу оцінити її сподіване значення. Зауважимо, що λ(d)λ(x)    dx\lambda(d) \ge \lambda(x) \iff d \ge x. Це тому, що зменшення квадратів лише зменшує кількість точок у кожному квадраті (розщеплює точки на інші квадрати), і це продовжує зменшувати суму квадратів. Отже,

Pr(λ(d)λ(x))=Pr(dx)eλ(x)/16    Pr(λ(d)t)et/16    E[λ(d)]0+et/16dt=16\Pr(\lambda(d) \ge \lambda(x)) = \Pr(d \ge x) \le e^{-\lambda(x)/16} \implies \Pr(\lambda(d) \ge t) \le e^{-t/16} \implies \mathbb{E}[\lambda(d)] \le \int_{0}^{+\infty} e^{-t/16} \, \mathrm{d}t = 16

(ми скористалися тим, що E[X]=0+Pr(Xx)dxE[X] = \int_0^{+\infty} \Pr(X \ge x) \, \mathrm{d}x, див. доведення на Stackexchange).

Нарешті, E[C(d)]=E[λ(d)n]16n\mathbb{E}[C(d)] = \mathbb{E}[\lambda(d) \, n] \le 16n, і сподіваний час роботи дорівнює O(n)O(n), з розумним сталим множником. \quad \blacksquare

Реалізація алгоритму

Перевага цього алгоритму в тому, що його прямолінійно реалізувати, але він усе ж має добру швидкодію на практиці. Спочатку ми беремо вибірку з nn відстаней і задаємо dd як мінімум цих відстаней. Потім ми вставляємо точки у «блоки» за допомогою хеш-таблиці з 2D-координат у вектор точок. Нарешті, просто обчислюємо відстані між парами одного блока та парами сусідніх блоків. Операції з хеш-таблицею мають O(1)O(1) сподіваний час, і тому наш алгоритм зберігає O(n)O(n) сподіваний час зі збільшеною сталою.

Перегляньте це надсилання до Library Checker.

#include <bits/stdc++.h>
using namespace std;


using ll = long long;
using ld = long double;


struct pt {
ll x, y;
pt() {}
pt(ll x_, ll y_) : x(x_), y(y_) {}
void read() {
cin >> x >> y;
}
};

bool operator==(const pt& a, const pt& b) {
return a.x == b.x and a.y == b.y;
}


struct CustomHashPoint {
size_t operator()(const pt& p) const {
static const uint64_t C = chrono::steady_clock::now().time_since_epoch().count();
return C ^ ((p.x << 32) ^ p.y);
}
};


ll dist2(pt a, pt b) {
ll dx = a.x - b.x;
ll dy = a.y - b.y;
return dx*dx + dy*dy;
}


pair<int,int> closest_pair_of_points(vector<pt> P) {
int n = int(P.size());
assert(n >= 2);

// якщо є дубльована точка, то ми маємо розв'язок
unordered_map<pt,int,CustomHashPoint> previous;
for (int i = 0; i < int(P.size()); ++i) {
auto it = previous.find(P[i]);
if (it != previous.end()) {
return {it->second, i};
}
previous[P[i]] = i;
}

unordered_map<pt,vector<int>,CustomHashPoint> grid;
grid.reserve(n);

mt19937 rd(chrono::system_clock::now().time_since_epoch().count());
uniform_int_distribution<int> dis(0, n-1);

ll d2 = dist2(P[0], P[1]);
pair<int,int> closest = {0, 1};

auto candidate_closest = [&](int i, int j) -> void {
ll ab2 = dist2(P[i], P[j]);
if (ab2 < d2) {
d2 = ab2;
closest = {i, j};
}
};

for (int i = 0; i < n; ++i) {
int j = dis(rd);
int k = dis(rd);
while (j == k) k = dis(rd);
candidate_closest(j, k);
}

ll d = ll( sqrt(ld(d2)) + 1 );

for (int i = 0; i < n; ++i) {
grid[{P[i].x/d, P[i].y/d}].push_back(i);
}

// той самий блок
for (const auto& it : grid) {
int k = int(it.second.size());
for (int i = 0; i < k; ++i) {
for (int j = i+1; j < k; ++j) {
candidate_closest(it.second[i], it.second[j]);
}
}
}

// сусідні блоки
for (const auto& it : grid) {
auto coord = it.first;
for (int dx = 0; dx <= 1; ++dx) {
for (int dy = -1; dy <= 1; ++dy) {
if (dx == 0 and dy == 0) continue;
pt neighbour = pt(
coord.x + dx,
coord.y + dy
);
for (int i : it.second) {
if (not grid.count(neighbour)) continue;
for (int j : grid.at(neighbour)) {
candidate_closest(i, j);
}
}
}
}
}

return closest;
}

Альтернативний рандомізований алгоритм з лінійним сподіваним часом

Тепер ми наводимо інший рандомізований алгоритм, який менш практичний, але для якого дуже легко показати, що він працює за сподіваний лінійний час.

  • Випадково переставимо nn точок
  • Візьмемо δ:=dist(p1,p2)\delta := \operatorname{dist}(p_1, p_2)
  • Розіб'ємо площину на квадрати зі стороною δ/2\delta/2
  • Для i=1,2,,ni = 1,2,\dots,n:
    • Беремо квадрат, що відповідає pip_i
    • Перебираємо 2525 квадратів у межах двох кроків до нашого квадрата в сітці квадратів, що розбиває площину
    • Якщо деяка pjp_j у тих квадратах має dist(pj,pi)<δ\operatorname{dist}(p_j, p_i) < \delta, то
      • Перераховуємо розбиття та квадрати з δ:=dist(pj,pi)\delta := \operatorname{dist}(p_j, p_i)
      • Зберігаємо точки p1,,pip_1, \dots, p_i у відповідних квадратах
    • інакше зберігаємо pip_i у відповідному квадраті
  • виводимо δ\delta

Коректність випливає з того, що в будь-який момент ми вже маємо деяку пару з відстанню δ\delta, тож ми намагаємося знайти лише нові пари з відстанню, меншою за δ\delta. Оскільки кожен квадрат має сторону δ/2\delta/2, кандидатна пара може бути щонайбільше на відстані 22 квадратів, тож для заданої точки ми перевіряємо кандидатів у 2525 навколишніх квадратах. Будь-яка точка у квадраті, розташованому далі, завжди даватиме відстань, більшу за δ\delta.

Хоча цей алгоритм може виглядати повільним через багаторазовий перерахунок усього, ми можемо показати, що повна сподівана вартість є лінійною.

Доведення

Нехай XiX_i — випадкова величина, що дорівнює 11, коли точка pip_i спричиняє зміну δ\delta і перерахунок структур даних, і 00 — якщо ні. Легко показати, що вартість дорівнює O(n+i=1niXi)O(n + \sum_{i=1}^{n} i X_i), оскільки на ii-му кроці ми розглядаємо лише перші ii точок. Однак виявляється, що Pr(Xi=1)2i\Pr(X_i = 1) \le \frac{2}{i}. Це тому, що на ii-му кроці δ\delta — це відстань найближчої пари в {p1,,pi}\{p_1,\dots,p_i\}, а Pr(Xi=1)\Pr(X_i = 1) — це ймовірність того, що pip_i належить найближчій парі, що трапляється лише в 2(i1)2(i-1) парах з i(i1)i(i-1) можливих пар (за припущення, що всі відстані різні), тож імовірність щонайбільше 2(i1)i(i1)=2i\frac{2(i-1)}{i(i-1)} = \frac{2}{i}, оскільки ми попередньо перемішали точки рівномірно.

Тому ми можемо побачити, що сподівана вартість дорівнює

O ⁣(n+i=1niPr(Xi=1))O ⁣(n+i=1ni2i)=O(3n)=O(n)O\!\left(n + \sum_{i=1}^{n} i \Pr(X_i = 1)\right) \le O\!\left(n + \sum_{i=1}^{n} i \frac{2}{i}\right) = O(3n) = O(n) \quad \quad \blacksquare

Узагальнення: пошук трикутника з мінімальним периметром

Описаний вище алгоритм цікаво узагальнюється на таку задачу: серед заданої множини точок обрати три різні точки так, щоб сума попарних відстаней між ними була найменшою.

Насправді для розв'язання цієї задачі алгоритм залишається тим самим: ми ділимо поле на дві половини вертикальною прямою, рекурсивно викликаємо розв'язок на обох половинах, обираємо мінімум minperminper зі знайдених периметрів, будуємо смугу завтовшки minper/2minper / 2 і перебираємо всі трикутники, що можуть покращити відповідь. (Зауважимо, що трикутник з периметром minper\le minper має найдовшу сторону minper/2\le minper / 2.)

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

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