# теория · шаг 2 из 6
Почему в полосе мало соседей: восемь клеток
Коротко
Точек в полосе может оказаться сколько угодно - хоть все n. Но проверять для каждой нужно не
всех, а лишь семь следующих по y. Доказательство счётом: точки, опасные для нашей, лежат в
прямоугольнике 2d × d (2d по x - ширина полосы, d по y - вперёд по отсортированному
списку); разрежем его на восемь квадратиков со стороной d/2. Каждый квадратик
целиком лежит в одной половине, а внутри половины расстояние между точками не меньше d - значит в
квадратике не больше одной точки. Восемь квадратиков, восемь точек, одна из них наша: остаётся
семь. Отсюда объединение стоит O(n), и весь алгоритм - Θ(n·log n).
Если ты умеешь доказать оценку «не больше семи соседей» разбиением на квадраты и понимаешь, откуда берётся линейность объединения, листай до «Теперь сам».
Как думаешь, сколько точек стоит проверить
В полосе шириной 2d лежат, скажем, двести точек, отсортированных по y. Для первой из них надо
найти, нет ли рядом точки ближе d.
Кандидаты - те, у кого разница по y меньше d. Их может быть и десять, и сто девяносто девять:
полоса-то узкая по x, но по y она длинная, и ничто не мешает набить её точками. Вопрос: сколько
кандидатов может оказаться ОДНОВРЕМЕННО близко и по y, и вообще?
На что это похоже
Парковка с разметкой. Машины стоят не как попало: каждая занимает своё место, и в пределах участка
d на 2d их физически помещается ограниченное число - не потому, что кто-то считал, а потому что
каждая занимает площадь. Точки в полосе ведут себя так же: они не могут сгрудиться, потому что
внутри своей половины любые две отстоят минимум на d.
Механизм: разбиение на восемь квадратов
точка p лежит в полосе; опасны точки, у которых:
разница по y меньше d (иначе расстояние уже >= d)
x внутри полосы (ширина полосы 2d: по d в каждую сторону от разреза)
значит все опасные точки лежат в прямоугольнике 2d (по x) × d (по y),
считая только точки ДАЛЬШЕ p по y (с большим y) - те, что идут следом в отсортированном
списке; пары с меньшим y уже проверены, когда мы стояли в той точке
2d × d разбит на восемь квадратов со стороной d/2. Каждый квадрат целиком лежит по одну сторону от разреза, а внутри половины любые две точки отстоят минимум на d - значит в квадрате не может быть двух точек.площадь прямоугольника: 2d × d
квадрат со стороной d/2 → их ровно 8
диагональ квадрата: (d/2)·√2 ≈ 0.707·d < d
Правило
В полосе для каждой точки достаточно проверить СЕМЬ следующих по y. Доказательство: опасные
точки лежат в прямоугольнике 2d × d, который разбивается на восемь квадратов со стороной d/2;
каждый квадрат целиком лежит в одной половине, диагональ квадрата 0.707·d меньше d, а внутри
половины минимальное расстояние равно d - значит в квадрате не больше одной точки. Итого не
больше восьми точек, одна из них наша. Поэтому проход по полосе линеен, и вся схема даёт
Θ(n·log n) при поддержании порядка по y слиянием (или Θ(n·log² n) с пересортировкой).
Разбор: считаем точки в прямоугольнике
Проверим утверждение на пальцах. Пусть d = 10, разрез по x = 0, и точка p = (−3, 0).
опасный прямоугольник: x от −10 до 10, y от 0 до 10
разобьём на квадраты 5 × 5 (границу относим к ЛЕВОМУ и НИЖНЕМУ квадрату,
чтобы каждая точка попала ровно в один):
левая половина: x ∈ [−10,−5) и [−5, 0), y ∈ [0,5) и [5,10] → 4 квадрата
правая половина: x ∈ [0, 5) и [5, 10], y ∈ [0,5) и [5,10] → 4 квадрата
в каждом квадрате диагональ = 5·√2 ≈ 7.07 < 10
Почему в квадрате не больше одной точки: обе точки квадрата лежали бы в ОДНОЙ половине (квадрат
границу не пересекает), а внутри половины ближайшая пара уже найдена и равна d = 10. Если бы в
квадрате оказались две точки, расстояние между ними было бы не больше 7.07 - меньше 10, то есть
рекурсия для этой половины нашла бы пару ближе, чем нашла. Противоречие.
Значит точек в прямоугольнике не больше восьми, и, отбросив саму p, проверять надо не больше
семи. Число 7 (иногда пишут 8) - это КОНСТАНТА, не зависящая ни от n, ни от плотности точек.
Именно она превращает проход по полосе в линейный.
Разбор: откуда берётся n·log n, а откуда n·log² n
Разница - в том, как получается полоса, отсортированная по y.
вариант 1: на каждом уровне сортировать полосу заново
T(n) = 2·T(n/2) + n·log n
здесь f(n) = n·log n - это ровно n^(log₂ 2) · log n, то есть случай
«поровну по уровням» с ЛОГАРИФМИЧЕСКИМ множителем: каждый уровень весит
одинаково, и добавляется ещё один log n
ответ Θ(n·log² n)
вариант 2: рекурсия возвращает свои точки, УЖЕ отсортированные по y,
а родитель сливает два отсортированных списка за линию
T(n) = 2·T(n/2) + n
ответ Θ(n·log n)
Второй вариант - это ровно сортировка слиянием, встроенная в геометрический алгоритм: тот же
приём, что и в разделе про сортировки, только слияние здесь попутное. Практическая разница между
log n и log² n на миллионе точек - примерно в двадцать раз, и на этом размере она уже заметна.
Замер на движке курса (задача этой главы): 250 точек, деление пополам с пересортировкой полосы - 19 тысяч шагов, полный перебор - 188 тысяч. Почти в десять раз, и это при том, что константа у перебора минимальная, а у деления есть и рекурсия, и сортировки.
Что ломается без этого
Первое: проверять в полосе ВСЕ пары - верно, но квадратично; вся оценка держится на обрыве по y.
Второе: считать, что «семь» надо зашивать в код как for k in range(7): правильнее писать обрыв по
условию (y_j − y_i)² < d², а семёрка - это следствие, которое гарантирует, что цикл не затянется.
Третье: строить полосу от d, взятого до рекурсии, а не после: ширина должна быть от УЖЕ
найденного минимума, иначе полоса окажется шире нужного. Четвёртое: раскладывать полосу на «левые» и «правые» и сравнивать их между собой: для ОТВЕТА это
не ошибка (разрезанная пара по определению состоит из левой и правой точки), но код усложняется на
ровном месте, а оценка «семь соседей» перестаёт из него читаться - она доказана для общего списка,
отсортированного по y. Пятое:
переносить оценку «не больше семи» в трёхмерие: там ограничены уже не все оси (полоса даёт одну,
порядок сканирования - вторую, а третья ничем не связана), поэтому доказательство не переносится
вовсе - о многомерии речь в следующем уроке.
Зачем это в работе
Само число 7 применяется редко, а вот приём доказательства - постоянно. «Сколько объектов может
одновременно оказаться рядом, если они не могут быть слишком близко друг к другу» - это вопрос из
обнаружения столкновений, из разметки карт, из планирования радиочастот, из алгоритмов кластеризации.
Ответ всегда получается одинаково: накрыть область ячейками такого размера, чтобы в ячейку
помещался максимум один объект, и посчитать ячейки. Эта же идея лежит в основе пространственного
хеширования: разбить пространство на сетку с шагом d и смотреть только соседние клетки - тогда
для равномерных данных поиск соседей становится почти константным.
Теперь сам
Почему в квадрате со стороной d/2 не может быть двух точек ИЗ ОДНОЙ половины? И может ли там
оказаться две точки из разных половин?
Ответ. Диагональ квадрата со стороной d/2 равна (d/2)·√2 ≈ 0.707·d, то есть любые две
точки внутри квадрата отстоят не больше чем на 0.707·d. Если бы обе принадлежали одной половине,
то в этой половине нашлась бы пара на расстоянии меньше d - но d и есть минимум, найденный
рекурсией для этой половины. Противоречие, значит двух точек из одной половины в квадрате быть не
может.
Из разных половин - тоже не может, но по другой причине: квадрат со стороной d/2 целиком лежит
по одну сторону от разреза. Разбиение строится именно так: граница прямоугольника по x идёт от
−d до d, а разрез проходит ровно посередине, поэтому вертикальная линия разреза совпадает с
границей между квадратами и ни один квадрат её не пересекает.
Вот почему в доказательстве важны оба условия: и размер квадрата (диагональ меньше d), и его
положение (не пересекает разрез). Убери любое - и оценка «не больше одной точки в квадрате»
перестанет работать.
Главное
В полосе для каждой точки достаточно проверить семь следующих по y. Опасные точки лежат в
прямоугольнике 2d × d; он разбивается на восемь квадратов со стороной d/2, каждый квадрат
целиком в одной половине, его диагональ 0.707·d меньше d, а внутри половины расстояния не меньше d -
значит в квадрате не больше одной точки. Отсюда не больше восьми точек, минус сама наша: семь.
Константа не зависит ни от n, ни от плотности, и именно она делает проход по полосе линейным. В
коде семёрку не зашивают - пишут обрыв по условию (y_j − y_i)² < d². Если полосу сортировать
заново на каждом уровне, получается Θ(n·log² n); если рекурсия возвращает точки, уже
отсортированные по y, и родитель сливает их за линию - честные Θ(n·log n).
Комментарии
Пока нет комментариев. Будь первым!
Оставить комментарий