Статья Автор: Деникина Н.В., Деникин А.В.

Кластеризация звёзд

Анализ данных · Блок 27ЕГЭ 2026
SilverTests · Подготовка к ЕГЭКластеризация звёзд · полный курс
Кластеризация звёзд

Задание 27 ЕГЭ-2026 по информатике · от первой диаграммы до досрочного 2026 с параметрами звёзд


1Что просят сделать

На плоскости даны точки-звёзды. Их надо разбить на N непересекающихся кластеров (каждый помещается в прямоугольник H×W), для каждого найти центр-медоид и посчитать какую-то функцию от центров.

Файл Кластеров Размер Что даёт
A 2 ≤ 1000 точек 1 балл
B (Б) 3 ≤ 10 000 точек +1 балл
d(A, B) = √((x₂−x₁)² + (y₂−y₁)²)
евклидово расстояние между точками

Медоид — ключевой термин

Медоид кластера — это одна из точек кластера, у которой сумма расстояний до всех остальных минимальна.

Это НЕ среднее координат! Медоид всегда совпадает с одной из исходных звёзд. В Python: min(c, key=lambda p: sum(dist(p, q) for q in c)).

2Главный приём: глаз + Excel + Python

На станции КЕГЭ установлен LibreOffice Calc — это разрешённый инструмент. Кластеризацию делает не алгоритм, а твой глаз по точечной диаграмме. Python нужен только для медоида и подсчёта ответа.

ШАГ 1
Excel
Открыть файл, построить точечную диаграмму
ШАГ 2
Разделение
Глазами увидеть облака. Снять границы (x<5, y>3)
ШАГ 3
Python
Фильтр по условию, медоид каждого кластера
ШАГ 4
Формат
Считаем, что просит условие, выводим строго

3Шаг 1 в коде: как прочитать файл

Файл выглядит просто — в каждой строке координаты одной звезды через пробел, десятичный разделитель — запятая:

27_A.txt2,73 4,15
3,18 4,72
2,91 3,88
...

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

развёрнутоpts = []                                # пустой список точек
with open('27_A.txt', encoding='utf-8') as f:
    for line in f:                     # шаг 1: читаем по строкам
        line = line.strip()             # шаг 2: убираем переносы '\n'
        if not line:                    #   и пропускаем пустые строки
            continue
        line = line.replace(',', '.')    # шаг 3: запятая → точка
        parts = line.split()            # шаг 4: '2.73 4.15' → ['2.73', '4.15']
        x = float(parts[0])            # шаг 5: строка → число
        y = float(parts[1])
        pts.append((x, y))              # кладём кортеж (x, y) в список

А теперь то же самое одной строкой через list comprehension. Идея ровно та же, просто записана компактнее:

компактноwith open('27_A.txt', encoding='utf-8') as f:
    pts = [tuple(map(float, l.replace(',', '.').split()))
           for l in f]

Что делает каждая часть компактной записи

Кусочек Что делает
l.replace(',', '.') заменяет запятые на точки в строке l
.split() делит строку по пробелам: '2.73 4.15' → ['2.73', '4.15']
map(float, ...) превращает каждую строку списка в число: ['2.73', '4.15'] → [2.73, 4.15]
tuple(...) склеивает в кортеж: (2.73, 4.15)
[... for l in f] повторяет это для каждой строки файла
if l.strip() отбрасывает пустые строки в конце файла

А если в файле 3 столбца? (досрочный 2026)

В досрочном 2026 в каждой строке три значения: x y type. Тогда чтение совсем чуть-чуть длиннее:

3 столбцаstars = []
with open('27_A.txt', encoding='utf-8') as f:
    for line in f:
        line = line.strip().replace(',', '.')
        if not line: continue
        x, y, type_s = line.split()      # распаковка в 3 переменные
        stars.append((float(x), float(y), type_s))
Главное правило: сразу после чтения распечатай первые 3 точки — print(pts[:3]). Если видишь правильные пары чисел — значит, всё прочиталось. Если ValueError — забыл replace(',', '.').

4Интерактив: файл A (2 кластера, деление по X)

Слева — таблица координат (как в файле 27_A.txt). Справа — диаграмма. Двигай ползунок — точки в реальном времени перекрашиваются. Включи кнопку «медоиды» — увидишь центры кластеров.

Файл A — 2 кластераSIM · LIVE
 
Граница x =5.0
 
 

Попробуй: поставь границу на x = 1.5 — почти все точки в одном кластере. Правильное разделение — в районе x ≈ 5, где между облаками пустой зазор.


5Интерактив: файл Б (3 кластера, две границы)

Для файла Б нужно три кластера — значит, две разделяющие прямые. Двигай оба ползунка.

Файл Б — 3 кластераSIM · LIVE
 
Граница 14.0
Граница 28.0
 
 

6Медоид: разбираем функцию по косточкам

В интерактивах ты видел кнопку «медоиды» — и центры кластеров действительно оказывались на одной из точек. Сейчас разберём, какой код это делает. Вот компактная запись функции:

компактноdef medoid(c):
    return min(c, key=lambda p: sum(dist(p, q) for q in c))

В одной строке тут спрятано четыре идеи: min, key, lambda и сумма расстояний. Разберём по очереди.

Что значит каждый кусок

Кусочек Что делает
c список точек кластера, для которого ищем центр
min(c, key=...) находит точку из c, у которой значение функции key минимально
lambda p: ... безымянная функция: «дай мне точку p, я верну тебе число»
sum(dist(p, q) for q in c) складывает dist(p, q1) + dist(p, q2) + ... + dist(p, qN)

То же самое через обычный цикл

Если min, key и lambda кажутся магией — вот тот же алгоритм через обычный цикл, без single-line магии. Делает ровно то же самое:

через циклdef medoid(c):
    best = c[0]                # лучшая точка (пока — первая)
    best_sum = 10**18           # минимальная сумма (пока — «бесконечность»)

    for p in c:               # перебираем все точки кластера
        s = 0                  # сумма расстояний от p до остальных
        for q in c:
            s = s + dist(p, q)
        
        if s < best_sum:       # нашли точку с меньшей суммой —
            best_sum = s       #   запомнили её
            best = p

    return best             # точка с минимальной суммой расстояний

Как это работает на примере

Пусть кластер из 4 точек: A=(0,0), B=(1,0), C=(2,0), D=(3,0). Считаем суммы расстояний от каждой точки до остальных:

Точка Расстояния до остальных Сумма
A (0,0) 1 + 2 + 3 6
B (1,0) 1 + 1 + 2 4 — минимум!
C (2,0) 2 + 1 + 1 4 — тоже минимум
D (3,0) 3 + 2 + 1 6

Видим: точки B и C «равноправно центральные». min вернёт ту, которая встретилась первой. В реальной задаче ФИПИ гарантирует единственность медоида — такого равенства не будет.

Какой вариант писать на ЕГЭ? Оба работают одинаково. Если уверен в lambda и min — пиши компактно (3 строки). Если боишься запутаться — пиши развёрнуто через цикл (12 строк). На скорость не влияет, на балл тоже.

7Когда по X не работает: выбор направления

Облака могут лежать не только слева-справа. Тогда нужно выбрать правильное направление разделителя. В интерактиве ниже: сверху выбираешь расположение данных, снизу — направление. Только одна комбинация работает для каждого расположения.

Выбор направления разделителяSIM · SWITCHABLE
Расположение:
Делю по оси:
Граница x =5.0
 
 
 
Вижу на диаграмме Какое условие пишу Тип зазора
Облака слева ↔ справа if p[0] < A: c1 else: c2 вертикальный (по x)
Облака сверху ↔ снизу if p[1] < B: c1 else: c2 горизонтальный (по y)
Облака по диагонали if p[0] < A and p[1] > B составное условие
Облако внутри круга if (x-x₀)**2+(y-y₀)**2 < r**2 облако-кольцо
Облака под наклоном if 2*x + 3*y < 12 диагональная линия

8L-образное деление: 3 кластера «треугольником»

Когда облака стоят треугольником (одно сверху, два внизу), нужны две границы: одна горизонтальная для верхнего, одна вертикальная для нижних. Классическая L-образная конфигурация.

паттернif p[1] > 5:                        # y > 5 — верхнее облако
    c1.append(p)
elif p[0] < 5:                      # из оставшихся: x < 5 — левое нижнее
    c2.append(p)
else:                                # всё прочее — правое нижнее
    c3.append(p)
3 кластера — L-образная границаSIM · 2D-SPLIT
Верх y =5.0
Низ x =5.0
 
 
 

9Выбросы — убираем глазами

В части вариантов (ЕГЭ-2025, резервы) среди обычных точек прячутся выбросы — одинокие звёзды далеко от любого облака. На диаграмме они сразу заметны. Кликни по точке (или строке в таблице) — пометишь её как выброс. Она перестанет участвовать в медоиде.

2 кластера + выбросы (клик — пометить/снять)SIM · CLICKABLE
 
Граница x =5.0
 
 

Кнопка «подсветить выбросы» сама находит точки с ближайшим соседом дальше 2 единиц — именно это видит глаз.


10Досрочный 2026: характеристики звёзд

В досрочном ЕГЭ-2026 (7 апреля 2026) в задание 27 добавили третий столбец — характеристику звезды, записанную слитно: цвет (буква) + светимость (цифра) + размер (римская цифра).

пример строки2,73 4,15 G2II
 |    |   |||
 x    y   цвет свет. размер
               G — белый, 2 — светимости 2, II — карлик
               # итого: белый карлик светимости 2

Таблица обозначений

Цвет (1 буква) Размер (римская)
G белый I бланкет
J зелёный II карлик
L синий III гигант
N оранжевый IV сверхгигант
Y красный V мега-гигант
S голубой VI супер-гигант
Z жёлтый VII пухляк
Как разобрать характеристику в Python:
x, y, type_s = line.replace(',', '.').split()
x, y = float(x), float(y)
color = type_s[0]      # 1-я буква — цвет, напр. 'G'
bright = type_s[1]     # 2-й символ — цифра, напр. '2'
size = type_s[2:]      # всё после — размер, напр. 'II'
В задаче часто спрашивают про цвет + размер: 'GI' (белый бланет), 'NII' (оранжевый карлик), 'YIII' (красный гигант), 'ZIV' (жёлтый сверхгигант).

Легенда цветов

G — белый J — зелёный L — синий N — оранжевый Y — красный S — голубой Z — жёлтый

Радиус точки отражает «размер» звезды: I (бланкет) — маленький, VII (пухляк) — большой.

Файл A досрочного 2026 (звёзды разных типов)SIM · PARAMS
 
Граница y =5.0
Цвет+Размер:
 
 

Выбранный тип звёзд подсвечивается золотым контуром. При включении медоидов от центра большого кластера к подсвеченным звёздам тянутся золотые пунктиры; в статистике появляются A1 и A2.


11Полные коды решений

Базовая задача: средние координат центров (досрочный 2025)

базовыйfrom math import dist

with open('27_A.txt', encoding='utf-8') as f:
    pts = [tuple(map(float, l.replace(',', '.').split()))
           for l in f if l.strip()]

c1 = [p for p in pts if p[0] < 5]   # условие с диаграммы
c2 = [p for p in pts if p[0] >= 5]

def medoid(c):
    return min(c, key=lambda p: sum(dist(p, q) for q in c))

cen1, cen2 = medoid(c1), medoid(c2)
Px = (cen1[0] + cen2[0]) / 2
Py = (cen1[1] + cen2[1]) / 2
print(abs(int(Px * 10000)), abs(int(Py * 10000)))
# Эталон файла A досрочного 2025: 167990 73043

Досрочный 2026: звёзды с параметрами (файл A)

досрочный'26from math import dist

stars = []
with open('27_A.txt', encoding='utf-8') as f:
    for line in f:
        line = line.strip().replace(',', '.')
        if not line: continue
        x, y, type_s = line.split()
        stars.append((float(x), float(y),
                      type_s[0], type_s[1], type_s[2:]))

c1 = [s for s in stars if s[1] > 10]    # верхнее облако
c2 = [s for s in stars if s[1] <= 10]   # нижнее облако

big = max([c1, c2], key=len)               # самый большой кластер

def medoid(c):
    return min(c, key=lambda p: sum(dist((p[0],p[1]),(q[0],q[1]))
                                  for q in c))

center = medoid(big)

# Расстояния от центра до «белых бланетов» (GI) — со всех точек!
dists = []
for s in stars:
    if s[2] == 'G' and s[4] == 'I':
        if (s[0], s[1]) != (center[0], center[1]):
            dists.append(dist((s[0],s[1]), (center[0],center[1])))

A1, A2 = min(dists), max(dists)
print(int(abs(A1 * 10000)), int(abs(A2 * 10000)))
# Эталон: 5522 75202

12Чек-лист перед отправкой ответа
№ Что проверить
1 Построил точечную диаграмму в Excel/Calc
2 Увидел нужное число облаков (2 для A, 3 для Б)
3 Граница прошла по пустому зазору
4 Выбрал правильное направление разделителя (по x или по y)
5 len(c1) + len(c2) [+ len(c3)] == len(pts)
6 Центр = медоид, не среднее координат
7 Использовал int(), а не round()
8 Применил abs(), если требуется формулой
9 Не перепутал порядок чисел в ответе (Pₓ↔P_y, P1↔P2, Q1↔Q2)
10 Если есть выбросы — отсеял (if/elif без else)
11 Если есть характеристика — разобрал её правильно (size = type_s[2:])
12 Перечитал условие ещё раз

 
 
© Подготовка к ЕГЭ · Информатика · Задание 27 — полный курс
Печать