打印可以由一组n个字符组成的长度为k的所有排列?

时间:2017-01-05 13:13:16

标签: java c++ string algorithm combinatorics

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);
            }
        }
    } 
}

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

输出:

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 

到目前为止,我正试图找到一个解决方案并且没有任何内容。 Java,C ++或伪代码的解决方案没关系。 感谢

2 个答案:

答案 0 :(得分:1)

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

例如,如果长度为{ 0, 1, 2 },则从0022,基数为3。

要解决“仅一位数更改”约束,而不是逐渐计算,只有在下一个10 th 更改时才这样做。然后逐渐减少,然后再增加等......

例如在上面的例子中

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上,保持相同的推理,更改下一个10 th 然后根据当前数字的初始值上升或下降

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

递归算法是一种方法。函数深度0负责第一个(左)数字,即最高10 th ,并根据其当前数字状态向上或向下计数。如果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

请注意,您应始终获得N k 数字......

这是产生上述内容的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");
        }
    }
}
<{1>} 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--;
        }
    }
}

答案 1 :(得分:0)

您只需更改最不重要元素的迭代方向即可。如果您要为容器生成排列,则可以反转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