最小化'交换'的最小变化算法

时间:2010-04-23 12:46:23

标签: algorithm combinatorics

这是一个关于非数学家的组合学的问题,所以请尽量忍受我!

给定n个不同字符的数组,我想以最小变化顺序生成k个字符的子集,即生成i + 1恰好包含一个不在第i代中的字符的顺序。这本身并不太难。但是,我还想最大化生成i + 1中交换 out 的字符与第i代中 中交换的字符数相同的情况。为了说明,对于n = 7,k = 3:

abc abd abe * abf * abg * afg aeg * adg * acg * acd ace * acf * aef adf * ade bde bdf bef bcf * bce bcd * bcg * bdg beg * bfg * cfg ceg * cdg * cde cdf * cef def deg dfg efg

带星号的字符串表示我想要最大化的情况;例如abe中第3代的新e替换了第2代abd中的新内容。似乎不可能在每一代都发生这种情况,但我希望它尽可能频繁地发生。

我使用的典型阵列大小为20-30,子集大小大约为5-8。

我使用的是一种奇怪的语言,Icon(或实际上是它的衍生Unicon),所以我不希望任何人发布我可以直接使用的代码。但我会很感激伪代码的答案或提示,并会尽力翻译C等。另外,我注意到这类问题经常以整数数组的形式讨论,我当然可以应用解决方案以这样的方式发布我自己的问题。

由于

金巴斯汀

编辑2010年6月15日:

我似乎进入了比我想象的更深的水,虽然我很感激所有答案,但并非所有答案都相关。作为一个不充分的解决方案的例子,让我发布我自己的Unicon程序,以最小的变化顺序生成字符集的k元子集。要理解代码,你需要知道的事情是:前置*表示结构的大小,所以如果s是字符串,* s表示s的大小(它包含的字符数)。 ||是一个字符串连接操作。一个前置!产生结构的每个元素,例如,字符串的每个字符,依次是连续的传递。 'suspend'控制结构从一个过程返回一个结果,但是让过程“悬而未决”,所有的局部变量就位,这样如果在一个循环中调用过程就可以产生新的结果。

procedure revdoor(s, k)  
# Produces all k-subsets of a string or character set s in a 'revolving  
# door' order. Each column except the first traverses the characters  
# available to it in alphabetical and reverse alphabetical order  
# alternately. The order of the input string is preserved. 
# If called in a loop as revdoor("abcdefg", 3), 
# the order of production is: abc, abd, abe, abf, abg, acg, acf, ace, acd,  
# ade, adf, adg, aeg, aef, afg, bfg, bef, beg, bdg, bdf, bde, bcd, bce,  
# bcf, bcg, cdg, cdf, cde, cef, ceg, cfg, dfg, deg, def, efg  

local i  
static Ctl  

if /Ctl then {             # this means 'if Ctl doesn't exist'
  if k = 0 then return ""  
  Ctl := list(k, 1)        # a list of k elements, each initialised to 1.
}  

if Ctl[k] = 1 then {  
  if k = 1 then suspend !s else  
    every i := 1 to *s-k+1 do {  
      suspend s[i] || revdoor(s[i+1:0], k-1)  
    }   
  } else {  
    if k = 1 then suspend !reverse(s) else  
    every i := -k to -*s by -1 do {  
      suspend s[i] || revdoor(s[i+1:0], k-1)  
    }  
  }  

  # the following line multiplies element k of Ctl by -1 if k < size of Ctl
  # (this controls the order of generation of characters), 
  # and destroys Ctl on final exit from the procedure.
  if k < *Ctl then Ctl[k] *:= -1 else Ctl := &null   

end  

请注意,上述程序的输出在我看来并不是最佳的。到目前为止,我调查的一个结果是,n个元素的k元子集列表的最大“交换分数”不小于comb(n-1,k),或者在n = 7的情况下,k =在图3中,最大分数至少是comb(6,3)= 20.我将列表的“交换分数”定义为列表中的项目数,其中新元素替换上一项中本身是新的元素。我没有数学设备来证明这一点,但很容易看出k = 1或k = 2。对于某些(n,k),可能会略高一些,如n = 7,k = 3:

abc abd abe abf abg
acg adg aeg afg
efg dfg cfg bfg
求bdg bcg
bcd bce bcf
bdf bef
def cef aef
adf acf
acd ace
阿德
bde cde
cdf cdg
CEG
deg(交换分数= 21)

可以注意到,上面的列表是“强烈的最小变化顺序”(比如单词高尔夫:新角色总是与它所替换的角色位于同一位置),这可能表明我自己的工作正在采取的方向。我希望在几天内发布更多内容。

5 个答案:

答案 0 :(得分:1)

这很简单。为了最大化替换,只需将字符视为数字,然后将字符串递增1,直到达到上限。 然后检查字符串中是否有两次使用相同的字符。 我认为这样可行:

char c[] = {'a', 'b', 'c', 'd', 'e'};
const int n = 5;
const int k = 3;
char s[k];

void print()
{
    for( int i = 0; i < k; ++i )
        putchar(c[s[i]]);
    putchar('\n');
}

bool increment( int m )
{
    // reached the limit?
    if( ++s[m] == n && m == 0 )
        return false;

    next:   
    for( int i = 0; i < m; ++i )
    {
        if( s[m] == n )
        {
            // carry
            s[m] = 0;
            if( !increment( m-1 ))
                return false;
            goto next;
        }
        else if( s[i] == s[m] )
        {
            // the character is already used
            ++s[m];
            goto next;
        }   
    }
    return true;
}

int main(int, char**)
{   
    // initialise
    for( int i = 0; i < k; ++i )
        s[i] = i;

    // enumerate all combinations
    do
        print();
    while(increment(k-1));
}

答案 1 :(得分:0)

Kim,你的问题描述听起来非常类似于(家庭作业)尝试描述枚举一组n个元素的所有k组合的最简单的解决方案 - 而不是太容易地提供实际的解决方案。无论如何,请看下面我的镜头。我使用Java但重要部分与C没有区别。

public class Homework
{
  /**
   * Prints all k-combinations of a set of n elements. Answer to this 
   * question: http://stackoverflow.com/questions/2698551
   */
  public static void main(String[] args)
  {
    Combinations combinations = new Combinations(7, 3);
    System.out.printf(
        "Printing all %d %d-combinations of a set with %d elements:\n", 
        combinations.size(), combinations.k, combinations.n);
    for (int[] c : combinations)
      System.out.println(Arrays.toString(c));
  }

  /**
   * Provides an iterator for all k-combinations of a set of n elements. 
   */
  static class Combinations implements Iterable<int[]>  
  {
    public final int n, k;

    public Combinations(int n, int k)
    {
      if (n < 1 || n < k)
        throw new IllegalArgumentException();
      this.n = n;
      this.k = k;
    }

    @Override
    public Iterator<int[]> iterator()
    {
      return new Iterator<int[]>()
      {
        private int[] c;

        @Override
        public void remove() { throw new UnsupportedOperationException(); }

        @Override
        public int[] next()
        {
          if (c == null)
          {
            c = new int[k];
            for (int i = 0; i < k; i++)
              c[i] = i;
          }
          else
          {
            int i = c.length - 1;
            while (i >= 0 && c[i] == n - k + i)
              i--;

            if (i < 0)
              throw new NoSuchElementException();

            c[i]++;
            for (int j = i + 1; j < c.length; j++)
              c[j] = c[i] + j - i;
          }
          return c.clone(); // remove defensive copy if performance is more important
        }

        @Override
        public boolean hasNext() { return c == null || c[0] < n - k; }
      };
    }

    /**
     * Returns number of combinations: n! / (k! * (n - k)!).
     */
    public BigInteger size()
    {
      BigInteger s = BigInteger.valueOf(n);
      for (int i = n - 1; i > n - k; i--)
        s = s.multiply(BigInteger.valueOf(i));
      for (int i = k; i > 1; i--)
        s = s.divide(BigInteger.valueOf(i));
      return s;
    }
  }
}

以下是您的示例的输出:

Printing all 35 3-combinations of a set with 7 elements:
[0, 1, 2] [0, 1, 3] [0, 1, 4] [0, 1, 5] [0, 1, 6] [0, 2, 3] [0, 2, 4] [0, 2, 5] [0, 2, 6] [0, 3, 4] 
[0, 3, 5] [0, 3, 6] [0, 4, 5] [0, 4, 6] [0, 5, 6] [1, 2, 3] [1, 2, 4] [1, 2, 5] [1, 2, 6] [1, 3, 4] 
[1, 3, 5] [1, 3, 6] [1, 4, 5] [1, 4, 6] [1, 5, 6] [2, 3, 4] [2, 3, 5] [2, 3, 6] [2, 4, 5] [2, 4, 6] 
[2, 5, 6] [3, 4, 5] [3, 4, 6] [3, 5, 6] [4, 5, 6] 

答案 2 :(得分:0)

我没有从算法开始,而是试图找到一种方法来找到最大“交换分数”的表单,这样你就知道要拍什么了。通常,用于产生期望结构的算法可以从这样的证明中出现。

从大学开始已经有很长一段时间了,但是我试着想出一个有助于解决这个问题的组合模型,而且没有太多运气。

我开始将组合集想象为图中的顶点,边缘对应于组合的“邻接”(仅一个元素差异)。所以:

  • “n选择k”顶点
  • 每个顶点具有度k(n-k)
  • 边数=“n选择k”* k(n-k)/ 2 =“n选择2”*“n-2选择k-1”

这些图表有很多对称性。对于任何给定的{n,k},该图与{n,n-k}的图相同。如果k = 1或k = n-1,则它是n个顶点上的完整图形(每个组合与所有其他组合仅相差一个字符)。但是,我无法从中看到明显的算法。

编辑:我的下一个想法是设想图表略有不同的解释。您可以将每个{n,k}组合视为具有k 1的n位序列。 1s的位置对应于组合中存在n个字符中的哪一个。因此,对于n = 7 k = 3,abc是1110000,adg是1001001,efg是0000111.这样你也可以想象位于n维超立方体角落的点。因此,对于给定的子序列,如果它们是共面的,则边缘匹配您的“最小交换”标准:我将它们视为通过超立方体的“切割平面”。

您正在寻找通过此组合图表的汉密尔顿路径,符合您的特殊标准。

另一种思考问题的方法是尽量减少序列中更改组合中哪个字符被更改的次数。

答案 3 :(得分:0)

为了得到一个好的答案,可以同时计算组合列表是否可以接受,或者您是否需要一次计算一个?换句话说,你需要一个功能:

Combination nextCombo();

vector<Combination> allCombinations();

可以接受吗?

如果批量计算组合是允许的,那么迭代加深的A *搜索(或者只是A *搜索,但我怀疑它的内存耗尽)可能会起作用。通过允许的启发式,A *可以保证最佳。我的时间不够,所以如果我有时间编写代码,我决定发布这个部分答案并编辑帖子。

A *是图搜索算法。在这种情况下,节点是到目前为止使用的组合列表(列表中不允许重复)。我的计划是为节点使用位串表示。 n = 30将适合32位整数。我们可以随意置换任何解决方案,以便第一个组合以0开头并以1结尾,即000 ... 1111。具有较短列表的节点连接到较长的一个节点,如果两个列表相同直到最后一个元素,并且最后一个元素仅在一个0'位翻转为1并且一个1位翻转为0时不同。如果存在“交换”,则两者之间的距离为0;如果存在交换,则距离为1。

每个组合的第二个表示形式是打开的位的排序列表。此图的一种可能的允许启发式是使用此索引列表。每次(在组合列表中)索引在索引列表中的特定位置使用,将其标记为关闭。具有未使用指数的头寸数量 - 当前最后更改的指数是(我相信)需要发生的最小交换数量。

为了说明这种启发式方法,请考虑上面的序列abc abd abe * abf * abg afg。 (字母将是我治疗中的数字,但这是一个小的差异)。此序列(将是搜索图中的一个节点)将标记以下位置:

 1   2   3
*a      
 b  *b   
 c   c  *c
 d   d  *d
 e   e  *e
    *f  *f
        *g

因此,启发式预测至少需要一个交换(因为位置3中没有未标记的元素,当前位置为2)。

如果我有时间,我会编辑它以发布算法的代码和性能。


Re:NP完整性结果(在对Zac Thompson答案的评论中)。我们正在寻找最小成本哈密尔顿路径的图表具有非常特殊的结构。例如,通常NP完全哈密顿路径问题可以在O(n)时间内用“枚举所有组合”算法求解 - 其中n是图中节点的数量。这种结构使得虽然在一般图形上顶点覆盖很难,但是在图形上它可能是多项式的(甚至是线性的或二次的)。当然,由于图表有很多节点,例如n = 30,k = 8,你可能还有很多计算。

答案 4 :(得分:0)

我在2010年解决了这个问题,但当时未能找到解决方案。几天前,我又看了一下那段时间的笔记,怀疑我已经非常接近解决方案了。几分钟后,我拿到了钥匙。

重新注册:要求是字符串s的k子集的严格的最小变化排序,使得LIFO(后进先出)最大化。我在之前的帖子中称之为最大化的“交换”。

我在关键要求之后调用算法maxlifo(最大化LIFO)。它需要两个参数,一个字符串s,它不能包含重复的字符,一个正整数k不大于s的大小。算法是递归的,即maxlifo(s,k)使用maxlifo(s,k-1)的输出向下到k = 1。输出以列表形式返回。

下面我用一些例子给出一个非正式的解释,使用字符串“abcdefg”和各种k值。接下来是作为Unicon过程实现的示例。 (我不会流利使用任何比较常用的语言。)

案例k = 1是微不足道的 - 它从头到尾依次返回s的元素。

对于k&gt; 1,将以下规则应用于maxlifo(s,k-1)的输出:

(1)对于maxlifo(s,k-1)输出的每个元素,在一行中列出从该元素构建的k子集,其中每个缺失的s依次存在。子集中字符的顺序与s中相同。

(2)从第二行开始,将空白“占位符”替换为出现在前一行中的所有子集。 s的每个k子集现在只出现一次。

(3)在每个非空白行中,标记一个首字母!每个子集,使得占位符位于下一行的相同位置。这个标记意味着'第一'。每个非空白行中都会标记一个子集。

(4)删除所有完全空白的行(仅包含占位符)。

(5)在除了最后一行之外的每一行中,标记为决赛!对应于下一下一行中标记为“第一”的子集的位置中的子集。此标记表示“最后”。

现在我们可以列出子集的最终maxlifo排序。从上到下的每一行都是有序的,其元素按顺序添加到输出列表中。

(6)从上到下的每一行:

(6.1)删除所有空白占位符。

(6.2)将标记为“first”(initial!)的子集添加到输出列表中,并将其从行中删除。

(6.3)如果行中仍有子集,则最左边或最右边的子集将被标记为“last”(最终!)。如果最右边的子集标记为“last”,则按从左到右的顺序将子集添加到输出列表,否则按从右到左的顺序添加。

(7)处理完所有行后,返回输出列表。

使用maxlifo(“abcdefg”,2)的例子:

Col1包含maxlifo的输出(“abcdefg”,1)。 Col2的行包含与s:

的剩余字符形成的派系
Col1    Col2
----    ----------------------------
a       ab   ac   ad   ae   af   ag
b       ab   bc   bd   be   bf   bg
c       ac   bc   cd   ce   cf   cg
d       ad   bd   cd   de   df   dg
e       ae   be   ce   de   ef   eg
f       af   bf   cf   df   ef   fg
g       ag   bg   cg   dg   eg   fg

删除显示在前一行中的子集:

a       ab   ac   ad   ae   af   ag
b            bc   bd   be   bf   bg
c                 cd   ce   cf   cg
d                      de   df   dg
e                           ef   eg
f                                fg
g                               

标记每行中的“第一个”子集(下面有一个空白的子集):

a      !ab   ac   ad   ae   af   ag
b           !bc   bd   be   bf   bg
c                !cd   ce   cf   cg
d                     !de   df   dg
e                          !ef   eg
f                               !fg
g                               

删除所有完全空白的行(在这种情况下只有一行):

a      !ab   ac   ad   ae   af   ag
b           !bc   bd   be   bf   bg
c                !cd   ce   cf   cg
d                     !de   df   dg
e                          !ef   eg
f                               !fg

标记每行中的“最后”子集(在其下方具有“第一个”子集的那个子集)。

a      !ab   ac!  ad   ae   af   ag
b           !bc   bd!  be   bf   bg
c                !cd   ce!  cf   cg
d                     !de   df!  dg
e                          !ef   eg!
f                               !fg

按照上述顺序输出每一行:'first',unmarked,'last':

                                               Ordered rows:
a      !ab   ac!  ad   ae   af   ag            ab ag af ae ad ac
b           !bc   bd!  be   bf   bg            bc bg bf be bd
c                !cd   ce!  cf   cg            cd cg cf ce
d                     !de   df!  dg            de dg df
e                          !ef   eg!           ef eg
f                               !fg            fg

输出:[ab ag af ad ac bc bg bf bf cd cg cf ce df dg de ef eg fg]

3&lt; = k&lt; = 6的实例更详细地给出。在步骤4中删除的空行将保留在原位。

maxlifo(“abcdefg”,3):

                                                       Ordered rows:
ab     !abc   abd   abe   abf   abg!                   abc abd abe abf abg
ag            acg   adg   aeg! !afg                    afg acg adg aeg
af            acf   adf! !aef                          aef acf adf
ae            ace! !ade                                ade ace
ad           !acd!                                     acd
ac                     
bc           !bcd   bce   bcf   bcg!                   bcd bce bcf bcg
bg                  bdg   beg! !bfg                    bfg bdg beg
bf                  bdf! !bef                          bef bdf
be                 !bde!                               bde 
bd                                  
cd                 !cde   cdf   cdg!                   cde cdf cdg
cg                        ceg! !cfg                    cfg ceg
cf                       !cef!                         cef
ce                             
de                       !def   deg!                   def deg
dg                             !dfg!                   dfg
df                             
ef                             !efg                    efg
eg                       
fg                       

输出:[abc abd abe abf abg afg acg adg aeg aef acf adf ade ace acd
    bcd bce bcf bcg bfg bdg beg bef bdf bde     cde cdf cdg cfg ceg cef     def deg dfg     EFG]

maxlifo(“abcdefg”,4):

                                            Ordered rows:
abc         !abcd  abce!  abcf   abcg       abcd abcg abcf abce
abd               !abde   abdf!  abdg       abde abdg abdf
abe                      !abef   abeg!      abef abeg
abf                             !abfg!      abfg
abg                                   
afg                acfg!  adfg  !aefg       aefg adfg acfg
acg               !acdg   aceg!             acdg aceg
adg                      !adeg!             adeg
aeg                                  
aef                acef! !adef              adef acef
acf               !acdf!                    acdf
adf                                  
ade               !acde!                    acde
ace                                   
acd                                   
bcd               !bcde   bcdf!  bcdg       bcde bcdg bcdf
bce                      !bcef   bceg!      bcef bceg
bcf                             !bcfg!      bcfg
bcg                                  
bfg                       bdfg! !befg       befg bdfg
bdg                      !bdeg!             bdeg
beg                                  
bef                      !bdef!             bdef
bdf                                  
bde                                  
cde                      !cdef   cdeg!      cdef cdeg
cdf                             !cdfg!      cdfg
cdg                                   
cfg                             !cefg!      cefg
ceg                                  
cef                                  
def                             !defg       defg
deg                          
dfg                          
efg                          

输出:[abcd abcg abcf abce abde abdg abdf abef abeg abfg aefg adfg acfg acdg aceg adeg adef acef acdf acde     bcde bcdg bcdf bcef bceg bcfg befg bdfg bdeg bdef     cdef cdeg cdfg cefg     DEFG]

maxlifo(“abcdefg”,5):

                                            Ordered rows:
abcd       !abcde   abcdf   abcdg!          abcde abcdf abcdg 
abcg                abceg! !abcfg           abcfg abceg 
abcf               !abcef!                  abcef 
abce                                
abde               !abdef   abdeg!          abdef abdeg 
abdg                       !abdfg!          abdfg 
abdf                         
abef                       !abefg!          abefg 
abeg                         
abfg                         
aefg                acefg! !adefg           adefg acefg 
adfg               !acdfg!                  acdfg 
acfg                         
acdg               !acdeg!                  acdeg 
aceg                         
adeg                         
adef               !acdef!                  acdef 
acef                         
acdf                         
acde                         
bcde               !bcdef   bcdeg!          bcdef bcdeg 
bcdg                       !bcdfg!          bcdfg 
bcdf                         
bcef                       !bcefg!          bcefg 
bceg                         
bcfg                         
befg                       !bdefg!          bdefg 
bdfg                         
bdeg                         
bdef                         
cdef                       !cdefg           cdefg 
cdeg                         
cdfg                         
cefg                         
defg                         

输出:[abcde abcdf abcdg abcfg abceg abcef abdef abdeg abdfg abefg adefg acefg acdfg acdeg acdef     bcdef bcdeg bcdfg bcefg bdefg     CDEFG]

maxlifo(“abcdefg”,6):

                                            Ordered rows:
abcde               !abcdef   abcdeg!       abcdef abcdeg 
abcdf                        !abcdfg!       abcdfg 
abcdg                               
abcfg                        !abcefg!       abcefg 
abceg                              
abcef                               
abdef                        !abdefg!       abdefg 
abdeg                               
abdfg                               
abefg                               
adefg                               
acefg                        !acdefg!       acdefg 
acdfg                               
acdeg                               
acdef                               
bcdef                        !bcdefg        bcdefg 
bcdeg                               
bcdfg                               
bcefg                               
bdefg                               
cdefg                        

输出:[abcdef abcdeg abcdfg abcefg abdefg acdefg bcdefg]

Unicon实施:

procedure maxlifo(s:string, k:integer)
# A solution to my combinatorics problem from 2010.
# Return a list of the k subsets of the characters of a string s
# in a minimal change order such that last-in first-out is maximised.
# String s must not contain duplicate characters and in the present 
# implementation must not contain "!", which is used as a marker.

  local ch, cand, Hit, inps, i, j, K, L, Outp, R, S

  # Errors
  if *cset(s) ~= *s then 
    stop("Duplicate characters in set in maxlifo(", s, ", ", k, ")")
  if find("!", s) then 
    stop("Illegal character in set in maxlifo(", s, ", ", k, ")")
  if k > *s then 
    stop("Subset size larger than set size in maxlifo(", s, ", ", k, ")")

  # Special cases
  if k = 0 then return []
  if k = *s then return [s]

  Outp := []
  if k = 1 then {
    every put(Outp, !s)
    return Outp
  }

  # Default case
  S := set()
  K := []

  # Build cliques from output of maxlifo(s, k-1) with the remaining 
  # characters in s, substituting empty strings as placeholders for 
  # subsets already listed.
  every inps := !maxlifo(s, k-1) do { 
    R := []
    every ch := !s do
      if not find(ch, inps) then {
        cand := reorder(inps ++ ch, s)
        if member(S, cand) then cand := "" else insert(S, cand)
        put(R, cand)
      }
    put(K, R)
  }

  # Mark ‘first’ subset in each row with initial "!"
  every i := 1 to *K - 1 do {
    every j := 1 to *K[i] do
      if K[i, j] ~== "" & K[i+1, j] == "" then {
        K[i, j] := "!" || K[i, j]
        break
      }
  }

  # Remove rows containing only placeholders
  every i := *K to 1 by -1 do {
    every if !K[i] ~== "" then break next
    delete(K, i)  
  }

  # Mark ‘last’ subset in each row with final "!"
  every i := 1 to *K - 1 do 
    every j := 1 to *K[i] do 
      if K[i+1, j][1] == "!" then {
        K[i, j] ||:= "!"
        break
      }

  # Build output list
  every R := !K do {

    # Delete placeholders from row (no longer needed and in the way)
    every j := *R to 1 by -1 do if R[j] == "" then delete(R, j)

    # Handle ‘first’ subset and remove from row
    # N.B. ‘First’ subset will be leftmost or rightmost in row
    if R[1][1] == "!" then 
      put(Outp, trim(get(R), '!', 0)) 
      else put(Outp, trim(pull(R), '!', 0))   

    # Handle any remaining subsets, ‘last’ subset last, stripping '!' markers
    # N.B. ‘Last’ subset will be leftmost or rightmost in row after removal
    # of ‘first’ subset.
    if R[-1][-1] == "!" then while put(Outp, trim(get(R), '!', 0)) else
      while put(Outp, trim(pull(R), '!', 0))
  }

  return Outp

end


procedure reorder(cs:cset, s:string)
# Reorder cset cs according to string s

  local r
  # If no s, return set in alphabetical order
  if /s then return string(cs)

  r := ""
  s ? while tab(upto(cs)) do r ||:= move(1)
  return r

end