【问题标题】:Creating all subsets recursively without using array不使用数组递归创建所有子集
【发布时间】:2018-10-28 15:41:39
【问题描述】:

我们从用户那里得到非负整数n,我们必须打印集合({1,2,3,...,n})的所有子集。 (n<=20)

例如对于n=3,我们必须打印:

{1 , 2 , 3}
{1 , 2}
{1 , 3}
{1}
{2 , 3}
{2}
{3}
{}

,s 是可选的,序列可以不带任何逗号打印。 (如 {1 2 3}) 我必须补充一点,子集的序列必须与示例完全相同。首先是具有 1 的子集,然后是具有 2 和 ... 的子集。必须首先打印最长的子集。 (从最大子集(集合本身)到空集的字典顺序)

我在网上看到很多代码用数组或位数组来解决这个问题,指示我们是否使用数字。问题是在这个问题中,我们不允许使用任何类型的数组或其他数据结构,如向量等。即使使用类似字符串的数组行为也是完全禁止的。只能通过递归来解决。

我们也不允许使用任何高级功能。例如,如果我们用C 编写它,我们只允许使用stdio.hC++,只允许使用<iostream>,不允许使用其他库。

如果没有任何数组,我不知道该怎么做。如何检查它必须打印哪个号码,同时管理{}

PS1。 问题只是具有以下条件的发电组:

完全禁止使用数组、字符串和偶数循环。只是递归。

用户 Kosyr 使用位运算符提交了一个非常好的答案。因此,如果您想提交另一个答案,请提交一个甚至不使用位运算符的答案。

PS2.

我在 George 的帮助下编写了这段代码,但它不能正常工作。它没有像 1 2 4 这样的东西。它还会重复某些情况。

#include <stdio.h>


void printAllSets (int size)
  {printRows (size, 1);}

void printRows (int size , int start)
{
  if (start<=size)
  {printf( "{ ");
  printRow (start, size);
  printf ("}");
  printf ("\n");}
  if (start <= size)
  {printRows(size -1 , start);
    printRows (size , (start + 1));}
}
printRow (int start, int limit)
{

  if (start <= limit)
  {

    printf ("%d ",start);
    printRow (start +1, limit);
  }
}


int main()
{
    printAllSets(5);
    printf("{ }");
    return 0;
}

PS3.

用户 Kosyr 使用位运算符提交了一个非常好的答案。因此,如果您想提交另一个答案,请提交一个甚至不使用位运算符的答案。

【问题讨论】:

  • 提示:打印所有长度为 0 的子集,然后打印所有长度为 1 的子集,以此类推,直到最后打印唯一长度为 n 的子集。
  • @thebjorn 我不知道该怎么做。我必须补充一点,子集的序列必须与示例完全相同。首先是具有 1 的子集,然后是具有 2 和 ... 的子集。必须首先打印最长的子集。不仅仅是长度为零。然后长度为 1 和 ...
  • n 的最大值是多少?

标签: algorithm recursion subset powerset


【解决方案1】:

递归算法非常占用内存。这里算法为n &lt;= 31

#include <iostream>

void bin(unsigned bit, unsigned k, unsigned max_bits) {
    if (bit == max_bits) {
        std::cout << "{";
        bin(bit - 1, k, max_bits);
    }
    else {
        if ((k & (1u << bit)) != 0) {
            std::cout << (max_bits - bit) << " ";
        }
        if (bit != 0) {
            bin(bit - 1, k, max_bits);
        }
        else {
            std::cout << "}" << std::endl;
        }
    }
}

void print(unsigned k, unsigned n, unsigned max_bits) {
    bin(max_bits, k, max_bits);
    if (k != 0) {
        print(k - 1, n, max_bits);
    }
}

int main()
{
    unsigned n;
    std::cin >> n;
    print((1u << n) - 1u, 1u<<n, n);
    return 0;
}

第一次递归print 枚举k2^n-10,第二次递归bin 枚举k 的所有位并打印非零位。例如,max_bits = 5k = 1910011b = 16 + 2 + 1 = 2^4 + 2^1 + 2^0,位4,1,0 互操作为集合{5-4,5-1,5-0} =&gt; {1,4,5}

【讨论】:

  • 非常感谢。但是你可以编辑吗,它会先打印最大的。我的意思是像我的例子一样,按字典顺序从最大子集(集合本身)到空集?我是编程新手,这是我第一次需要使用这些位运算符。还有 n
  • 还有一个问题:没有位运算符有没有办法解决这个问题? (并且也不使用循环和数组)。我的意思是像河内塔问题?我还必须添加 n
  • 更新了字典排序的源代码。没有数组和位我无法递归解决它,但n&lt;=20 提示我们使用位:)
  • 再次感谢您!如果两天内没有答案,我将接受您的答案作为最佳答案。再次感谢您。
  • 递归算法可以被充分优化的编译器或解释器翻译,如果它们被编写为纯函数,使用称为“尾调用优化”的技术仅在执行路径的末尾递归。
【解决方案2】:

循环的替代方法是递归。

为了解决这个问题(我认为......还没有测试过),我通过将样本日期制成表格来调查问题,并识别了三个状态,SizeStartLimit,进展如下:

Size  Start Limit   Output
  10      1    10    1..10
  10      1     9     1..9
              ...      ...
  10      1     1        1
  10      2    10    2..10
  10      2     9     2..9
              ...      ...
  10      2     2        2
        ...   ...      ...
  10     10    10       10

以下伪代码中的递归算法可以解决问题:

printAllSets size
  printRows size 1

printRows size start
  print "{"
  printRow start size
  print "}"
  print CRLF
  if start <= size
    printRows size (start + 1)

printRow start limit
  if start <= limit
    print start + SPACE
    printRow start (limit - 1)

希望这至少可以帮助您指明正确的方向。

【讨论】:

  • 谢谢。你的伪代码有一些我在我的程序中编辑的问题。但是您的答案不包括 1 2 3 4 5 7 8 9 10 之类的内容。(它没有 6,但有所有其他数字)。无论如何,非常感谢。
  • @titansarus 您的问题特别指出它是 [1,n] 范围内的一组整数。按照这个公式,你的 1 2 3 4 5 7 8 9 10 困境是没有意义的。
  • @erip 我说对于 n = 10。例如 {1 , 2 , 3 ,4 , 5 ,7 , 8 , 9 ,10} 是 {1 , 2, .. . , 10} 但上述算法不会生成。我的问题是生成没有循环、数组和字符串的 Power Set ......只使用纯递归。我在互联网上检查了很多算法,但即使是最简单的算法也至少使用了循环或字符串。我没有发现任何东西可以同时满足我的所有问题条件。
  • @titansarus 经过一番思考,我添加了另一个答案。尽管没有优化尾调用,但它相当优雅(您可能可以贡献一些东西)。它根据powerset k 定义powerset (k+1)
【解决方案3】:

我认为我们可以迭代地解决这个问题,我们可以假设也可以将其转换为递归,尽管这似乎没有必要。考虑到我们可以使用常识对给定索引的任何组合进行排序。所以我们需要做的就是计算我们跳过了多少早期组合,以及在迭代的每个阶段我们需要取消排名多少(我可能遗漏了以下内容,但我认为总体思路是合理的):

Skip 0, unrank from `3 choose 3`
`2 choose 2` combinations
{1 , 2 , 3} 

Skip 0, unrank from `3 choose 2`
`2 choose 1` combinations
{1 , 2}
{1 , 3}

Skip 0, unrank from `3 choose 1`
`2 choose 0` combinations
{1}

Skip `3 choose 2 - 2 choose 2`,
unrank from `3 choose 2`
`1 choose 1` combinations
{2 , 3}

Skip `3 choose 1 - 2 choose 1`,
unrank from `3 choose 1`
`1 choose 0` combinations
{2}

Skip `3 choose 1 - 1 choose 1`,
unrank from `3 choose 1`
`0 choose 0` combinations
{3}

Empty set
{}

【讨论】:

    【解决方案4】:

    根据定义,集合kpowerset k 的幂集是包含来自给定集合的元素的所有可能集合的集合,包括空集合本身。显然,当k 是空集时,powerset [] 只是包含空集[ [] ] 的集合。现在,给定一个幂集kpowerset kk 的幂集加上一个附加元素Epowerset (K+E),将包括所有可能的集合,其中包含没有Epowerset k 的元素,加上那些相同的元素,除了现在都包含E

    伪代码...

    let powerset k =
       match k with
       | [] -> [[]]
       | x:xs -> x * powerset xs + powerset xs
    

    或具有等效的尾调用性能

    let powerset k =
       let (*) e ess = map (es -> e::es) ess
       reduce (e ess -> (e * ess) ++ ess) [ [] ] (reverse k)
    

    ....(在 F# 中)

    let rec powerset k =
      match k with
      | []    -> [ [] ]
      | x::xs -> 
          let withoutE = powerset xs
          let withE = List.map (fun es -> x::es) withoutE
          List.append withE withoutE
    

    或者更简洁

    let rec powerset = function
      | []    -> [ [] ]
      | x::xs -> List.append (List.map (fun es -> x::es) (powerset xs)) (powerset xs)
    

    更好的版本将允许尾调用优化...我们使用通用功能模式实现了这一点:

    let rec powerset2 k = 
      let inline (++) a b = List.concat [a;b]
      let inline (+*) a bs = List.map (fun b -> a::b) bs
      List.fold 
        (fun ac a -> (a +* ac) ++ ac)
        [ [] ] 
        (List.rev k)
    

    -- 这一切都花了我一段时间才重新发现。这是一个有趣的小谜题。 :)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-04-18
      • 1970-01-01
      • 2017-07-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-01
      相关资源
      最近更新 更多