【问题标题】:Print all permutation of length k that can be formed from a set of n characters?打印可以由一组 n 个字符组成的所有长度为 k 的排列?
【发布时间】:2017-05-20 01:21:49
【问题描述】:
public class PrintStrLenK {
    public static void main(String[] args){
        int k = 2;
        char[] set = {'0', '1'};
        char[] str = new char[k];
        generate(k, set, str, 0);
    }

        static void generate(int k, char[] set, char[] str, int index){
            if (index == k){
                System.out.println(new String(str));
            }
            else {
                for (int i = 0; i < set.length; i++){
                    str[index] = set[i];
                    generate(k, set, str, index + 1);
            }
        }
    } 
}

我找到了这段代码,问题是我被要求在排列之间只改变一个字符

输出:

00
01
02
03
10 --> 2 Char changes. Not OK.
11
12
13
20 --> 2 Char changes. Not OK.
21
22
23
30 --> 2 Char changes. Not OK.
31
32
33

应该是

00
01
02
03
13 --> 1 Char change. OK
12
11
10
20 -- > 1 Char change. OK
21
22
23
33 -- > 1 Char change. OK
32
31
30

它必须适用于不同的集合和 k。例如

set = {'0', '1'} and k= 3.
000 001 011 010 110 111 101 100 

set = {'0', '1','2','3'} and k= 3.
000 001 002 003 013 012 011 010 020 021 022 023 033 032 031 030 130 131 132 133 123 122 121 120 110 111 112 113 103 102 101 100 200 201 202 203 213 212 211 210 220 221 222 223 233 232 231 230 330 331 332 333 323 322 321 320 310 311 312 313 303 302 301 300 

我试图找到解决方案已经 2 天了,但到目前为止还没有。解决方案的 Java、C++ 或伪代码都可以。 谢谢

【问题讨论】:

  • 那么,您的问题到底是什么?您发布的代码有什么问题?既然代码是 Java,为什么还要标记 C++?目前还不清楚您到底在问什么。
  • @AlgirdasPreidžius 第一个输出,如果你运行上面的代码得到的,是错误的。因为它应该像帖子中文本“应该是”之后的输出。如您所见,从 03 到 10,2 char 值正在发生变化。我被要求做的是只更改 1 个字符。 03 应该更改为 13,只有 char 0 更改为 1。我已标记 C++,因为如果有人在 C++ 中发布解决方案,则可以。
  • 因此,您的任务是进行修改。您尝试过任何方法吗?还是您希望我们 为您编写代码? SO 不是代码编写服务。相关阅读:Open letter to students with homework problems.
  • @AlgirdasPreidžius 是的,我试过了,您不必编写代码。你可以给我一些想法或尝试帮助我。如果我无法寻求解决方案或帮助,这个网站是关于什么的??
  • @Mattia 是的,你措辞的方式可能没有帮助。 FWIW,我不是投反对票的人之一,而且由于看来您现在已经在 ringø 的帮助下找到了答案,所以我取消了其中一个。

标签: java c++ string algorithm combinatorics


【解决方案1】:

这个问题实际上就像在 sizeof(set) 的长度 k 上计数(假设集合最多有 10 个项目)。

例如,对于长度为 2 的一组 { 0, 1, 2 },您从 00 计数到 22,基数为 3。

要解决“仅一位数变化”的限制,而不是增加计数,只在下一个 10th 变化之前这样做。然后减少计数,然后再次增加等等......

例如上面的例子

00 -> 02 then increase the next tenth (12), then count downward
12 -> 10 then again +10 to get 20, then go up again
20 -> 22

在长度3上,保持相同的推理,改变下10th然后根据当前数字的初始值向上或向下

000 -> 002, 012 -> 010, 020 -> 022
122 -> 120, 110 -> 112, 102 -> 100
200 -> 202, 212 -> 210, 220 -> 222

递归算法是一种方法。函数 depth 0 处理第一个(左)数字,即最高的 10th,并根据其当前数字状态向上或向下计数。如果0,则向上计数,否则向下计数。对于每个状态,在递增之前,函数使用下一个(右)数字状态(即0 或集合中的最后一项)递归调用自身。最大深度是长度k

将数字状态保存在长度为 k 的数组中。数组初始化为{0 ... 0}。为函数指定数组中的索引(从 0 开始)。对于每次迭代,如果我们处于最大深度(即i == k-1),则打印数组;否则使用i+1递归调用函数。

伪代码

k length of number (number of digits)
N size of set (1 .. 10), values from 0 to N-1
A array of size k

A[0 .. k-1] = 0

function f ( i )
begin
   inc = -1 if (A[i] > 0), 1 otherwise     # Increment
   end =  0 if (A[i] > 0), N-1 otherwise   # Max value
   j is the counter

   j = A[ i ]   # Init
   while ( (inc<0 AND j>=end) OR (inc>0 AND j<=end) )
   do
        A[ i ] = j

        if (i < k-1) call f ( i+1 )
        otherwise print array A

        j = j + inc
   done
end

call f ( 0 )

这是你应该得到的N = 3, and k = 4

0000 0001 0002 0012 0011 0010 0020 0021 0022
0122 0121 0120 0110 0111 0112 0102 0101 0100
0200 0201 0202 0212 0211 0210 0220 0221 0222
1222 1221 1220 1210 1211 1212 1202 1201 1200
1100 1101 1102 1112 1111 1110 1120 1121 1122
1022 1021 1020 1010 1011 1012 1002 1001 1000
2000 2001 2002 2012 2011 2010 2020 2021 2022
2122 2121 2120 2110 2111 2112 2102 2101 2100
2200 2201 2202 2212 2211 2210 2220 2221 2222

请注意,您应该始终获得 Nk 个数字...


这是生成上述代码的 C 代码:

int a[20] = {0}; // Put here the right size instead of 20, or use #define...
int N,k;

void f(int i) {
    int inc = a[i] ? -1:1;
    int end = a[i] ? 0:N-1;
    int j;

    for(j=a[i] ; inc<0 && j>=end || inc>0 && j<=end ; j+=inc) {
        a[i] = j;
        if (i < k-1) f(i+1);
        else {
            int z;
            for(z=0 ; z<k ; z++) printf("%d", a[z]);
            printf("\n");
        }
    }
}

main()初始化Nk并调用

f(0);

一个迭代版本,基本上做同样的事情

void fi() {
    int z,i,inc[k];

    for(i=0 ; i<k ; i++) {
      a[i] = 0;     // initialize our array if needed
      inc[i] = 1;   // all digits are in +1 mode
    }
    int p = k-1;    // p, position: start from last digit (right)

    while(p >= 0) {
        if (p == k-1) {
            for(z=0 ; z<k ; z++) printf("%d", a[z]);
            printf("\n");
        }
        if (inc[p]<0 && a[p]>0 || inc[p]>0 && a[p]<N-1) {
          a[p] += inc[p];
          p = k-1;
        }
        else {
          inc[p] = -inc[p];
          p--;
        }
    }
}

【讨论】:

  • 非常有意义。太感谢了。我会试着把它翻译成代码。
  • 一个小时过去了:如果伪代码不够清晰,请添加生成上述结果的C代码。
  • 这很清楚,但我在将其翻译成 Java 时犯了一些错误,正如您在此处看到的 ideone.com/TMRPJz,我正在尝试修复它。现在更容易从 C 中修复它。谢谢
  • 嗯,尝试将 set 初始化为 {0,0,0} 以获得更清晰的结果...算法假设每个数字都在 0之间> 和 sizeof(set)-1
  • 好的,我修复了它ideone.com/bjalbn。我将尝试通过创建 N,k 和数组全局变量而不是将它们传递给函数来使它变得更好(我是 Java 新手)。完成后我会更新主帖。非常感谢您的帮助和算法。
【解决方案2】:

您只是在更改最不重要元素的迭代方向。如果要生成容器的排列,则可以每隔一个 size(set) 排列颠倒 size(set) 排列的顺序。

另一种方法是编写您自己的置换生成器来为您解决这个问题。例如,在 C++ 中,一个简单的置换生成器和打印机如下所示:

vector<vector<int>::const_iterator> its(k, cbegin(set));

do {
    transform(cbegin(its), cend(its), ostream_iterator<int>(cout), [](const auto& i) { return *i; });

    cout << endl;

    for (auto it = rbegin(its); it != rend(its) && ++*it == cend(set); ++it) *it = cbegin(set);
} while (count(cbegin(its), cend(its), cbegin(set)) != k);

Live Example

您需要进行的修改是在每次到达集合末尾时交替最不重要的迭代器的迭代方向,如下所示:

vector<vector<int>::const_iterator> its(k, cbegin(set));
vector<bool> ns(k);

for(int i = k - 1; its.front() != cend(set); its[i] = next(its[i], ns[i] ? -1 : 1), i = k - 1) {
    transform(cbegin(its), cend(its), ostream_iterator<int>(cout), [](const auto& i) { return *i; });

    cout << endl;

    while (i > 0 && (!ns[i] && its[i] == prev(cend(set)) || ns[i] && its[i] == cbegin(set))) {
        ns[i] = !ns[i];
        --i;
    }
}

Live Example

【讨论】:

  • 谢谢,您的解决方案将花费我更多时间,因为它使用向量和迭代器。我过去没怎么用过这个库。
  • @Mattia 这是一个有趣的问题(+1 你可能刚刚得到了反对票,因为你包括了c++)所以我继续编写了一个可能的答案。您会注意到它大大增加了复杂性,因此如果您可以选择避免这样做,那将是值得的。
  • 避免这样做是什么意思?以“标准”方式进行操作?例如,当它达到 03 时,它是 10 而不是 13?我必须使用“仅一位数更改”约束来做到这一点。我在互联网上到处搜索以找到解决方案而没有在这里询问,我可能会得到反对票,因为我按照你说的那样包含了 c++。
  • @Mattia 哦,我的意思是避免约束。看看我展示的两个代码示例之间增加的复杂性!这只是更多的工作。无论如何,是的,c++ 对于不必要的投票是残酷的。不过,坚持下去,这个网站上有很多很好的知识。
  • 您的第二个解决方案一直有效到030。应该是030 --> 130 而不是030 --> 100。我会尝试自己查找错误或使用之前发布的伪代码解决方案。
猜你喜欢
  • 2018-12-12
  • 1970-01-01
  • 2020-08-18
  • 1970-01-01
  • 1970-01-01
  • 2019-07-13
  • 2023-04-10
  • 2018-10-14
相关资源
最近更新 更多