Ruby 使用.permutation强制尝试TSP的所有可能路径——有没有更快的方法?
我有一个方法,tour_length(tour),它接受一系列节点(一个tour),并按顺序返回访问这些节点的最短路径(允许通过节点而不访问)。这以O(n)时间运行,其中n=tour.length。这是因为我在散列中存储了每对节点之间最短路径的长度,所以只需查找它们并将它们相加即可 因此,如果我有一个数组,其中每个节点都需要在tour nodes=[a,b,c,d,e]中,那么在所有节点排列的集合中找到最小的tour(节点排序)的最快方法是什么 有一件事是有效的,但可能更快,那就是Ruby 使用.permutation强制尝试TSP的所有可能路径——有没有更快的方法?,ruby,recursion,permutation,Ruby,Recursion,Permutation,我有一个方法,tour_length(tour),它接受一系列节点(一个tour),并按顺序返回访问这些节点的最短路径(允许通过节点而不访问)。这以O(n)时间运行,其中n=tour.length。这是因为我在散列中存储了每对节点之间最短路径的长度,所以只需查找它们并将它们相加即可 因此,如果我有一个数组,其中每个节点都需要在tour nodes=[a,b,c,d,e]中,那么在所有节点排列的集合中找到最小的tour(节点排序)的最快方法是什么 有一件事是有效的,但可能更快,那就是 nodes.
nodes.permutation.min_by{|tour| tour_length(tour)}
这样做的问题是,每次完整巡演都会调用巡演长度
但是请注意,对于路径
b, c, d, a, e
及
部分行程的长度
b, c, d
保持不变,因此重新计算它是多余的
因此,有没有更好的方法,我可以使用排列,或者我应该做一个递归的方法,将部分行程长度相加(这样到目前为止的行程长度可以保持不变,而不是重新计算)
多亏了存储子路径不是一种可扩展的加速计算的方法。他们太多了。密钥构建和存储成本将远远超过缓存距离总和中节省的CPU成本 有一些方法可以枚举排列,这样序列中的下一个排列就可以从上一个排列加上一个小变化计算出来。一个很好的例子可能是 如果使用此算法生成排列,则您将知道哪些元素已更改,只需根据添加和删除的链接的差异进行调整 然而,在纯Ruby中运行该方法的成本可能会超过不必对整个数组求和所获得的任何好处,特别是当暴力方法将不再适用于20个节点以下的情况时。与
permutation
的Ruby内部实现(在C中)相比,求20个数字的总和是缓慢的,但与在Ruby中重新创建相同方法的您自己的版本相比,它将是快速的
在permutation
Ruby内部,似乎在每一步算法中都运行着类似的可预测的小变化,但这是一个实现细节,您不能依赖它在所有版本中都是相同的,并且您无法将距离重新计算代码与排列在每一步的变化联系起来
关于蛮力限制-假设您可以找到一个“完美”算法,该算法在一个CPU时钟周期内生成下一个排列,然后在第二个时钟周期内计算下一个距离。您可以使用它来检查具有17个节点的系统。是时候对所有17人施暴了!3GHz处理器上的排列可能是
2*17!/3*10**9=237124秒
或大约2天半
由于OP无法“查看源代码”,了解Ruby如何在顶层实现排列,因此这里仅供参考:
static VALUE
rb_ary_permutation(int argc, VALUE *argv, VALUE ary)
{
VALUE num;
long r, n, i;
n = RARRAY_LEN(ary); /* Array length */
RETURN_SIZED_ENUMERATOR(ary, argc, argv, rb_ary_permutation_size); /* Return enumerator if no block */
rb_scan_args(argc, argv, "01", &num);
r = NIL_P(num) ? n : NUM2LONG(num); /* Permutation size from argument */
if (r < 0 || n < r) {
/* no permutations: yield nothing */
}
else if (r == 0) { /* exactly one permutation: the zero-length array */
rb_yield(rb_ary_new2(0));
}
else if (r == 1) { /* this is a special, easy case */
for (i = 0; i < RARRAY_LEN(ary); i++) {
rb_yield(rb_ary_new3(1, RARRAY_AREF(ary, i)));
}
}
else { /* this is the general case */
volatile VALUE t0 = tmpbuf(n,sizeof(long));
long *p = (long*)RSTRING_PTR(t0);
volatile VALUE t1 = tmpbuf(n,sizeof(char));
char *used = (char*)RSTRING_PTR(t1);
VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */
RBASIC_CLEAR_CLASS(ary0);
MEMZERO(used, char, n); /* initialize array */
permute0(n, r, p, 0, used, ary0); /* compute and yield permutations */
tmpbuf_discard(t0);
tmpbuf_discard(t1);
RBASIC_SET_CLASS_RAW(ary0, rb_cArray);
}
return ary;
}
/*
* Recursively compute permutations of +r+ elements of the set
* <code>[0..n-1]</code>.
*
* When we have a complete permutation of array indexes, copy the values
* at those indexes into a new array and yield that array.
*
* n: the size of the set
* r: the number of elements in each permutation
* p: the array (of size r) that we're filling in
* index: what index we're filling in now
* used: an array of booleans: whether a given index is already used
* values: the Ruby array that holds the actual values to permute
*/
static void
permute0(long n, long r, long *p, long index, char *used, VALUE values)
{
long i,j;
for (i = 0; i < n; i++) {
if (used[i] == 0) {
p[index] = i;
if (index < r-1) { /* if not done yet */
used[i] = 1; /* mark index used */
permute0(n, r, p, index+1, /* recurse */
used, values);
used[i] = 0; /* index unused */
}
else {
/* We have a complete permutation of array indexes */
/* Build a ruby array of the corresponding values */
/* And yield it to the associated block */
VALUE result = rb_ary_new2(r);
VALUE *result_array = RARRAY_PTR(result);
const VALUE *values_array = RARRAY_PTR(values);
for (j = 0; j < r; j++) result_array[j] = values_array[p[j]];
ARY_SET_LEN(result, r);
rb_yield(result);
if (RBASIC(values)->klass) {
rb_raise(rb_eRuntimeError, "permute reentered");
}
}
}
}
}
如果你问(让我引用你的话)“暴力尝试所有可能的城市排序”,同时牢记“避免计算每次旅行的全部长度,而是保持部分旅行长度”,那么这就是TSP问题,更具体地说,这是,通过从较小的问题中构建问题来解决问题的方法
如果你考虑部分游程长度,运行时间从O(n)(即排列数)到O(n ^ 2×2 ^ n),这仍然是大的和难以缩放的,是对文字蛮力的改进,它是,作为2017,最快的算法来获得对你的问题的精确解。这就是你想学的。它通过以蛮力的方式考虑每个可能的子路径的每个最优解来构造最优精确解。
也许我不完全理解您在这里所做的,但这看起来像。如果是这样的话,有几种方法可以加快它的速度(参见链接或谷歌),但它们通常涉及到找到一个“足够好”的解决方案某些最短路径算法提供从给定节点到其他节点的最短路径,作为从给定节点到另一个给定节点的最短路径的副产品。使用这种算法,您只需要在每个(比如说,开始)节点上枚举。如果我对你的问题理解正确,“tour”不是最好的词,因为我认为你必须访问每个节点,这会使旅行推销员遇到问题“路径”在这里是一个更好的描述。@neuronaut我不是在寻找一种加速TSP的方法,真的。我把自己限制在暴力的范围内,尝试所有可能的城市秩序。鉴于此,我试图避免计算每次巡更的全长,而是保留部分巡更的长度。@CarySwoveland是的,我使用Dijkstra得到所有节点对之间的最短路径长度。这些信息是存储的。所以,按照这个顺序进行[a,c,d,b]的“旅行”,这将是最短的(a,c)+最短的(c,d)+最短的(d,b),等等。读了你的问题,我有点困惑。假设从一组节点、一组定向链接以及存在链接的每对节点之间的距离开始。您希望找到最短路径为最小的一对节点x和y。您说过并非所有节点都必须访问。如果对访问哪些节点没有限制,则存在n^2个最短路径问题。然而,似乎给定的子集节点必须访问,但没有给定的顺序,这使得它成为旅行商问题的变体。对吗?这是个好答案,谢谢。什么是ruby进程/工具/它使用C做的任何事情。排列?也许我应该尝试使用一些C语言。Ruby的内部代码通常是用C语言编写的,而且很多Arrstatic VALUE
rb_ary_permutation(int argc, VALUE *argv, VALUE ary)
{
VALUE num;
long r, n, i;
n = RARRAY_LEN(ary); /* Array length */
RETURN_SIZED_ENUMERATOR(ary, argc, argv, rb_ary_permutation_size); /* Return enumerator if no block */
rb_scan_args(argc, argv, "01", &num);
r = NIL_P(num) ? n : NUM2LONG(num); /* Permutation size from argument */
if (r < 0 || n < r) {
/* no permutations: yield nothing */
}
else if (r == 0) { /* exactly one permutation: the zero-length array */
rb_yield(rb_ary_new2(0));
}
else if (r == 1) { /* this is a special, easy case */
for (i = 0; i < RARRAY_LEN(ary); i++) {
rb_yield(rb_ary_new3(1, RARRAY_AREF(ary, i)));
}
}
else { /* this is the general case */
volatile VALUE t0 = tmpbuf(n,sizeof(long));
long *p = (long*)RSTRING_PTR(t0);
volatile VALUE t1 = tmpbuf(n,sizeof(char));
char *used = (char*)RSTRING_PTR(t1);
VALUE ary0 = ary_make_shared_copy(ary); /* private defensive copy of ary */
RBASIC_CLEAR_CLASS(ary0);
MEMZERO(used, char, n); /* initialize array */
permute0(n, r, p, 0, used, ary0); /* compute and yield permutations */
tmpbuf_discard(t0);
tmpbuf_discard(t1);
RBASIC_SET_CLASS_RAW(ary0, rb_cArray);
}
return ary;
}
/*
* Recursively compute permutations of +r+ elements of the set
* <code>[0..n-1]</code>.
*
* When we have a complete permutation of array indexes, copy the values
* at those indexes into a new array and yield that array.
*
* n: the size of the set
* r: the number of elements in each permutation
* p: the array (of size r) that we're filling in
* index: what index we're filling in now
* used: an array of booleans: whether a given index is already used
* values: the Ruby array that holds the actual values to permute
*/
static void
permute0(long n, long r, long *p, long index, char *used, VALUE values)
{
long i,j;
for (i = 0; i < n; i++) {
if (used[i] == 0) {
p[index] = i;
if (index < r-1) { /* if not done yet */
used[i] = 1; /* mark index used */
permute0(n, r, p, index+1, /* recurse */
used, values);
used[i] = 0; /* index unused */
}
else {
/* We have a complete permutation of array indexes */
/* Build a ruby array of the corresponding values */
/* And yield it to the associated block */
VALUE result = rb_ary_new2(r);
VALUE *result_array = RARRAY_PTR(result);
const VALUE *values_array = RARRAY_PTR(values);
for (j = 0; j < r; j++) result_array[j] = values_array[p[j]];
ARY_SET_LEN(result, r);
rb_yield(result);
if (RBASIC(values)->klass) {
rb_raise(rb_eRuntimeError, "permute reentered");
}
}
}
}
}