Выбирайте самые дальние k точек из заданных n точек

У меня есть множество S из n точек в размерности d, для которых я могу рассчитать все попарные расстояния, если это необходимо. Мне нужно выбрать k точек в этом наборе, чтобы сумма их попарных расстояний была максимальной. В других, немного более математических словах, я хочу, чтобы p1,..., pk в S, что сумма (i, j < k) dist (pi, pj) максимальна.

Я знаю, что этот вопрос связан с этим (который в основном такой же, как у меня, но для k = 2) и, возможно, для этот (с "самым дальним", а не "ближайшим" ).

Я не слишком уверен в этом, но, возможно, все возможные решения имеют все свои точки в выпуклой оболочке?

Любая разумная аппроксимация/эвристика в порядке.

Виртуальная бонусная точка № 1 для решения, которое работает для любой функции, которая дает оценку из четырех точек (один из которых может быть квадратным корнем из суммы квадратов расстояний).

Виртуальная бонусная точка №2, если решение легко реализовать в python + numpy/scipy.

Ответ 1

Ваша проблема казалась похожей на взвешенную задачу минимального покрытия вершин (которая является NP-полной). Спасибо @Gareth Rees за пояснения, поясняющие, что я неправильно понимал отношение крышки вершин к набору, который вы ищете. Но вы все еще можете исследовать проблему обложки вершин и литературу, потому что ваша проблема может обсуждаться вместе с ней, поскольку они все еще имеют некоторые функции.

Если вы хотите работать с диаметром вместо взвешенного веса графа, вы можете использовать подход для набора минимального диаметра, который вы связали в своем вопросе. Если ваша текущая дистанционная мера называется d (той, для которой вы хотите, чтобы точки были наиболее удалены друг от друга), просто определите d' = 1/d и разрешите задачу минимального расстояния с помощью d'.

Также может существовать взаимосвязь между каким-либо алгоритмом обработки графа, например normalized cut и подмножество, которое вы ищете. Если ваша дистанционная мера используется в качестве веса графа или сродства между узлами, вы можете изменить существующую функцию резания графика, чтобы она соответствовала вашей целевой функции (поиск группы из k узлов с максимальным суммарным весом).

Это похоже на комбинаторно сложную проблему. Вы можете рассмотреть что-то простое, как симуляционный отжиг. Функция предложения может просто выбрать в точке, которая в настоящее время находится в k -подмножестве, в случайном порядке и случайно заменить ее точкой, не находящейся в настоящее время в k -subset.

Вам нужен хороший график охлаждения для температурного режима и, возможно, потребуется использовать повторный нагрев в зависимости от стоимости. Но такого рода это очень просто программировать. Пока n достаточно мал, вы можете просто случайно выбирать k -подписки и отжигать в сторону k -подписки с очень большим полным расстоянием.

Это только приблизит вас, но даже детерминированные методы, вероятно, будут решать это примерно.

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

all_nodes = np.asarray(...) # Set of nodes
all_dists = np.asarray(...) # Pairwise distances

N = len(all_nodes)
k = 10 # Or however many you want.

def calculate_distance(node_subset, distances):
    # A function you write to determine sum of distances
    # among a particular subset of nodes.    

# Initial random subset of k elements
shuffle = np.random.shuffle(all_nodes) 
current_subset = shuffle[0:k]
current_outsiders = shuffle[k:]

# Simulated annealing parameters.
temp = 100.0
cooling_rate = 0.95
num_iters = 10000

# Simulated annealing loop.
for ii in range(num_iters):
    proposed_subset = current_subset.copy()
    proposed_outsiders =  current_outsiders.copy()

    index_to_swap = np.random.randint(k)
    outsider_to_swap = np.random.randint(N - k)

    tmp = current_subset[index_to_swap]
    proposed_subset[index_to_swap] = current_outsiders[outsider_to_swap]
    proposed_outsiders[outsider_to_swap] = tmp

    potential_change = np.exp((-1.0/temp)*
        calculate_distance(proposed_subset,all_dists)/
        calculate_distance(current_subset, all_dists)) 

    if potential_change > 1 or potential_change >= np.random.rand():
         current_subset = proposed_subset
         current_outsiders = proposed_outsiders

    temp = cooling_rate * temp

Ответ 2

Как насчет этого жадного алгоритма:

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

Позволяет называть наибольшее расстояние между любыми двумя точками D, для каждой добавленной к решению точки мы добавляем по крайней мере D из-за неравенства треугольника. поэтому решение будет по крайней мере (k-1) * D, тогда как любое решение будет иметь (k-1) ^ 2 расстояния, ни один из них не превосходит D, поэтому в худшем случае вы получаете решение k в разы оптимального.

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

Ответ 3

Шаг 1: Предкоммутировать dist (pi, pj) для всех пар pi, pj

Шаг 2. Построим полный граф V = {p1,..., pn} с весами ребер w_ij = dist (pi, pj)

Шаг 3: Решите проблему максимальной клики (MEC) с предельной кромкой.

MEC определенно NP-полно, и он сильно связан с квадратичным программированием. Таким образом, вы можете сосредоточиться на эвристике, если n велико (или даже умеренного размера).

Заметим, что при предварительном вычислении кратно-весов ограничений на функцию расстояния

нет,

Ответ 4

Здесь выполняется рабочая (грубая) реализация для малых n, которая, если ничего не показывает выразительность понятий генератора:

selection = max(
    itertools.combinations(points, k),
    key=lambda candidate: sum(
        dist(p, q) for p, q in itertools.combinations(candidate, 2)
    )
)

Хотя это в конечном итоге вызывает dist много:

(k! / 2! / (k-2)!) * (n! / k! / (n-k)! == n! /(2(k-2)!(n-k)!)