Подтема 3 из 6
Центр кластера
Центр — точка кластера с наименьшей суммой расстояний: перебор кандидатов, почему средняя точка не подходит, своя формула расстояния и взвешенная сумма.
Центр — точка кластера
Какую точку кластера считать его центром?
Центр — одна из точек самого кластера: та, у которой сумма расстояний до остальных точек наименьшая. Найти её можно перебором — проверить каждую точку как кандидата. Кандидат — точка, которую сейчас проверяем: «а что, если центр — она?»
Возьмём кластер из пяти точек на прямой: (0; 0), (1; 0), (2; 0), (3; 0) и (100; 0). На одной горизонтали расстояние — модуль разности x. Для (0; 0) сумма до остальных 1 + 2 + 3 + 100 = 106, для (1; 0) — 1 + 1 + 2 + 99 = 103. Пройди так всех пятерых:
- Центр — одна из точек кластера. Проверим каждую по очереди, как кандидата.
- Кандидат (0; 0): 1 + 2 + 3 + 100 = 106. Пока это лучшая сумма.
- Кандидат (1; 0): 1 + 1 + 2 + 99 = 103. Пока это лучшая сумма.
- Кандидат (2; 0): 2 + 1 + 1 + 98 = 102. Пока это лучшая сумма.
- Кандидат (3; 0): 3 + 2 + 1 + 97 = 103. Хуже, чем 102.
- Кандидат (100; 0): 100 + 99 + 98 + 97 = 394. Хуже, чем 102.
- Все кандидаты проверены: центр (2; 0), наименьшая сумма 102.
Центр — (2; 0) с суммой 102. У соседней (3; 0) сумма 103, у далёкой (100; 0) — 394.
Центр, центроид, медоид и среднее расстояние
В условиях такую точку называют «центром» или «центроидом», в математике — медоидом. Решай по определению из условия, а не по слову.
Если в условии минимизируется среднее расстояние до остальных точек, центр тот же: среднее — сумма, делённая на m − 1, а делитель одинаков для всех кандидатов кластера. Но если просят вывести само среднее, деление нужно: для нашего центра 102 / 4 = 25,5 до остальных точек и 102 / 5 = 20,4 по всем пяти, включая сам центр.
Программа
Как записать перебор кандидатов программой?
Двумя вложенными циклами: внешний перебирает кандидатов, внутренний складывает расстояния от кандидата до всех точек кластера. Лучшую сумму и её кандидата запоминают.
from math import dist def center_sum(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: best_sum = total best_point = candidate return best_point cluster = [(0, 0), (1, 0), (2, 0), (3, 0), (100, 0)]print(center_sum(cluster))float("inf")— бесконечность: любая настоящая сумма меньше, поэтому первый кандидат сразу становится лучшим.total = 0.0стоит внутри внешнего цикла: у каждого кандидата своя сумма. Поставь её до цикла — и суммы разных кандидатов сложатся вместе.total += …— , то же, чтоtotal = total + ….- Сравнивать с лучшей суммой можно только после внутреннего цикла, когда сумма кандидата посчитана целиком.
Функция находит центр наверняка: она проверяет всех кандидатов и для каждого считает ровно ту величину, что стоит в условии. Угадывать центр по рисунку не нужно.
Расстояние до себя, равные суммы и одинаковые точки
Расстояние от кандидата до самого себя равно нулю, поэтому проверка if point != candidate: не нужна: сумма до всех точек равна сумме до остальных. Хуже того, она выбросит и другую запись с теми же координатами — а совпадающие точки удалять нельзя, если условие этого не просит.
При равных суммах сравнение < оставляет первого из равных кандидатов, <= — последнего. Обычно условие гарантирует, что центр единственный.
Сколько работы делает перебор
Для каждого из m кандидатов считаются m расстояний — всего m²:
| Точек в кластере | Вычислений расстояния |
|---|---|
| 100 | 10 000 |
| 1 000 | 1 000 000 |
| 5 000 | 25 000 000 |
Расстояния считают только внутри своего кластера: 9 000 точек в трёх кластерах по 3 000 — это 27 миллионов вычислений, а не 81. В файлах экзамена сотни или тысячи точек, и перебор занимает секунды.
Среднее — не центр
Нельзя ли вместо перебора просто взять среднюю точку кластера?
Нет: средняя точка обычно не совпадает ни с одной точкой кластера, и даже ближайшая к ней точка не обязательно центр. Средняя точка наших пяти — x̄ = (0 + 1 + 2 + 3 + 100) / 5 = 21,2, ȳ = 0: далёкая точка (100; 0) сильно её смещает. Ближайшая к (21,2; 0) точка кластера — (3; 0). Проверим её сумму:
mean = (sum(p[0] for p in cluster) / 5, 0)print(mean) nearest = cluster[0]for p in cluster: if dist(p, mean) < dist(nearest, mean): nearest = p best = center_sum(cluster)print(nearest, sum(dist(nearest, p) for p in cluster))print(best, sum(dist(best, p) for p in cluster))У (3; 0) сумма 103, у настоящего центра (2; 0) — 102. Один пример опровергает сразу оба соблазнительных правила: и «центр — средняя точка», и «центр — ближайшая к средней».
Сумма квадратов
А если ради скорости убрать корень и сравнивать суммы квадратов расстояний?
Нельзя: сумма квадратов — другая величина, и её минимум бывает у другой точки. Сравним кандидатов 2 и 3 из нашего кластера:
for c in [(2, 0), (3, 0)]: plain = sum(dist(c, p) for p in cluster) squares = sum(dist(c, p) ** 2 for p in cluster) print(c, plain, squares)По сумме расстояний лучше кандидат 2, по сумме квадратов — 3: 2² + 1² + 0² + 1² + 98² = 9610, а 3² + 2² + 1² + 0² + 97² = 9423. Квадрат сильнее раздувает большие расстояния, и сократить одно большое выгоднее, чем несколько маленьких.
Сравнивать квадраты можно, когда сравниваешь одно расстояние с порогом (подтема 1), но не суммы. Центр по сумме квадратов встречается в редких задачах — он разобран в дополнительном уроке.
Своя формула расстояния
Что меняется, если в условии своя формула расстояния?
Только функция расстояния — перебор кандидатов остаётся прежним. Добавим в center_sum параметр d — функцию расстояния; по умолчанию это обычная dist:
def center_sum(cluster, d=dist): best_point = cluster[0] best_sum = float("inf") for candidate in cluster: total = 0.0 for point in cluster: total += d(candidate, point) if total < best_sum: best_sum = total best_point = candidate return best_pointНапример, манхэттенское расстояние d(A, B) = |x₁ − x₂| + |y₁ − y₂| — длина пути, если ходить только по горизонтали и вертикали. Для (1; 2) и (4; 6) это 3 + 4 = 7, а не 5. — модуль числа:
def manhattan(a, b): return abs(a[0] - b[0]) + abs(a[1] - b[1]) group = [(0, 3), (3, 0), (1, 6), (1, 0)]print(center_sum(group), center_sum(group, manhattan))Для одних и тех же четырёх точек евклидов центр — (0; 3), а манхэттенский — (1; 0): формула расстояния меняет ответ.
Так устроена и задача темы: «расстояние» между частицами — модуль разности их энергий |E₁ − E₂|, а координаты в выборе центра не участвуют. Функции read_particles, energy и energy_groups — из подтем 1 и 2:
def energy_gap(a, b): return abs(energy(a) - energy(b)) particles = read_particles("27-4.txt")groups = energy_groups(particles, [3.5, 7, 10.5])for g in groups: c = center_sum(g, energy_gap) print(len(g), c[5], round(energy(c), 3))У центров четырёх кластеров энергии 2,028, 5,362, 8,964 и 12,503. Q₂ задачи темы — наибольшая из них; ответ соберём в подтеме 5.
Взвешенная сумма
Как найти центр, если точки неравноценны — например, деревья разной высоты?
Каждое расстояние умножают на вес той точки, до которой его считают; в остальном перебор прежний. Такая задача есть в подборках: деревья свозят к точке сбора, «сложность» доставки дерева — расстояние, умноженное на его высоту, а точка сбора — дерево с наименьшей суммарной сложностью.
Пять деревьев у дороги: (0; 0) высотой 2, (1; 0), (2; 0) и (3; 0) высотой 1 и (8; 0) высотой 4. Сравним обычную и взвешенную суммы каждого кандидата:
| Кандидат | Сумма расстояний | Сумма с весами |
|---|---|---|
| (0; 0) | 14 | 38 |
| (1; 0) | 11 | 33 |
| (2; 0) | 10 | 30 |
| (3; 0) | 11 | 29 |
| (8; 0) | 26 | 34 |
Без весов центр — (2; 0), с весами — (3; 0): высокое дерево (8; 0) тянет точку сбора к себе. В программе меняется одна строка — к расстоянию добавляется множитель:
trees = [(0, 0, 2), (1, 0, 1), (2, 0, 1), (3, 0, 1), (8, 0, 4)] def center_weighted(trees): best, best_sum = None, float("inf") for c in trees: total = 0.0 for t in trees: total += dist(c[:2], t[:2]) * t[2] if total < best_sum: best, best_sum = c, total return best, best_sum print(center_weighted(trees))Множитель — t[2], высота дерева, которое везут, а не кандидата c. Перепутать их — значит решить другую задачу.
Итого
- Центродна из точек кластера, наименьшая сумма расстояний
- Переборвнешний цикл — кандидаты, внутренний — сумма
- totalобнуляется для каждого кандидата
- Средняя точкане центр; ближайшая к ней — тоже не обязательно
- Своя формуламеняется только функция
d, перебор тот же - Весрасстояние × вес точки, до которой считаем
Проверьте себя
Ответь сначала сам, потом открой ответ.
(0, 0), (4, 0), (5, 0), (6, 0), (20, 0) найди центр по наименьшей сумме расстояний. Совпадает ли он с ближайшей к средней точке?ОтветСкрыть ответ
(5, 0): сумма 22 (у (4, 0) и (6, 0) — по 23). Средняя точка 7, ближайшая к ней (6, 0) — не центр.(0, 0), (4, 0), (4, 3), (0, 3), (1, 1) и его сумму расстояний.ОтветСкрыть ответ
(1, 1): сумма ≈ 10,418; у углов суммы от 13,414 до 15,606.total = 0.0 поставили до внешнего цикла. Что тогда сравнивается с best_sum для второго кандидата?ОтветСкрыть ответ
(2, 6), (3, 6), (3, 1), (4, 2) по евклидову и по манхэттенскому расстоянию.ОтветСкрыть ответ
(4, 2) (≈ 10,01), манхэттенское — (3, 6) (сумма 11, у остальных по 13).(0, 0) высотой 1, (2, 0) высотой 1 и (3, 0) высотой 5. Где точка сбора по сумме «расстояние × высота дерева» и где центр без весов?ОтветСкрыть ответ
(3, 0): суммы 17, 7 и 4. Без весов — (2, 0): суммы 5, 3 и 4.Практика
Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.
Центр левого кластера
В файле 27-1.txt записаны точки двух кластеров (граница — из задачи 7 подтемы 3). Центр кластера — его точка с наименьшей суммой расстояний до остальных точек кластера. Найди центр кластера, расположенного левее. В ответе запиши целые части произведений его абсциссы и ординаты на 10 000.
Введите два целых числа через пробел.
Подсказка
Каждую точку кластера проверь как кандидата в центр.
Решение
33054 41399: центр (3,305409; 4,139976).
Решение программой
from math import dist def read_points(filename): points = [] with open(filename, encoding='utf-8-sig') as f: for line in f: parts = line.replace(',', '.').split() points.append((float(parts[0]), float(parts[1]))) return points def center_sum(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: best_sum = total best_point = candidate return best_point left, right = [], []for p in read_points('27-1.txt'): if p[0] < 6: left.append(p) else: right.append(p) c = center_sum(left)print(int(c[0] * 10000), int(c[1] * 10000))Ответ: 33054 41399
Центр по энергиям
В файле 27-4.txt записаны частицы; они делятся на 4 кластера по кинетической энергии (R = 2). Центр кластера — частица с наименьшей суммой модулей разностей энергий с остальными частицами кластера. Найди энергию центра кластера с наименьшими энергиями. В ответе запиши целую часть произведения этой энергии на 10 000.
Введите число.
Подсказка
Расстояние между частицами здесь — модуль разности энергий.
Решение
20278: энергия центра ≈ 2,02787.
Решение программой
parts = []with open('27-4.txt', encoding='utf-8-sig') as f: for line in f: p = line.replace(',', '.').split() parts.append([float(v) for v in p[:5]] + [p[5]]) def energy(p): return p[4] * (p[2] ** 2 + p[3] ** 2) / 2 clusters = [[], [], [], []]for p in parts: k = 0 for bound in [3.5, 7, 10.5]: if energy(p) > bound: k += 1 clusters[k].append(p) def center_energy(cluster): best, best_sum = cluster[0], float('inf') for c in cluster: total = 0.0 for p in cluster: total += abs(energy(c) - energy(p)) if total < best_sum: best, best_sum = c, total return best print(int(energy(center_energy(clusters[0])) * 10000))Ответ: 20278
Точка сбора деревьев
На участке растут семь деревьев (x, y, высота): (2, 3, 1), (5, 1, 1), (4, 6, 1), (8, 2, 1), (6, 5, 1), (3, 8, 3), (9, 7, 1). Сложность доставки дерева — расстояние от него до точки сбора, умноженное на его высоту. Точка сбора — одно из деревьев, для которого суммарная сложность доставки остальных деревьев наименьшая. Найди координаты точки сбора.
Введите два числа через пробел: сначала x, затем y.
Подсказка
Множитель — высота того дерева, которое везут, а не кандидата.
Решение
Без высот центр был бы (6; 5); дерево высотой 3 в (3; 8) сдвигает точку сбора к (4; 6).
Решение программой
from math import dist trees = [(2, 3, 1), (5, 1, 1), (4, 6, 1), (8, 2, 1), (6, 5, 1), (3, 8, 3), (9, 7, 1)] def cost(c): return sum(dist(c[:2], t[:2]) * t[2] for t in trees) best = min(trees, key=cost)print(best[0], best[1])Ответ: 4 6