我正在尝试为这种旅行者问题实施一种简单而有效的算法(但这不是“旅行推销员”):
A traveller has to visit N towns, and:
1. each trip from town X to town Y occurs once and only once
2. the origin of each trip is the destination of the previous trip
所以,如果你有例如城镇A,B,C,
A->B, B->A, A->C, **C->A, B->C**, C->B
不是解决方案,因为你不能做C-> A然后B-> C(你需要A->B
),而:
A->B, B->C, C->B, B->A, A->C, C->A
是一个可接受的解决方案(每个目的地都是下一次旅行的起点)。
下面是Java中的插图,例如4个城镇。
ItineraryAlgorithm
是提供回答问题的算法时要实现的界面。如果您将main()
替换为new TooSimpleAlgo()
,new MyAlgorithm()
方法将测试您的算法是否重复。
package algorithm;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Traveller {
private static final String[] TOWNS = new String[] { "Paris", "London", "Madrid", "Berlin"};
public static void main(String[] args) {
ItineraryAlgorithm algorithm = new TooSimpleAlgo();
List<Integer> locations = algorithm.processItinerary(TOWNS);
showResult(locations);
}
private static void showResult(List<Integer> locations) {
System.out.println("The itinerary is:");
for (int i=0; i<locations.size(); i++) {
System.out.print(locations.get(i) + " ");
}
/*
* Show detailed itinerary and check for duplicates
*/
System.out.println("\n");
System.out.println("The detailed itinerary is:");
List<String> allTrips = new ArrayList<String>();
for (int m=0; m<locations.size()-1; m++) {
String trip = TOWNS[locations.get(m).intValue()] + " to "+TOWNS[locations.get(m+1).intValue()];
boolean duplicate = allTrips.contains(trip);
System.out.println(trip+(duplicate?" - ERROR: already done this trip!":""));
allTrips.add(trip);
}
System.out.println();
}
/**
* Interface for interchangeable algorithms that process an itinerary to go
* from town to town, provided that all possible trips are present in the
* itinerary, and only once. Note that after a trip from town A to town B,
* the traveler being in town B, the next trip is from town B.
*/
private static interface ItineraryAlgorithm {
/**
* Calculates an itinerary in which all trips from one town to another
* are done. Trip to town A to town B should occur only once.
*
* @param the
* number of towns to visit
* @return the list of towns the traveler visits one by one, obviously
* the same town should figure more than once
*/
List<Integer> processItinerary(String[] towns);
}
/**
* This algorithm is too simple because it misses some trips! and generates
* duplicates
*/
private static class TooSimpleAlgo implements ItineraryAlgorithm {
public TooSimpleAlgo() {
}
public List<Integer> processItinerary(String[] towns) {
final int nbOfTowns = towns.length;
List<Integer> visitedTowns = new ArrayList<Integer>();
/* the first visited town is town "0" where the travel starts */
visitedTowns.add(Integer.valueOf(0));
for (int i=0; i<nbOfTowns; i++) {
for (int j=i+1; j<nbOfTowns; j++) {
/* travel to town "j" */
visitedTowns.add(Integer.valueOf(j));
/* travel back to town "i" */
visitedTowns.add(Integer.valueOf(i));
}
}
return visitedTowns;
}
}
}
这个示例程序给出了以下输出,第一部分是旅行者访问它们的城镇索引列表(0表示“巴黎”,1表示“伦敦”,2表示“马德里”,3表示“柏林“)。
The itinerary is:
0 1 0 2 0 3 0 2 1 3 1 3 2
The detailed itinerary is:
Paris to London
London to Paris
Paris to Madrid
Madrid to Paris
Paris to Berlin
Berlin to Paris
Paris to Madrid - ERROR: already done this trip!
Madrid to London
London to Berlin
Berlin to London
London to Berlin - ERROR: already done this trip!
Berlin to Madrid
您如何建议实施ItineraryAlgorithm?
编辑:如果您愿意,可以根据需要为最多10个城镇提供最多4个,5个城镇的解决方案。
答案 0 :(得分:1)
这不是旅行商问题和恕我直言,它不是NP完整的,可以在O(N ^ 2)时间内完成。
您可以从任何节点到所有节点执行简单的递归DFS (带回溯)。
例如,如果节点是abcde
,
路线应为
abcde-dce-cbdbe-bacadaea
(Total C(5,2) * 2 = 20 edges)
复杂度顺序为O(n ^ 2),因为边数= 2 * C(n,2)
C ++中的完整工作代码:(抱歉,我不熟悉Java。您可以相应地修改它)
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;
string cities;
void recurRoute( int prevIndex, int currIndex, vector<pair<int,int> > &traversed ) {
// For each i > currIndex, if edge (currindex to i) not in traversed,
// then add the edge and recur on new index i.
for ( int i = currIndex+1; i < cities.size(); i++ ) {
pair<int,int> newEdge( currIndex, i );
if ( find( traversed.begin(), traversed.end(), newEdge ) == traversed.end() ) {
traversed.push_back( newEdge );
recurRoute( currIndex, i, traversed );
}
}
// if there is a previous index,
// then add the back edge (currIndex to prevIndex) and return.
if ( prevIndex >= 0) {
pair<int,int> prevEdge( currIndex, prevIndex );
traversed.push_back( prevEdge );
}
return;
}
int main()
{
cin >> cities;
vector<pair<int,int> > edges;
recurRoute( -1, 0, edges );
for ( int i = 0; i < edges.size(); i++ ) {
cout << cities[ edges[i].first ] << cities[ edges[i].second ] << endl;
}
return 0;
}
输入:
abc
输出:
ab
bc
cb
ba
ac
ca
输入:
abcde
输出:(将新行更改为空格)
ab bc cd de ed dc ce ec cb bd db be eb ba ac ca ad da ae ea
( abcde-dce-cbdbe-bacadae as noted previously )
答案 1 :(得分:1)
这是我的Java解决方案(使用Backtracking算法):
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class BobbelAlgo implements ItineraryAlgorithm {
private final Stack<String> routes = new Stack<String>();
public List<Integer> processItinerary(String[] towns) {
routes.removeAllElements();
final List<Integer> results = new ArrayList<Integer>();
final int[] townIndexList = new int[towns.length];
for (int i = 0; i < towns.length; i++) {
townIndexList[i] = i;
}
// add starting town to list
results.add(0);
// start with route 'town 0' to 'town 1'
visitTowns(townIndexList, townIndexList[0], townIndexList[1], results);
return results;
}
public int visitTowns(final int[] towns, final Integer from, final Integer to, final List<Integer> results) {
// 'from' is equals to 'to' or route already exists
if (from.equals(to) || routes.contains(from + "-" + to)) {
return 2;
}
routes.push(from + "-" + to);
results.add(to);
if (routes.size() == towns.length * (towns.length - 1)) {
// finished, all ways done
return 0;
}
for (final int town : towns) {
final int ret = visitTowns(towns, to, town, results);
if (ret == 0) {
// finished, all ways done
return 0;
} else if (ret == 1) {
// no new way found, go back!
routes.pop();
results.remove(results.size() - 1);
}
}
// no new way found, go back!
return 1;
}
}
对于一个基准测试,我已经通过更多的城镇来完成它,请参阅:
For 10 it took 1 ms.
For 15 it took 0 ms.
For 20 it took 0 ms.
For 25 it took 15 ms.
For 30 it took 15 ms.
For 35 it took 32 ms.
For 40 it took 93 ms.
For 45 it took 171 ms.
For 50 it took 328 ms.
For 55 it took 577 ms.
For 60 it took 609 ms.
For 65 it took 905 ms.
For 70 it took 1140 ms.
For 75 it took 1467 ms.
For 80 it took 1873 ms.
For 85 it took 2544 ms.
For 90 it took 3386 ms.
For 95 it took 4401 ms.
For 100 it took 5632 ms.
在这里你可以看到 O(n ^ 2)的复杂性
在大约100个城镇之后,它获得StackOverflowError
,因为递归调用对于默认堆栈大小配置而言太深(参见:Stack overflows from deep recursion in Java?)。
答案 2 :(得分:1)
我想我找到了我想要的东西:
private static class SylvainSAlgo implements ItineraryAlgorithm {
@Override
public List<Integer> processItinerary(String[] towns) {
List<Integer> itinerary = new ArrayList<Integer>();
for (int i = 0; i<towns.length; i++) {
for (int j = i + 1; j < towns.length; j++) {
itinerary.add(Integer.valueOf(i));
itinerary.add(Integer.valueOf(j));
}
}
itinerary.add(Integer.valueOf(0));
return itinerary;
}
}
答案 3 :(得分:0)
如前所述,这是一个严重的研究问题,随着城市数量的增加,可以迅速解决这个问题。但是,approximations可用于图节点之间的距离代表欧氏距离的情况。
近似算法可以在多项式时间内(与指数相反)为您提供解决方案,但问题在于存在关联的错误限制。解决方案并不简单,需要付出相当大的努力才能实施。大多数算法在几何上接近问题而不是将其视为图形,因此假设距离代表欧氏距离。
答案 4 :(得分:0)
这个问题看起来像是在有向图中确定欧拉循环[1]。
同样在你的情况下,对于N个城镇,每两个城镇之间总是存在两条对称道路(例如,所有对的A-> B,B-> A等等)。 如果是这样的话,我认为你不需要编写这样的算法,如果你的工作只是找到其中一个周期,因为1,2 ... N,周期1,2..N-1 ,N,N-1..2,1始终符合要求。
但是如果每两个城镇之间并不总是有两条对称的道路,事情可能会有所不同而且更加复杂,你可能想要看一下有向图的欧拉路径算法(你)应该能够在离散的数学教科书或算法教科书的图形章节中找到它,如果有这样的路径并且路径具有相同的起点和终点,那就意味着有一个解决方案可以解决你的问题
答案 5 :(得分:-1)
此算法似乎根据您的约束生成可接受的解决方案:
private static class Algorithm implements ItineraryAlgorithm {
public List<Integer> processItinerary(String[] towns) {
List<Integer> sequence = new ArrayList<>(towns.length*(towns.length+1));
for(int idx1 = 0; idx < towns.length; idx1++){
result.add(idx1);
for(int idx2 = idx1+1; idx2 < towns.length; idx2++){
sequence.add(idx2);
sequence.add(idx1);
}
}
List<Integer> segments = new ArrayList<>(result.length*2-2);
for(int i: sequence){
segments.add(i);
segments.add(i);
}
segments.remove(0);
segments.remove(segments.length-1);
return segments;
}
}