5

我正在为学校的数学课做一个项目,我选择做我的旅行商问题,这是我一直想进一步研究的问题。但是,我的蛮力求解算法存在问题。

*请到底部更新查看最新版本的代码



如果您知道旅行推销员的问题是什么,请跳过此段落: 尽可能地总结一下,TSP 是这样的:你是一个推销员,想要访问一个地区的每个城市(一个城市本质上是地图上的一个点)。在有界的 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 个未标记城市的路线开始。
  • 标记“最后一个未标记”的城市(这个城市是“枢轴”)
  • 找到每个“不在 ROUTE R”中的城市,并将其添加到新路线中。
  • 在这些路由中的每一个上递归调用 BruteForce 方法。

(如果所有路线都没有标记,但路线包含每个城市)

  • 标记最后一个城市

(否则...这意味着该路线已标记每个城市并包含每个可能的城市)

  • 看看这是否是最短的路线 - 如果是,将其存储在全局变量中

这张图片将帮助我解释问题......所以程序正确地从左侧下降。然而,在它到达底部之后,人们会期望递归跳回到第 4 步,它确实如此。然而,不是 R 将城市 A 标记和城市 B 未标记,然后在包含 Aflag 和 B 的“新路线”上递归调用自己,R 现在包含所有 4 个城市,并且所有 4 个城市都被标记。它失败了,因为它将城市 D 再次添加到“newRoute”,再次递归调用自身,在另一种方法中,我们得到一个数组越界错误,因为我所在的地区没有 5 个城市,但路线 r 中有 5 个城市错误(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;
        }

    }

问题是,当我们跳出递归时,for 循环中的变量 i 似乎失去了意义,并且循环不会继续。想法?

4

1 回答 1

6

递归调用返回后,您应该从路线中删除城市。你来做这件事:

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

但从来没有一个newRoute.removeFromRoute或类似的。

请注意,您正在编写 Java,并且在 Java 中,对象的分配是通过引用完成的。这意味着rnewRoute同一个对象newRoute不是r您可能期望的独立副本。所以任何修改newRoute都会改变r。您可能想在那里明确复制您的路线。对此的 Java 术语是clone。确保你的克隆足够深,即克隆所有相关的数据结构,而不是在原始数据结构和它的克隆之间共享它们。

注意:有很多地方可以让你的代码更高效,但无论如何,蛮力远非高效,而且你只是在谈论几个城市,也许你不必关心。但是,如果您想研究替代方案,请考虑维护所有未访问城市的单个链接列表。每个调用都会遍历该列表,删除一个元素,递归并放回元素。无需在每次调用中从头开始构建此列表。跳舞链接的想法可以巧妙地应用于此任务,作为预制链接列表实现的替代方案。

编辑:

您的代码的以下变体对我有用:

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);
    }
}
于 2012-07-28T19:22:15.337 回答