Переход от одного набора точек в трехмерном пространстве к другому набору точек с минимально возможным накопительным расстоянием - PullRequest
0 голосов
/ 15 февраля 2019

У нас есть 2 списка (черный и красный), каждый из которых содержит несколько точек в трехмерном пространстве.Мы должны переместить каждую черную точку в красную точку и сделать это таким образом, чтобы общее расстояние, на которое можно было сделать ходы, было наименьшим.Списки могут быть разных размеров.

Простое правильное решение в 2D-пространстве: Correct Example

Неправильное решение: Incorrect solution


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

Пример разделения: Splitting example

Пример стека: Stacking example


Наша лучшая попытка решить эту проблему состоит из следующих общих шагов:

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

  2. Повторяйте шаг 1 до тех пор, пока все черные точки не будут совмещены.

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

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

Некоторый код C #:

private void SaveFrames(List<List<Vector3>> frameList) {
        List<Dictionary<Vector3, List<Vector3>>> resultingPairs = new List<Dictionary<Vector3, List<Vector3>>>();
        for (int iFrame = 0; iFrame < frameList.Count+1; iFrame++) {
            List<Vector3> currentFrame = frameList[iFrame % frameList.Count];
            List<Vector3> nextFrame = frameList[(iFrame + 1) % frameList.Count];

            int maxIterations = Mathf.Min(currentFrame.Count, nextFrame.Count);
            Dictionary<Vector3, List<Vector3>> pairs = new Dictionary<Vector3, List<Vector3>>();
            HashSet<Vector3> takenRed = new HashSet<Vector3>();
            HashSet<Vector3> takenBlack = new HashSet<Vector3>();
            HashSet<Vector3> takenDestination = new HashSet<Vector3>();
            bool moreRed = currentFrame.Count < nextFrame.Count;

            if (moreRed) {
                for (int i = 0; i < maxIterations; i++) {
                    // Find furthest black point from any red point
                    float distance = 0;
                    Vector3 furthestBlack = Vector3.zero;
                    foreach (Vector3 black in currentFrame) {
                        if (takenBlack.Contains(black)) continue;
                        foreach (var red in nextFrame) {
                            if (Vector3.Distance(black, red) > distance) {
                                distance = Vector3.Distance(black, red);
                                furthestBlack = black;
                            }
                        }
                    }

                    // Find the closest red point to the furthest black point
                    distance = float.MaxValue;
                    Vector3 closestRed = Vector3.zero;
                    foreach (var red in nextFrame) {
                        if (takenRed.Contains(red)) continue;
                        if (Vector3.Distance(furthestBlack, red) < distance) {
                            distance = Vector3.Distance(furthestBlack, red);
                            closestRed = red;
                        }
                    }

                    if (!pairs.ContainsKey(furthestBlack)) {
                        pairs[furthestBlack] = new List<Vector3>();
                    }

                    if (!takenDestination.Contains(closestRed)) {
                        pairs[furthestBlack].Add(closestRed);
                        takenBlack.Add(furthestBlack);
                        takenRed.Add(closestRed);
                        takenDestination.Add(closestRed);
                    }

//                    Debug.Log("Pair: " + furthestBlack.ToString() + " to " + closestRed.ToString());
                }
            } else {
                for (int i = 0; i < maxIterations; i++) {
                    // Find furthest red point from any black point
                    float distance = 0;
                    Vector3 furthestRed = Vector3.zero;
                    foreach (Vector3 red in nextFrame) {
                        if (takenRed.Contains(red)) continue;
                        foreach (Vector3 black in currentFrame) {
                            if (Vector3.Distance(black, red) > distance) {
                                distance = Vector3.Distance(black, red);
                                furthestRed = red;
                            }
                        }
                    }

                    // Find the closest black point to the furthest red point
                    distance = float.MaxValue;
                    Vector3 closestBlack = Vector3.zero;
                    foreach (var black in currentFrame) {
                        if (takenBlack.Contains(black)) continue;
                        if (Vector3.Distance(furthestRed, black) < distance) {
                            distance = Vector3.Distance(furthestRed, black);
                            closestBlack = black;
                        }
                    }

                    if (!pairs.ContainsKey(closestBlack)) {
                        pairs[closestBlack] = new List<Vector3>();
                    }

                    if (!takenDestination.Contains(furthestRed)) {
                        pairs[closestBlack].Add(furthestRed);
                        takenBlack.Add(closestBlack);
                        takenRed.Add(furthestRed);
                        takenDestination.Add(furthestRed);
                    }

//                    Debug.Log("Pair: " + closestBlack.ToString() + " to " + furthestRed.ToString());
                }
            }




            if (currentFrame.Count < nextFrame.Count) {
                // For every nextFrame[i], find the closest black point and pair it.
                for (int i = currentFrame.Count; i < nextFrame.Count; i++) {
                    float distance = float.MaxValue;
                    Vector3 closestBlack = Vector3.zero;
                    foreach (var black in currentFrame) {
                        if (Vector3.Distance(nextFrame[i], black) < distance) {
                            distance = Vector3.Distance(nextFrame[i], black);
                            closestBlack = black;
                        }
                    }

                    if (!pairs.ContainsKey(closestBlack)) {
                        pairs[closestBlack] = new List<Vector3>();
                    }

                    if (!takenDestination.Contains(nextFrame[i])) {
                        pairs[closestBlack].Add(nextFrame[i]);
                        takenDestination.Add(nextFrame[i]);
                    }

//                    Debug.Log("Pair: " + closestBlack.ToString() + " to " + nextFrame[i].ToString());
                }
            }


            if (currentFrame.Count > nextFrame.Count) {
                // For every currentFrame[i], find the closest red point and pair it.
                for (int i = nextFrame.Count; i < currentFrame.Count; i++) {
                    float distance = float.MaxValue;
                    Vector3 closestRed = Vector3.zero;
                    foreach (var red in nextFrame) {
                        if (Vector3.Distance(currentFrame[i], red) < distance) {
                            distance = Vector3.Distance(currentFrame[i], red);
                            closestRed = red;
                        }
                    }

                    if (!pairs.ContainsKey(currentFrame[i])) {
                        pairs[currentFrame[i]] = new List<Vector3>();
                    }

                    if (!takenDestination.Contains(closestRed)) {
                        pairs[currentFrame[i]].Add(closestRed);
                        takenDestination.Add(closestRed);
                    }

//                    Debug.Log("Pair: " + currentFrame[i].ToString() + " to " + closestRed.ToString());
                }
            }
            resultingPairs.Add(pairs);
        }
}

Этот метод работает для простых фигуркак кубики.enter image description here

Однако он начинает действовать, когда позиции куба пересекаются в трехмерном пространстве от одного набора точек к другому.

enter image description here

И это делает еще более забавные вещи с более сложными точками: enter image description here

Я не совсем уверен, почему это сломается, и я не мог придуматьпростой 2D-пример, где этот подход идет не так, как надо.

Мы испробовали 3 различных метода в течение 3 очень долгих дней и, похоже, не можем найти решение этой, казалось бы, простой проблемы.

Ответы [ 2 ]

0 голосов
/ 15 февраля 2019

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

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

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

0 голосов
/ 15 февраля 2019

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

Если вы хотите что-то менее волнующее, подумайте о моделировании проблемы как о проблеме асимметричного коммивояжера.Соедините каждую черную точку с каждой красной точкой с направленным краем веса, пропорциональным ее евклидову расстоянию между ними.Затем подключите каждую красную точку к каждой черной точке с направленным краем веса 0. Затем решите с помощью существующего асимметричного решателя TSP, а затем добавьте дополнительные узлы +, как обычно, при необходимости.Однако обратите внимание, что это приведет к удалению большого количества полезной информации (например, нас не особенно волнует, к какому черному узлу мы подключаемся в следующий раз), в обмен на возможность использовать существующее программное обеспечение с проверенной и проверенной эвристикой и оптимизацией.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...