К содержанию

Подтема 6 из 6 · дополнительный урок

Соседи, другие центры и статистика

Цепочки соседей вместо границ, минимакс, антицентр и сумма квадратов, точка наведения, корреляция Пирсона и выбросы по квартилям.

Порог соседства

Как разделить точки на группы, если границы по рисунку провести трудно?

Соединить точки, которые ближе порога ε, и считать одной группой всё, что связано цепочками таких шагов. Две точки — соседи, если d(A, B) ≤ ε. Возьмём A = (0; 0), B = (0,8; 0), C = (1,6; 0) и ε = 0,9.

Тогда d(A, B) = 0,8 ≤ 0,9 и d(B, C) = 0,8 ≤ 0,9, но d(A, C) = 1,6 > 0,9. Тем не менее все три точки образуют одну связную группу: от A до C можно пройти через B, причём каждый шаг достаточно короткий.

Внутри такой группы не требуется, чтобы каждая точка была близка к каждой. Требуется возможность добраться от одной точки до другой по цепочке соседей.

Если нарисовать отрезки между всеми парами соседей, получится граф: точки называются его вершинами, соединяющие их отрезки — рёбрами. Группа вершин, между которыми можно пройти по рёбрам и к которой нельзя присоединить другие достижимые вершины, называется компонентой связности. Термин выглядит сложнее самой идеи: это отдельный связный кусок рисунка.

Листай кадры схемы: в каждом свой порог. При маленьком значении точкам не хватает связей, при подходящем возникают две группы, при слишком большом они сливаются.

  1. Порог 0,5: соединены пары на расстоянии не больше 0,5, групп 8 (1, 1, 1, 1, 1, 1, 1, 1).
  2. Порог 0,9: соединены пары на расстоянии не больше 0,9, групп 2 (4, 4).
  3. Порог 3: соединены пары на расстоянии не больше 3, групп 1 (8).

Построение группы

Как программа находит всю группу, а не только ближайших соседей первой точки?

Группу растят от любой точки: ищут соседей у каждой добавленной точки, пока новых не останется. Алгоритм построения одной группы:

  1. Взять любую ещё не распределённую точку.
  2. Найти её нераспределённых соседей.
  3. Добавить их в текущую группу.
  4. Найти соседей у добавленных точек.
  5. Продолжать, пока все точки текущей группы не будут обработаны.

После этого берём следующую нераспределённую точку и начинаем новую группу.

Здесь принципиально важен четвёртый шаг. Если искать соседей только у первой точки, мы получим лишь её непосредственное окружение, а не весь кластер. Вот реализация:

from math import dist  def split_clusters(points, eps):    remaining = points.copy()    clusters = []    while remaining:        cluster = [remaining.pop()]        pos = 0        while pos < len(cluster):            point = cluster[pos]            rest = []            for other in remaining:                if dist(point, other) <= eps:                    cluster.append(other)                else:                    rest.append(other)            remaining = rest            pos += 1        clusters.append(cluster)    return clusters

Разберём устройство кода.

  • remaining содержит точки, которые ещё не отнесены ни к одной группе. создаёт отдельный список: сам исходный список points мы не будем опустошать.
  • — цикл продолжается, пока список не пуст.
  • cluster = [remaining.pop()]: pop() извлекает последний элемент списка и удаляет его из списка. Эта точка становится первой точкой новой группы.
  • pos — номер точки текущей группы, соседей которой мы сейчас будем искать.
  • while pos < len(cluster): — размер cluster может увеличиваться по мере работы. Поэтому цикл обработает и исходную точку, и всех добавленных соседей.
  • В rest соберём точки, которые пока остались нераспределёнными. Если точка близка к обрабатываемой — cluster.append(other), если нет — rest.append(other).
  • После прохода remaining = rest: точки, присоединённые к кластеру, исчезают из списка нераспределённых.
  • pos += 1 — переходим к следующей точке растущей группы.

Пройдём алгоритм по шагам на точках A, B, C и ещё двух точках D = (3,5; 0), E = (4,2; 0,4) с порогом 0,9:

  1. remaining — все точки, групп пока нет.
  2. pop() забирает последнюю точку E: с неё начинается новая группа.
  3. Соседи E в пределах 0,9: D — в группу.
  4. Соседи D в пределах 0,9: новых нет.
  5. pos дошёл до конца: группа [E, D] готова.
  6. pop() забирает последнюю точку C: с неё начинается новая группа.
  7. Соседи C в пределах 0,9: B — в группу.
  8. Соседи B в пределах 0,9: A — в группу.
  9. Соседи A в пределах 0,9: новых нет.
  10. pos дошёл до конца: группа [C, B, A] готова. Нераспределённых точек нет.
Почему находится вся группа

Почему алгоритм находит всю связную группу? Каждая добавленная точка рано или поздно обрабатывается. Значит, будут найдены её ещё не добавленные соседи. Затем соседи этих соседей — и так далее.

Если существует цепочка из коротких шагов от начальной точки до некоторой другой, алгоритм последовательно пройдёт по ней.

И наоборот: точка добавляется только потому, что близка к уже включённой. Значит, случайно перескочить в группу, с которой вообще нет связей, алгоритм не может.

Однако здесь есть важное ограничение: алгоритм точно находит компоненты связности для выбранного порога. Совпадут ли они с кластерами из условия — отдельный вопрос.

Выбор порога

Как выбрать порог ε?

Порог должен быть не меньше шагов внутри кластеров и меньше промежутков между кластерами; проверяют несколько значений и смотрят на размеры групп. В файле example27.txt у каждой группы точки связаны шагами √2 < 1,5, а между группами близких пар нет. Функции read_points и center_sum — из основных подтем:

points = read_points("example27.txt", has_header=True)clusters = split_clusters(points, eps=1.5)print("Количество групп:", len(clusters))for cluster in clusters:    print(len(cluster), center_sum(cluster))
Количество групп: 2 3 (11.0, 1.0) 5 (1.0, 1.0)

Центры те же, что при границе x = 5. Первой получилась правая группа: pop() начал с последней точки файла. Требования к порогу:

  1. Внутри каждого требуемого кластера точки соединяются цепочками шагов длиной не больше ε.
  2. Между разными требуемыми кластерами нет ни одной пары точек на расстоянии не больше ε.

Обрати внимание: первое требование относится к цепочкам, а не к наибольшему расстоянию между любыми двумя точками внутри кластера. Вытянутый кластер может иметь длину 20 единиц, но состоять из точек с промежутками по 0,2. Для его связности вовсе не нужен порог 20.

Если порог слишком маленький, одна настоящая группа может распасться на несколько. Если слишком большой, отдельные группы могут соединиться.

При этом даже правильное количество получившихся групп ещё ничего не доказывает. Например, один настоящий кластер распался на два, а два других слились в один. Общее количество осталось прежним, но распределение неверно.

После автоматического разделения полезно вывести размеры и проверить несколько близких значений порога. В файле 27-6.txt две дуги вложены одна в другую: вертикальная или горизонтальная граница здесь не помогает, а цепочки — помогают:

Файл 27-6.txt: верхняя дуга заходит концами в нижнюю, и любая вертикальная или горизонтальная прямая режет хотя бы одну из них. Цепочки с порогом 0,5 разделяют дуги: кружки и квадраты — две группы из 140 и 120 точек.
points = read_points("27-6.txt") for eps in [0.3, 0.5, 1.0, 2.0]:    clusters = split_clusters(points, eps)     sizes = []     for cluster in clusters:        sizes.append(len(cluster))     print(eps, len(sizes), sorted(sizes, reverse=True)[:3])
0.3 6 [139, 63, 33] 0.5 2 [140, 120] 1.0 2 [140, 120] 2.0 2 [140, 120]

При пороге 0,3 одна дуга распалась на куски; от 0,5 до 2 разделение одно и то же — две дуги из 140 и 120 точек.

Если в некотором диапазоне порогов сохраняется одно и то же разумное разделение, это полезный признак устойчивости. Но он не заменяет проверку геометрии и требований условия.

Секторы файла 27-5.txt показывают обе ошибки порога. У вершины C точки разных секторов сходятся, а на дальнем краю сектора точки редкие:

points = read_points("27-5.txt") for eps in [1.2, 3, 5]:    clusters = split_clusters(points, eps)    print(eps, sorted([len(c) for c in clusters], reverse=True))
1.2 [83, 76, 63, 3, 3] 3 [83, 76, 69] 5 [228]

При 1,2 третий сектор из 69 точек распался на куски 63, 3 и 3; при 5 все секторы слились через вершину; при 3 разделение верное. Угол направления из подтемы 2 разделяет эти точки без подбора порога.

Иногда единого подходящего порога вообще нет. Например, внутри одного разреженного кластера промежутки больше, чем расстояние между двумя другими кластерами. Тогда увеличение порога, необходимое для соединения первого, уже склеит два других. Поэтому разделение по соседям — важный инструмент, но не универсальная замена анализу условия.

Также не следует смешивать три разных понятия: радиус круга, упомянутого в условии; расстояние от вычисленного центра до самой далёкой точки; порог соседства ε. Это могут быть совершенно разные числа.

Маленькие группы — ещё не аномалии

Маленькие группы после автоматического разделения — ещё не обязательно аномалии. Допустим, после разделения получились группы размеров 2400, 3100, 2800, 1, 2. Если по условию должно быть три содержательных кластера и три аномальные точки, последние две группы могут оказаться аномалиями. Но важно проверить:

  • где они находятся;
  • почему их нужно исключить;
  • действительно ли исключается три точки;
  • не является ли маленькая группа допустимым кластером.

Число аномальных точек и число маленьких групп — не одно и то же. Три аномалии могут образовывать три одиночные группы, одну группу из трёх точек или группы размеров 1 и 2. Нельзя без обоснования писать:

# Нельзя считать это универсальным правилом!if len(cluster) > 10:    ...

В одной задаче это может сработать, в другой удалит настоящий кластер.

Есть ещё проблема: аномальная точка может оказаться близко к настоящему кластеру и присоединиться к нему при обработке по порогу. Несколько аномалий могут даже образовать «мостик» между группами. Поэтому автоматическое выделение маленьких компонент не решает любую задачу с шумом.

Возможно, ты встретишь название DBSCAN. Это метод, который дополнительно учитывает плотность: важно не только расстояние до соседей, но и их количество. У него есть параметры eps и min_samples. Описанное выше обычное объединение по цепочкам не следует без пояснений считать полноценным алгоритмом фильтрации шума.

Для понимания заданий сначала достаточно уверенно владеть координатными границами и компонентами связности.

Минимакс

Как найти центр, если в условии — наименьшее расстояние до самой далёкой точки?

Для каждого кандидата найти самое большое расстояние до точек кластера и выбрать кандидата с наименьшим таким максимумом. Такой принцип называют минимаксом: минимум среди максимумов. Сравним его с суммой расстояний на точках (0; 0), (1; 0), (2; 0), (5; 0) и (10; 0).

КандидатСумма расстоянийСамое большое расстояние
(0, 0)1810
(1, 0)159
(2, 0)148
(5, 0)175
(10, 0)3210

По сумме расстояний центр — (2, 0). По минимальному максимальному расстоянию — (5, 0). Это разные задачи с разными ответами.

from math import dist  def center_minimax(cluster):    best_point = cluster[0]    best_radius = float("inf")    for candidate in cluster:        radius = 0.0        for point in cluster:            d = dist(candidate, point)            if d > radius:                radius = d        if radius < best_radius:            best_radius = radius            best_point = candidate    return best_point, best_radius
line = [(0, 0), (1, 0), (2, 0), (5, 0), (10, 0)] center, radius = center_minimax(line)print(center, radius)print(center_sum(line))
(5, 0) 5.0 (2, 0)

Функция возвращает два значения: center — выбранную точку, radius — расстояние от неё до самой далёкой точки. Такое определение встречается в задачах подборки, где кластеры — круги, а ответ — средний радиус.

Если центр по условию обязан быть точкой кластера, нельзя свободно поставить его между точками. Например, для двух точек (0, 0) и (10, 0) геометрическая середина (5, 0) покрывает их кругом радиуса 5. Но если разрешено выбирать центр только из этих двух точек, минимальное максимальное расстояние равно 10. Ограничение на допустимые центры принципиально меняет задачу.

Антицентр

Что значат в условии «антицентр» и «антипод»?

То, что написано в определении: по одному слову алгоритм не угадать. Переведи определение в два ответа — что считаем для кандидата и как выбираем. Выбери определение:

минимум суммы расстояний
сумму расстояний до точек кластеранаименьшую сумму
максимум суммы расстояний
ту же суммунаибольшую сумму
минимум максимального расстояния
самое большое расстояние до точки кластеранаименьший максимум
самая далёкая точка от уже найденного центра
одно расстояние до этого центранаибольшее расстояние

Последние две части каждой строки помогают буквально перевести предложение на язык циклов.

Если «антицентр» определён как точка с максимальной суммой расстояний, в функции center_sum меняются начальное значение best_sum = -float("inf") и сравнение if total > best_sum:. Всё остальное устройство сохраняется. В подборках так ищут антицентры самых крупных кластеров: сначала sorted(clusters, key=len), затем антицентр каждого из нужных.

Но найти точку с максимальной суммой расстояний и найти точку, самую далёкую от центра, — это не одно и то же вычисление. Для самой далёкой от уже известного центра:

center = (2, 0)farthest_point = line[0]max_distance = -1.0 for point in line:    d = dist(center, point)     if d > max_distance:        max_distance = d        farthest_point = point print(farthest_point, max_distance)
(10, 0) 8.0

Здесь нет суммирования: каждой точке соответствует только одно расстояние.

Сумма квадратов

Как найти центр по наименьшей сумме квадратов расстояний?

Это точка кластера, ближайшая к средней точке: сумма квадратов растёт с удалением от средней точки, как парабола. На точках 0, 1, 2, 3, 100 сумму квадратов выиграл кандидат 3 — ближайший к средней точке 21,2 (подтема 3). Нарисуем сумму квадратов T(x) для любого x на прямой:

Сумма квадратов расстояний от точки x до точек 0, 1, 2, 3, 100 — парабола с вершиной в средней точке 21,2. Кандидаты лежат на ней; ниже всех тот, что ближе к вершине, — (3; 0).

Если разрешена любая точка плоскости, минимум достигается в самой средней точке. Если разрешены только точки исходного кластера, нужно выбрать ближайшую из них.

Обоснование · необязательноПочему сумма квадратов зависит только от расстояния до средней точки

Пусть в кластере m точек: (x₁; y₁), …, (xₘ; yₘ). Проверяем кандидата P = (x; y). Нужная сумма:

T(P) = Σᵢ ((x − xᵢ)² + (y − yᵢ)²).

Знак Σ означает суммирование. Индекс i последовательно принимает значения от 1 до m. То есть формула — сокращённая запись ((x − x₁)² + (y − y₁)²) + … + ((x − xₘ)² + (y − yₘ)²).

Обозначим средние координаты: x̄ = (x₁ + … + xₘ) / m, ȳ = (y₁ + … + yₘ) / m.

После раскрытия скобок и группировки слагаемых получается:

T(P) = m · ((x − x̄)² + (y − ȳ)²) + Σᵢ ((xᵢ − x̄)² + (yᵢ − ȳ)²).

Посмотрим на смысл двух частей. Первая, m · ((x − x̄)² + (y − ȳ)²), — количество точек, умноженное на квадрат расстояния от кандидата до средней точки. Вторая часть зависит только от исходных точек. При смене кандидата она не изменяется.

Поэтому при сравнении кандидатов достаточно сравнивать первую часть. А множитель m одинаков для всех.

Чтобы увидеть источник преобразования, достаточно раскрыть одну часть:

Σᵢ (x − xᵢ)² = m · x² − 2x · Σᵢ xᵢ + Σᵢ xᵢ².

Поскольку Σᵢ xᵢ = m · x̄, получаем квадрат m · (x − x̄)² и остаток, не зависящий от x. С координатой y происходит то же самое.

def center_squared(cluster):    sum_x = 0.0    sum_y = 0.0    for x, y in cluster:        sum_x += x        sum_y += y    mean_x = sum_x / len(cluster)    mean_y = sum_y / len(cluster)    best_point = cluster[0]    best_value = float("inf")    for point in cluster:        x, y = point        value = (x - mean_x) ** 2 + (y - mean_y) ** 2        if value < best_value:            best_value = value            best_point = point    return best_point
cluster = [(0, 0), (1, 0), (2, 0), (3, 0), (100, 0)]print(center_squared(cluster), center_sum(cluster))
(3, 0) (2, 0)

Здесь два прохода по точкам вместо перебора всех пар, и ответ при этом точный — он следует из формулы. Но только для суммы квадратов обычных расстояний: для суммы самих расстояний или для манхэттенской формулы ближайшая к средней точка может оказаться не центром — пример был в подтеме 3.

Точка наведения

Как выбрать звезду, на которую навести телескоп, чтобы в поле зрения попало больше всего звёзд?

Для каждой звезды-кандидата посчитать, сколько других звёзд ближе радиуса R, и взять кандидата с наибольшим числом, а при равенстве — ближайшего к началу координат. Такая задача есть в подборках: поле зрения — круг радиуса R, звёзды на самой границе не видны, поэтому неравенство строгое.

У четырёх звёзд по два соседа ближе 1,5 — это наибольшее число. Из них к началу координат ближе всех (1; 1): она и есть точка наведения.

Это тот же перебор кандидатов, только для кандидата считают не сумму, а количество, и правил выбора два. Оба правила укладываются в один ключ:

from math import dist stars = [(1, 1), (2, 1), (2, 2), (5, 4), (6.2, 4.4), (5.6, 5.6)]R = 1.5  def visible(c):    return sum(dist(c, s) < R for s in stars) - 1  aim = min(stars, key=lambda c: (-visible(c), dist(c, (0, 0))))print([visible(c) for c in stars])print(aim)
[2, 2, 2, 1, 2, 1] (1, 1)
  • sum(dist(c, s) < R for s in stars) складывает истинные сравнения как единицы. Сама звезда тоже попадает в счёт — расстояние 0 — поэтому вычитаем 1. Так звёзды с одинаковыми координатами посчитаются верно.
  • Ключ — пара: сначала -visible(c) (чем больше видно, тем меньше ключ), при равенстве — расстояние до начала координат. min сравнивает пары по порядку.

Корреляция

Как проверить, что два параметра измерений связаны — растёт один, растёт и другой?

Посчитать коэффициент корреляции Пирсона r: он лежит от −1 до 1, и чем ближе |r| к 1, тем точнее точки ложатся на прямую. r около 0 — облако без наклона, r < 0 — один параметр растёт, а другой убывает. В подборках связь называют сильной, если |r| ≥ 0,9.

r = Σ(xᵢ − x̄)(yᵢ − ȳ) / √(Σ(xᵢ − x̄)² · Σ(yᵢ − ȳ)²), где x̄ и ȳ — средние. Для точек (1; 2), (2; 3), (3; 5), (4; 4), (5; 6): x̄ = 3, ȳ = 4, отклонения x — −2, −1, 0, 1, 2, отклонения y — −2, −1, 1, 0, 2. Сумма произведений 4 + 1 + 0 + 0 + 4 = 9, суммы квадратов 10 и 10, r = 9 / √100 = 0,9:

def pearson(points):    n = len(points)    mx = sum(p[0] for p in points) / n    my = sum(p[1] for p in points) / n    sxy = sum((p[0] - mx) * (p[1] - my) for p in points)    sxx = sum((p[0] - mx) ** 2 for p in points)    syy = sum((p[1] - my) ** 2 for p in points)    return sxy / (sxx * syy) ** 0.5  print(pearson([(1, 2), (2, 3), (3, 5), (4, 4), (5, 6)]))
0.9

В файле 27-9.txt записаны результаты трёх экспериментов вперемешку. На рисунке они разделены пустыми полосами — делим границами и считаем r для каждого:

Три эксперимента файла 27-9.txt. У первого и третьего точки вытянуты вдоль прямой — сильная связь, у среднего облако без наклона.
points = read_points("27-9.txt")groups = [[], [], []]for x, y in points:    if x < 15:        groups[0].append((x, y))    elif x < 35:        groups[1].append((x, y))    else:        groups[2].append((x, y))for g in groups:    print(len(g), round(pearson(g), 3))
60 0.941 50 -0.016 70 0.918

Сильная связь — у первого и третьего экспериментов: в них 60 + 70 = 130 измерений. В Python 3.10 и новее то же число считает statistics.correlation(xs, ys). В подборке эксперименты разделяют цепочками соседей с порогом из условия — функцией split_clusters из начала урока.

Выбросы по квартилям

Как найти выбросы — случайные ошибочные измерения, которые надо убрать перед расчётом?

Методом межквартильного размаха: найти квартили Q1 и Q3, размах IQR = Q3 − Q1 и считать выбросом всё, что дальше 1,5 · IQR от квартилей. Квартили считают так, как сказано в условии. В подборке: Q1 — медиана нижней половины упорядоченных значений, Q3 — медиана верхней; при нечётном количестве срединное значение не входит ни в одну половину.

Значения 3, 9, 10, 10, 11, 12, 12, 13, 14, 29: нижняя половина 3, 9, 10, 10, 11 — Q1 = 10, верхняя 12, 12, 13, 14, 29 — Q3 = 13. IQR = 3, границы 10 − 4,5 = 5,5 и 13 + 4,5 = 17,5:

Прямоугольник — от Q1 до Q3, пунктир — границы 5,5 и 17,5. За ними два выброса: 3 и 29.
def median(v):    n = len(v)    if n % 2:        return v[n // 2]    return (v[n // 2 - 1] + v[n // 2]) / 2  def quartiles(values):    v = sorted(values)    n = len(v)    return median(v[:n // 2]), median(v[(n + 1) // 2:])  data = [12, 3, 10, 14, 11, 29, 9, 12, 13, 10]q1, q3 = quartiles(data)iqr = q3 - q1lo, hi = q1 - 1.5 * iqr, q3 + 1.5 * iqrprint(q1, q3, lo, hi)print([v for v in data if v < lo or v > hi])
10 13 5.5 17.5 [3, 29]

Срез v[:n // 2] — нижняя половина, v[(n + 1) // 2:] — верхняя: при нечётном n срединный элемент пропускается. Если точки двумерные, границы считают отдельно для x и для y, и точка — выброс, когда хотя бы одна её координата за границей.

Почему не statistics.quantiles

Квартили можно считать по-разному. Функция statistics.quantiles(data, n=4) для тех же данных даёт 9,75 и 13,25 — другие границы и, в других данных, другие выбросы. Поэтому квартили пишут по описанию из условия.

Ускорения перебора

Можно ли искать центр быстрее, не рискуя ответом?

Да: считать каждое расстояние один раз и прибавлять его сразу к двум суммам, а кандидата бросать, как только его сумма догнала лучшую. В основном переборе d(A, B) считается дважды — когда кандидат A и когда кандидат B. Но d(A, B) = d(B, A).

Для этого создаём отдельную сумму для каждой точки: totals = [0.0] * len(cluster). Если точек четыре, получится [0.0, 0.0, 0.0, 0.0]. Полная функция:

def center_sum_pairs(cluster):    n = len(cluster)    totals = [0.0] * n    for i in range(n):        for j in range(i + 1, n):            d = dist(cluster[i], cluster[j])            totals[i] += d            totals[j] += d    best_index = totals.index(min(totals))    return cluster[best_index]

range(n) создаёт последовательность номеров 0, 1, 2, …, n − 1. Внутренний цикл начинается с i + 1, поэтому перебираются только пары, в которых второй номер больше первого. Для четырёх точек это (0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3).

Пара (1, 0) не нужна: она уже учтена как (0, 1). Расстояние от точки до самой себя тоже не вычисляется.

Все пары номеров четырёх точек: нужны только 6 клеток над диагональю. Диагональ — расстояние до себя, под ней — те же пары в обратном порядке.

Число различных пар — m(m − 1) / 2, примерно вдвое меньше исходных m². После завершения циклов min(totals) находит наименьшую сумму, — номер её первого появления, и по этому номеру возвращаем исходную точку cluster[best_index]:

print(center_sum_pairs(cluster), center_sum_pairs(line))
(2, 0) (2, 0)

Порядок роста остаётся квадратичным, но вычислений расстояния становится меньше. Таблица всех попарных расстояний не нужна — хватает списка сумм длины m.

Досрочный отказ от кандидата

Второе точное ускорение — досрочно прекращать проверку заведомо проигравшего кандидата. Допустим, для уже проверенной точки сумма равна 500. Для нового кандидата посчитана только часть расстояний, а сумма уже равна 520.

Оставшиеся расстояния неотрицательны. Следовательно, итоговая сумма не станет меньше 520 и не сможет улучшить результат 500. Можно остановить внутренний цикл — завершает ближайший цикл, внутри которого находится, здесь — внутренний. Запусти по шагам на двух точках и посмотри, на каком кандидате срабатывает break:

from math import dist  def center_sum_early(cluster):    best_point = cluster[0]    best_sum = float("inf")    for candidate in cluster:        total = 0.0        for point in cluster:            total += dist(candidate, point)            if total >= best_sum:                break        if total < best_sum:            best_sum = total            best_point = candidate    return best_point  print(center_sum_early([(0, 0), (5, 0)]))
(0, 0)

Первый кандидат (0; 0) даёт сумму 0 + 5 = 5. У второго, (5; 0), уже первое слагаемое 5 не меньше лучшей суммы 5 — внутренний цикл прерывается, второе слагаемое не считается. Равный результат не заменяет найденную точку.

Почему ускорение точное? Потому что мы отбрасываем кандидата только после доказательства, что он уже не может улучшить ответ. Это отличается от предположения «точка выглядит слишком далёкой, поэтому проверять её не будем».

Такое ускорение не меняет худший квадратичный порядок работы и не обязано давать одинаковый выигрыш на любых данных.

А вот проверять только часть точек — ближайшие к средней, каждую десятую, случайные — нельзя: если центр не попал в проверенные, программа его не найдёт.

Итого

  • Цепочки соседейгруппа — всё, что связано шагами не длиннее ε
  • Порогбольше шагов внутри, меньше промежутков между
  • Другие центрыминимакс, антицентр, ближайшая к средней точке
  • Точка наведенияключ (-видимых, до начала координат)
  • Корреляцияr Пирсона; сильная связь — |r| ≥ 0,9
  • Выбросыза пределами Q1 − 1,5 · IQR и Q3 + 1,5 · IQR

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1
Даны точки (0, 0), (0.8, 0), (1.6, 0). Сколько связных групп получится при пороге 0,9 и при пороге 0,7?
ОтветСкрыть ответ
Ответ
При 0,9 — одна: крайние точки связаны цепочкой через среднюю. При 0,7 — три: ни одна пара не проходит порог.
Вопрос 2
Точки (0, 0), (1, 0), (2, 0), (5, 0), (6, 0). Сколько групп при пороге 1, 2,9 и 3?
ОтветСкрыть ответ
Ответ
При 1 и при 2,9 — две группы: 0–2 и 5–6. При 3 — одна: промежуток от 2 до 5 равен 3.
Вопрос 3
Среди точек (0, 0), (2, 0), (3, 0), (7, 0), (12, 0) найди центр по минимаксу и центр по наименьшей сумме расстояний.
ОтветСкрыть ответ
Ответ
По минимаксу — (7, 0): самая далёкая точка на расстоянии 7. По сумме — (3, 0): сумма 17.
Вопрос 4
Значения 2, 4, 5, 5, 6, 7, 8, 30. Найди Q1, Q3, границы выбросов и сами выбросы — квартили как медианы половин.
ОтветСкрыть ответ
Ответ
Q1 = 4,5, Q3 = 7,5, IQR = 3, границы 0 и 12 — выброс один: 30.

Практика

Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.

Наибольший целый порог

Найди наибольший целый порог, при котором три сектора файла 27-5.txt ещё разделяются правильно.

27-5.txtТочки трёх кластеров в секторахТекстовый файл · 4,4 КБ

Введите число.

Центры двух дуг

В файле 27-6.txt записаны точки двух кластеров, разделённых промежутком. Центр кластера — его точка с наименьшей суммой расстояний до остальных точек кластера. Найди Pₓ и P_y — средние арифметические абсцисс и ординат центров кластеров. В ответе запиши целые части произведений Pₓ · 10 000 и P_y · 10 000.

27-6.txtТочки двух кластеров-дугТекстовый файл · 3,7 КБ

Введите два целых числа через пробел.

Радиус двух точек

Кластер из двух точек (0, 0) и (6, 8). Каков минимальный радиус, если центр — одна из точек кластера, и если центр — любая точка плоскости?

Введите два числа через пробел: радиус с центром в точке кластера, затем с центром в любой точке.

Средний радиус по минимаксу

В файле 27-1.txt записаны точки двух кластеров. Центр кластера — его точка, для которой минимально максимальное из расстояний до остальных точек кластера; радиус кластера — это максимальное расстояние. Найди средний радиус двух кластеров. В ответе запиши целую часть произведения среднего радиуса на 10 000.

27-1.txtТочки двух кластеровТекстовый файл · 3,6 КБ

Введите число.

Антицентры крупнейших кластеров

В файле 27-2.txt записаны точки трёх кластеров и три аномалии. Антицентр кластера — его точка с наибольшей суммой расстояний до остальных точек кластера. Найди Pₓ и P_y — средние арифметические абсцисс и ординат антицентров двух самых больших по числу точек кластеров. В ответе запиши целые части произведений |Pₓ| · 10 000 и |P_y| · 10 000.

27-2.txtТочки трёх кластеров и три аномалииТекстовый файл · 11,0 КБ

Введите два числа через пробел в том порядке, в каком их спрашивают.

Антицентр маленького кластера

В файле 27-2.txt записаны точки трёх кластеров и три аномалии. Антицентр кластера — его точка с наибольшей суммой расстояний до остальных точек кластера. Найди сумму абсциссы и ординаты антицентра кластера с наименьшим количеством точек. В ответе запиши целую часть абсолютной величины произведения этой суммы на 10 000.

27-2.txtТочки трёх кластеров и три аномалииТекстовый файл · 11,0 КБ

Введите число.

Точка наведения телескопа

Звёзды: (1; 2), (2; 2), (2; 3), (6; 1), (7; 1), (7; 2), (8; 2), (3; 7), (4; 7), (3; 8), (4; 8). Телескоп наводят на одну из звёзд; в поле зрения попадают звёзды, которые ближе 1,5 к ней (на границе не видны). Выбери звезду, при наведении на которую видно больше всего других звёзд; если таких несколько — ближайшую к началу координат. Найди её координаты.

Введите два числа через пробел: сначала x, затем y.

Сильно коррелирующие эксперименты

В файле 27-9.txt записаны результаты трёх экспериментов вперемешку: в каждой строке — два числа одного измерения. На диаграмме рассеяния эксперименты разделены пустыми полосами. Связь в эксперименте сильная, если модуль коэффициента корреляции Пирсона его измерений не меньше 0,9. Найди количество измерений в экспериментах с сильной связью и среднее арифметическое их коэффициентов корреляции; в ответе для второго числа запиши целую часть произведения на 100 000.

27-9.txtИзмерения трёх экспериментовТекстовый файл · 2,0 КБ

Введите два числа через пробел в том порядке, в каком их спрашивают.

Выбросы по квартилям

Результаты измерений: 47, 52, 49, 55, 61, 50, 48, 95, 53, 51, 12, 56, 54. Q1 — медиана нижней половины упорядоченных значений, Q3 — медиана верхней; при нечётном количестве срединное значение не входит ни в одну половину. Выброс — значение вне отрезка [Q1 − 1,5 · IQR; Q3 + 1,5 · IQR], где IQR = Q3 − Q1. Сколько выбросов и чему равно наибольшее значение, которое выбросом не считается?

Введите два числа через пробел в том порядке, в каком их спрашивают.