Алгоритм грубой силы для задачи коммивояжера в Java

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

* Перейдите к обновлению внизу, чтобы просмотреть самую последнюю версию кода



ПРОПУСТИТЕ ЭТОТ ПАРАГРАФ, ЕСЛИ ВЫ ЗНАЕТЕ, ЧТО ТАКОЕ ПРОБЛЕМА ПРОДАВЦА-ПУТЕШЕСТВЕННИКА: Если кратко, то ЦП выглядит следующим образом: вы - продавец, который хочет посетить каждый город в регионе (город - это по сути точка на карте). В ограниченной области x и y есть n городов, и каждый город связан с каждым городом (предположим, что это прямая дорога). Вам нужно найти самый короткий маршрут среди городов, который позволит вам посетить каждый город. Один из алгоритмов, который я хочу использовать (и мне нужно будет протестировать другие алгоритмы), - это грубая сила, которая проверяет все возможные маршруты и возвращает самый короткий маршрут. Причина, по которой это не всегда используется, заключается в том, что это требует от нас проверки (n-1)! возможных маршрутов, и это число становится огромным с увеличением n - фактически, имея всего 50 городов, это будет 608281864034267560872252163321295376887552831379210240000000000 маршрутов для проверки.

ПРИНЯТЬ ДЛЯ ВСЕХ ПРИМЕРОВ, ОБЩИХ В ЭТОМ ПОИСКЕ, ЧТО МЫ БУДЕМ ИСПОЛЬЗОВАТЬ ПРОИЗВОЛЬНЫЙ РЕГИОН С 4 ГОРОДАМИ (даже несмотря на то, что алгоритм может обрабатывать n городов. Также нам не важны расстояния - мы хотим поразить все возможные пути грубой силой).

Вот простая картинка, демонстрирующая то, о чем я говорю (4 города - это то, с чего я начинаю, чтобы проверить, правильно ли работает процесс)

изображение карты с городами

Вот алгоритм грубой силы (предположим, что все остальные вызываемые методы работают правильно, потому что они работают):

(см. ниже для получения более подробного объяснения)

[код]

public void BruteForceFindBestRoute(Route r) //Must start r having 1 unflagged city to begin with
    {
        if(!r.allFlagged() && r.route.size() != m.cities.size())
        {
            /*STEP 1 Begin with last unflagged city*/
            City pivot = r.lastCityAdded();
            /*STEP 2: Flag city*/
            pivot.visited = true;
            /*STEP 3: Find cities "NOT IN ROUTE"*/
            ArrayList<City> citiesNotInRoute = new ArrayList<City>();
            for(int i = 0; i<m.cities.size(); i++)
            {
                if(!r.isCityInRoute(m.cities.get(i).name))
                {
                    citiesNotInRoute.add(m.cities.get(i));
                }
            }
            /*STEP 4: Recursively call BruteForceFindBestRoute() using these cities added to the end of our original route*/
            for(int i = 0; i<citiesNotInRoute.size(); i++)
            {
                Route newRoute = r;
                newRoute.addToRoute(citiesNotInRoute.get(i));
                BruteForceFindBestRoute(newRoute);
            }
        }
        /*STEP 5: If the route is full but the last city isn't flagged, then flag it call BruteForceFindBestRoute() again, with the last city flagged*/
        else if(!r.allFlagged() && r.route.size() == m.cities.size())
        {
            if(r.allFlaggedButLast())
            {
                Route x = r;
                x.flagLastCity();
                BruteForceFindBestRoute(x);
            }
        }
        /*STEP 6: If all cities are flagged, the route is full. Check to see if it's the best route.*/
        else if(r.allFlagged())
        {
            if(IsBestRoute(r))
                bestRoute = r;
        }
        else
            System.err.println("Error: somehow all cities got flagged, but the route isn't full");

    }

Вот моя логика: (Примечание: у объекта города есть логическая переменная «флаг», называемая «посетил»)

(если не отмечены все маршруты и не указаны все возможные города)

  • Начнем с маршрута с 1 непомеченным городом.
  • отметьте "последний непомеченный" город (этот город является "пивотом")
  • Найдите каждый город, который НЕ В МАРШРУТЕ R, и добавьте его в новый маршрут.
  • рекурсивно вызвать метод BruteForce на каждом из этих маршрутов.

(если не отмечены все маршруты, но маршрут содержит каждый город)

  • флаг последний город

(иначе ... это означает, что на маршруте отмечены все города и указаны все возможные города)

  • посмотрите, является ли это кратчайшим путем - если это так, сохраните его в глобальной переменной

Это изображение поможет мне объяснить проблему ... Итак, программа правильно спускается по левой стороне. Однако после того, как он дойдет до конца, можно было бы ожидать, что рекурсия вернется к шагу 4, что она и делает. Однако вместо того, чтобы иметь в R город A помеченного флага и город B без флага, а затем рекурсивно вызывать себя на «новом маршруте», содержащем Aflag и B, R теперь включает все 4 города, и все 4 помечены. Это не удается, потому что он снова добавляет город D в "newRoute", снова рекурсивно вызывает себя, а в другом методе мы получаем ошибку массива вне границ, потому что в моем регионе нет 5 городов, но есть 5 городов на маршруте r по ошибке. (A, B, C, D, D).

Полезное изображение рекурсивной древовидной структуры

Проблема как-то связана с вызовом рекурсии в цикле или с маршрутом 'r', на который ссылается рекурсивный вызов.

Если у вас есть идеи, что мне нужно сделать, я был бы СЕРЬЕЗНО признателен за помощь.

Спасибо всем, кто мне поможет. Я отправлю весь проект всем, кто тоже захочет помочь.


ОБНОВЛЕНИЕ

Хорошо, поэтому я попытался сократить и упростить свой исходный метод, и вот что у меня есть:

public void BruteForceFindBestRoute(Route r, ArrayList<City> citiesNotInRoute)
    {
        if(!citiesNotInRoute.isEmpty())
        {
            for(int i = 0; i<citiesNotInRoute.size(); i++)
            {
                City justRemoved = (City) citiesNotInRoute.remove(0).clone();
                Route newRoute = (Route) r.clone();
                newRoute.addToRoute(justRemoved);

                BruteForceFindBestRoute(newRoute, citiesNotInRoute);
                citiesNotInRoute.add(justRemoved);
            }
        }
        else //if(citiesNotInRoute.isEmpty())
        {
            if(IsBestRoute(r))
                bestRoute = r;
        }

    }

Проблема в том, что переменная i внутри цикла for, кажется, теряет смысл, когда мы прерываем рекурсию, и цикл не продолжается. Идеи?


person null    schedule 28.07.2012    source источник
comment
@nhgrif Да, на этот вопрос был дан ответ, и код действительно работает. Проблема была в моей реализации Clone, которая здесь даже не показана; алгоритм грубой силы после ОБНОВЛЕНИЯ определенно работает, как и тот, который я использовал в своем проекте. Стоит ли убирать исходный вопрос?   -  person null    schedule 29.06.2015
comment
Мэтт, нет, прости. Этот комментарий был получен в ответ на комментарий, оставленный кем-то другим, и ваш вопрос должен быть размещен на другом сайте. Я бы не стал убирать вопрос трехлетней давности, на который уже был дан удовлетворительный ответ, если только вы действительно этого не хотите.   -  person nhgrif    schedule 29.06.2015


Ответы (1)


Вы должны удалить города из маршрута после возврата рекурсивного вызова. Ты делаешь это:

            Route newRoute = r;
            newRoute.addToRoute(citiesNotInRoute.get(i));
            BruteForceFindBestRoute(newRoute);

но никогда newRoute.removeFromRoute или подобное.

Обратите внимание, что вы пишете Java, а в Java назначение объекта выполняется по ссылке. Это означает, что r и newRoute являются одним и тем же объектом. newRoute не является независимой копией r, как вы могли ожидать. Таким образом, любая модификация newRoute также изменит r. Возможно, вы захотите явно скопировать туда свой маршрут. В Java для этого используется термин клон. Убедитесь, что ваш клон достаточно глубокий, т.е. вы клонируете все соответствующие структуры данных, а не делитесь ими между оригиналом и его клоном.

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

РЕДАКТИРОВАТЬ:

Мне подходит следующий вариант вашего кода:

import java.util.*;

class SO11703827 {

    private static ArrayList<Integer> bestRoute;

    public static void bruteForceFindBestRoute
        (ArrayList<Integer> r,
         ArrayList<Integer> citiesNotInRoute)
    {
        if(!citiesNotInRoute.isEmpty())
        {
            for(int i = 0; i<citiesNotInRoute.size(); i++)
            {
                Integer justRemoved =
                    (Integer) citiesNotInRoute.remove(0);
                ArrayList<Integer> newRoute =
                    (ArrayList<Integer>) r.clone();
                newRoute.add(justRemoved);

                bruteForceFindBestRoute(newRoute, citiesNotInRoute);
                citiesNotInRoute.add(justRemoved);
            }
        }
        else //if(citiesNotInRoute.isEmpty())
        {
            if(isBestRoute(r))
                bestRoute = r;
        }

    }

    private static boolean isBestRoute(ArrayList<Integer> r) {
        System.out.println(r.toString());
        return false;
    }

    public static void main(String[] args) {
        ArrayList<Integer> lst = new ArrayList<Integer>();
        for (int i = 0; i < 6; ++i)
            lst.add(i);
        ArrayList<Integer> route = new ArrayList<Integer>();
        bruteForceFindBestRoute(route, lst);
    }
}
person MvG    schedule 28.07.2012
comment
Это именно то, что я пытаюсь сделать, но когда рекурсивный вызов возвращается, мой маршрут (называемый r) каким-то образом включает в себя все города, и это то, что я не понимаю. Я ожидаю, что при первом возврате первого рекурсивного вызова у меня будет что-то вроде r = [Af, Bf, C]. Затем при втором возврате первого рекурсивного вызова r = [Af, B]. и т.д. Однако я получаю это при первом возврате первого рекурсивного вызова: r = [Af, Bf, Cf, Df], что не имеет смысла. Если я не совсем ясен, просто дайте мне знать, и я могу попытаться объяснить заново. Спасибо за быстрый ответ - person null; 28.07.2012
comment
Хм, я могу попробовать вашу идею связанного списка. Мне придется немного подумать над этим, но это звучит как хорошая альтернатива. Я возился с моей первой идеей реализации более 6 часов безрезультатно, поэтому я открыт для новых идей. - person null; 28.07.2012
comment
@ Мэтт, похоже, мой ответ был недостаточно ясным. Думаю, я упустил главную причину вашего замешательства. Теперь отредактировал код, чтобы указать на общий объект. - person MvG; 28.07.2012
comment
Хорошо, поэтому я написал правильные методы clone () для всех моих объектов и переписал метод грубой силы, чтобы воспользоваться ими. Это все еще не сработало. Я снова переписал грубую силу, пытаясь упростить ее - пожалуйста, проверьте OP еще раз, чтобы увидеть обновленный код - внизу. Еще раз спасибо, я очень, очень ценю вашу помощь. Надеюсь, на этот раз будет легче читать, Мэтт - person null; 29.07.2012
comment
@Matt, то, как вы используете список массивов в качестве циклического буфера, на мгновение смутило меня, но должно быть в порядке. ArrayDeque был бы лучшей реализацией этого списка, но это актуально только для производительности. Клонирование городов должно быть ненужным. Кроме того, у меня все работает, поэтому я предполагаю, что что-то не так с тем, как вы реализовали клонирование. - person MvG; 29.07.2012
comment
Вы спасаете жизнь. Это действительно была проблема с клонированием все это время. Только сегодня я потратил на это более 10 часов - оооочень большое спасибо !!! - person null; 29.07.2012