首页 文章

最小化'swapping'的最小变化算法

提问于
浏览
7

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

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

带星号的字符串表示我想要最大化的情况;例如第3代abe中的新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
ADE
bde cde
cdf cdg
CEG
deg(交换分数= 21)

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

5 回答

  • 1

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

    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你可能还有很多计算在前面 .

  • 0

    这很简单 . 为了最大化替换,只需将字符视为数字并将字符串递增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));
    }
    
  • 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> 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”,则按从左到右的顺序将子集添加到输出列表,否则按从右到左的顺序 .

    (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 ae ac bc bg bf bf cd cg cf ce df dg de ef eg fg]

    3 <= k <= 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 bcef bcfg befg bdfg bdeg bdef cdef cdef cfg 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
    
  • 0

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

    从大学开始已经有很长一段时间了,但是我试着想出一个有助于弄清楚这一点的组合模型,没有太多运气 .

    我首先想象组合的集合作为图形中的顶点,边缘对应于组合的“邻接”(仅一个元素差异) . 所以:

    • "n choose k"个顶点

    • 每个顶点都有度k(n-k)

    • 边数= "n choose k" * k(n-k)/ 2 = "n choose 2" * "n-2 choose 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维超立方体角落的点 . 因此,对于给定的子序列,如果它们是共面的,则边缘匹配您的“最小交换”标准:我将它们视为通过超立方体的“切割平面” .

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

    另一种思考问题的方法是最小化序列中您更改组合中哪个字符被更改的次数 .

  • 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]
    

相关问题