【问题标题】:Algorithm for generating strings of +/-s with a specific property用于生成具有特定属性的 +/-s 字符串的算法
【发布时间】:2017-09-21 16:00:59
【问题描述】:

我有兴趣编写一个函数generate(n,m),它详尽地生成长度为n(n-1)/2 的字符串,仅由+/- 字符组成。然后这些字符串将通过以下方式转换为 n × n 对称 (-1,0,1) 矩阵:

toTriangle["+--+-+-++-"]  

{{1, -1, -1, 1}, {-1, 1, -1}, {1, 1}, {-1}}
toMatrix[%, 0] // MatrixForm

             | 0  1 -1 -1  1 |  
             | 1  0 -1  1 -1 |  
matrixForm = |-1 -1  0  1  1 |  
             |-1  1  1  0 -1 |
             | 1 -1  1 -1  0 |  

因此,给定的字符串表示矩阵的右上三角形,然后将其反射以生成其余部分。

问题:如何生成所有+/- 字符串,生成的矩阵每行正好有m -1

例如,generate(5,3) 将给出长度为5(5-1)/2 = 10 的所有字符串,这样每一行都恰好包含三个-1。

如果能帮助我构建这样的算法,我将不胜感激。

【问题讨论】:

  • 您确定可以生成 any 5x5 矩阵,每行恰好包含三个 -1?
  • 事实上,我在关注generate(74,32),但我不确定这在计算上是否可行……不过,我也想将算法用于较小的情况。跨度>
  • 你是否因为试图优化某些东西而试图产生所有可能性?
  • 事实是,我正在尝试生成只有两个特征值的 74 × 74 矩阵......并且这个属性显着减少了搜索空间。
  • 我用文本替换了图像,因为在 SO 上不鼓励将图像用于文本或代码(它们不可搜索且屏幕阅读器无法解释)。

标签: algorithm matrix wolfram-mathematica computer-science


【解决方案1】:

这是为给定的 n 和 m 生成每个矩阵的逻辑。这有点令人费解,所以我不确定实现会比蛮力快多少;我认为对于较大的值,差异会变得更加明显。

(为方便起见,以下将生成 0 和 1 的输出,其中 0 表示加号,1 表示减号。)

每行有 m 个的方阵转换为这些折叠的行/列有 m 个的三角矩阵:

x 0 1 0 1    x 0 1 0 1    0          1        0      1
0 x 1 1 0                 x 1 1 0    1        1      0
1 1 x 0 0                            x 0 0    0      0
0 1 0 x 1                                     x 1    1
1 0 0 1 x                                            x

这些组中的每一个都与所有其他组重叠;为前 k 个组选择值意味着组 k+1 的垂直部分已经确定。

我们首先将每行所需的数量放在对角线上;例如对于 (5,2) 即:

2 . . . .
  2 . . .
    2 . .
      2 .
        2

然后我们为第一组生成具有 m 个 1 的每个位模式;其中有 (n-1 选择 m) 个,并且可以有效地生成它们,例如与 Gosper 的 hack。

(4,2)  ->  0011  0101  0110  1001  1010  1100  

对于每一个,我们将它们填充到矩阵中,然后从所需的数量中减去它们:

X 0 0 1 1
  2 . . .
    2 . .
      1 .
        1

然后用较小的三角形递归:

2 . . .
  2 . .
    1 .
      1

如果我们到达对角线上一些必需的数量为零的点,例如:

2 . . .
  1 . .
    0 .
      1

那么我们已经可以在该列中放置一个零,并为更少的列生成可能的位模式;在示例中,将是 (2,2) 而不是 (3,2),因此只有一种可能的位模式:11。然后我们将位模式分布在其下具有非零要求计数的列上:

2 . 0 .    X 1 0 1
  1 . .      0 . .
    0 .        0 .
      1          0

但是,并非所有可能的位模式都会导致有效的解决方案;举个例子:

2 . . . .    X 0 0 1 1
  2 . . .      2 . . .    2 . . .    X 0 1 1
    2 . .        2 . .      2 . .      2 . .    2 . .
      2 .          1 .        1 .        0 .      0 .
        2            1          1          0        0

我们最终得到的一行需要另外 2 个,而两列都不能再占用任何一个。发现这种情况的方法是查看倒数第二步中每个选项创建的每列所需的列表:

pattern    required
 0 1 1  ->  2 0 0
 1 0 1  ->  1 1 0
 1 1 0  ->  1 0 1

如果列表中的第一个值是 x,那么它之后必须至少有 x 个非零值;这对于三个选项中的第一个是错误的。

(这里有优化的空间:在1,1,0,6,0,2,1,1这样的计数列表中,6之前只有2个非零值,这意味着6最多会减少2倍,所以它的最小值当它成为第一个元素时的值将是 4;但是,它之后只有 3 个非零值,所以在这个阶段你已经知道这个列表不会导致任何有效的解决方案。检查这会增加代码的复杂性,所以我不确定这是否会提高执行速度。)

所以 (n,m) 的完整算法是这样开始的:

  • 创建一个 n 大小的列表,所有值都设置为 m(每组所需的计数)。
  • 生成所有大小为 n-1 的位模式,其中有 m 个;对于其中的每一个:
    • 从计数列表的副本中减去模式(不包括第一个元素)。
    • 使用计数列表的模式和副本进行递归。

之后的递归步骤是:

  • 收到到目前为止的序列和计数列表。
  • 计数列表的长度是n,它的第一个元素是m。
  • 设 k 为计数列表中非零值的数量(不包括第一个元素)。
  • 生成所有大小为 k 的位模式,其中有 m 个;对于其中的每一个:
    • 创建一个由 0 填充的列表,大小为 n-1。
    • 在其上分配位模式,跳过计数为零的列。
    • 到目前为止,将值列表添加到序列中。
    • 从计数列表的副本中减去值列表(不包括第一个元素)。
    • 如果计数列表副本中的第一个值大于其后的非零数,则跳过此模式。
    • 在最深的递归级别,存储序列,否则:
    • 使用到目前为止的序列和计数列表的副本进行递归。

这里有一个代码 sn-p 作为概念证明;用一种严肃的语言,并且使用整数而不是位图的数组,这应该更快:

function generate(n, m) {
    // if ((n % 2) && (m % 2)) return; // to catch (3,1)
    var counts = [], pattern = [];
    for (var i = 0; i < n - 1; i++) {
        counts.push(m);
        pattern.push(i < m ? 1 : 0);
    }
    do {
        var c_copy = counts.slice();
        for (var i = 0; i < n - 1; i++) c_copy[i] -= pattern[i];
        recurse(pattern, c_copy);
    }
    while (revLexi(pattern));
}

function recurse(sequence, counts) {
    var n = counts.length, m = counts.shift(), k = 0;
    for (var i = 0; i < n - 1; i++) if (counts[i]) ++k;
    var pattern = [];
    for (var i = 0; i < k; i++) pattern.push(i < m ? 1 : 0);
    do {
        var values = [], pos = 0;
        for (var i = 0; i < n - 1; i++) {
            if (counts[i]) values.push(pattern[pos++]);
            else values.push(0);
        }
        var s_copy = sequence.concat(values);
        var c_copy = counts.slice();
        var nonzero = 0;
        for (var i = 0; i < n - 1; i++) {
            c_copy[i] -= values[i];
            if (i && c_copy[i]) ++nonzero;
        }
        if (c_copy[0] > nonzero) continue;
        if (n == 2) {
            for (var i = 0; i < s_copy.length; i++) {
                document.write(["+ ", "&minus; "][s_copy[i]]);
            }
            document.write("<br>");
        }
        else recurse(s_copy, c_copy);
    }
    while (revLexi(pattern));
}

function revLexi(seq) { // reverse lexicographical because I had this lying around
    var max = true, pos = seq.length, set = 1;
    while (pos-- && (max || !seq[pos])) if (seq[pos]) ++set; else max = false;
    if (pos < 0) return false;
    seq[pos] = 0;
    while (++pos < seq.length) seq[pos] = set-- > 0 ? 1 : 0;
    return true;
}

generate(5, 2);

这里是 n 到 10 的值的结果数和递归数,因此您可以比较它们以检查正确性。当 n 和 m 都是奇数时,没有有效结果;这是正确计算的,但 (3,1) 的情况除外;抓住这些病例并立即返回当然很容易。

 (n,m)            results        number of recursions

 (4,0)  (4,3)           1            2            2
 (4,1)  (4,2)           3            6            7

 (5,0)  (5,4)           1            3            3
 (5,1)  (5,3)           0           12           20
 (5,2)                 12           36

 (6,0)  (6,5)           1            4            4
 (6,1)  (6,4)          15           48           76
 (6,2)  (6,3)          70          226          269

 (7,0)  (7,6)           1            5            5
 (7,1)  (7,5)           0           99          257
 (7,2)  (7,4)         465        1,627        2,313
 (7,3)                  0        3,413

 (8,0)  (8,7)           1            6            6
 (8,1)  (8,6)         105          422        1,041
 (8,2)  (8,5)       3,507       13,180       23,302
 (8,3)  (8,4)      19,355       77,466       93,441

 (9,0)  (9,8)           1            7            7
 (9,1)  (9,7)           0          948        4,192
 (9,2)  (9,6)      30,016      119,896      270,707
 (9,3)  (9,5)           0    1,427,457    2,405,396
 (9,4)          1,024,380    4,851,650

(10,0) (10,9)           1            8            8
(10,1) (10,8)         945         4440        18930
(10,2) (10,7)     286,884    1,210,612    3,574,257
(10,3) (10,6)  11,180,820   47,559,340   88,725,087
(10,4) (10,5)  66,462,606  313,129,003  383,079,169

【讨论】:

  • 哇-感谢您的回答!稍后我会详细介绍它,如果我设法实现它,请告诉您。
  • @LukeCollins 我尝试了一个优化版本,但虽然它减少了三分之一的递归次数,但增加的复杂性使算法在实际中慢得多。
  • @LukeCollins 谢谢。这在 Mathematica 中是否有用? (另外,我是否可以建议您编辑问题的标题以更好地描述您正在做的数学,以便做类似事情的人可以找到问题?提到“具有特定属性的 +/-s 字符串”有点模糊。)
【解决方案2】:

我怀疑您是否真的想要大 n,m 值的所有变体 - 它们的数量非常大。

这个问题相当于生成m-正则图(注意,如果我们用零替换所有1,用1替换所有-1 - 我们可以看到图的邻接矩阵。正则图 - 所有顶点的度数等于m )。

Here 我们可以看到 (18,4) 正则图的数量约为 10^9 并且随着 n/m 值快速上升。文章包含link 以编程genreg,用于生成此类图形。指向代码和可执行文件的 FTP 链接对我不起作用——可能太旧了。

更新: Here is 另一个来源链接(虽然是 1996 年而不是纸上的 1999 年)

here 描述了生成一个正则图实例的简单方法。

对于小的 n/m 值,您还可以尝试蛮力:用 m 个填充第一行(有 C(n,m) 个变体,每个变体在第二行填充空闲位置,依此类推)

【讨论】:

  • 正确,我正在生成所谓的图的塞德尔邻接矩阵。我想在 74 个顶点上生成一个图,使得矩阵正好有两个特征值。
  • 是的,我希望蛮力适用于 n
  • 事实上,我对生成 strongly regular 图形很感兴趣,它是常规图形的子集。
【解决方案3】:

用 Wolfram Mathematica 编写。

generate[n_, m_] := Module[{},
  x = Table[StringJoin["i", ToString[i], "j", ToString[j]],
    {j, 1, n}, {i, 2, n}];
  y = Transpose[x];
  MapThread[(x[[#, ;; #2]] = y[[#, ;; #2]]) &,
   {-Range[n - 1], Reverse@Range[n - 1]}];
  Clear @@ Names["i*"];
  z = ToExpression[x];
  Clear[s];
  s = Reduce[Join[Total@# == m & /@ z,
     0 <= # <= 1 & /@ Union[Flatten@z]],
    Union@Flatten[z], Integers];
  Clear[t, u, v];
  Array[(t[#] = 
      Partition[Flatten[z] /.
         ToRules[s[[#]]], n - 1] /.
       {1 -> -1, 0 -> 1}) &, Length[s]];
  Array[Function[a,
    (u[a] = StringJoin[Flatten[MapThread[
          Take[#, 1 - #2] &,
          {t[a], Reverse[Range[n]]}]] /.
        {1 -> "+", -1 -> "-"}])], Length[s]];
  Array[Function[a,
    (v[a] = MapThread[Insert[#, 0, #2] &,
       {t[a], Range[n]}])], Length[s]]]

Timing[generate[9, 4];]
Length[s]
{202.208, Null}
1024380

该程序需要 202 秒才能生成 1,024,380 个解决方案。例如。最后一个

u[1024380]
----++++---++++-+-+++++-++++--------
v[1024380]
 0  -1  -1  -1  -1   1   1   1   1   
-1   0  -1  -1  -1   1   1   1   1   
-1  -1   0  -1   1  -1   1   1   1   
-1  -1  -1   0   1   1  -1   1   1   
-1  -1   1   1   0   1   1  -1  -1   
 1   1  -1   1   1   0  -1  -1  -1   
 1   1   1  -1   1  -1   0  -1  -1   
 1   1   1   1  -1  -1  -1   0  -1   
 1   1   1   1  -1  -1  -1  -1   0   

前十个字符串

u /@ Range[10]
++++----+++----+-+-----+----++++++++
++++----+++----+-+------+--+-+++++++
++++----+++----+-+-------+-++-++++++
++++----+++----+--+---+-----++++++++
++++----+++----+---+--+----+-+++++++
++++----+++----+----+-+----++-++++++
++++----+++----+--+-----+-+--+++++++ 
++++----+++----+--+------++-+-++++++
++++----+++----+---+---+--+--+++++++

【讨论】:

  • @m69 我得到了 (7,2) 和 (7,4) 的 465 和 (7,3) 的零。
猜你喜欢
  • 1970-01-01
  • 2020-08-21
  • 2011-05-13
  • 1970-01-01
  • 1970-01-01
  • 2016-10-13
  • 1970-01-01
  • 2011-07-04
  • 2015-07-01
相关资源
最近更新 更多