【问题标题】:Permutations with output limitation in C++C++ 中具有输出限制的排列
【发布时间】:2023-03-13 09:27:01
【问题描述】:

我正在尝试对 8 个字符进行排列,但我只对最多包含 3 个相同字符的输出感兴趣。因此,应跳过包含超过 3 次出现的任何字符的任何输出。
字符集:a、b、c、d、e、f、g、G
示例:
对输出不感兴趣,例如aaaaaaab , aabcdeaa, acdGGGGg, GGGGbbbb ...
对输出感兴趣,例如abcdefgG, aaabcdef, abacadGf ...

我尝试编写一个代码,在每个循环中评估每个字符的出现次数,如果出现超过 3 个相同的字符,则跳过(中断/继续)到下一个循环。
这是我无法解决的代码问题。该程序仅执行以字符“a”开头并在 aaabgGGG 处停止的排列,我无法管理它以继续以 b、c、d、e 等开头的迭代...
我想在循环期间实现过滤以避免发生不需要的循环 => 尽可能快地处理。
注释 ##### 行之间的“>3 次出现过滤器”代码时,所有排列都被正确处理。 我的代码:

#include <iostream>

// C++ program to print all  possible strings of length k 
using namespace std;

int procbreak = 0;

// The main recursive method to print all possible  strings of length k 
void printAllKLengthRec(char set[], int setn[], string prefix, int n, int k)
{
    // Base case: k is 0, print prefix 
    //cout << "03. In printAllKLengthRec function" << endl;
    if (k == 0)
    {
        //print table with characters and their count
        cout << (prefix) << endl;
        cout << " | ";
        for (size_t b = 0; b < 8; b++)
        {
            cout << set[b] << " | ";
        }
        cout << endl;
        cout << " | ";
        for (size_t c = 0; c < 8; c++)
        {
            cout << setn[c] << " | ";
        }
        cout << endl;

        return;
    }

    // One by one add all characters from set and recursively call for k equals to k-1 
    for (int i = 0; i < n; i++)
    {
        cout << "04. In for loop where one by one all chars are added. K = " << k << "; I = " << i << "; N = " << n << endl;

        string newPrefix;

        //update characters count table
        setn[i] += 1;
        if (i > 0)
        {
            setn[i - 1] -= 1;
        }
        else
        {
            if (setn[7] > 0)
            {
                setn[7] -= 1;
            }
        }

        //#############################################################################################
        //check if there is any character in a table with count more than 3, then break current cycle
        for (size_t d = 0; d < 8; d++)
        {
            if (setn[d] > 3)
            {
                procbreak = 1;
                break;          // enough to find one char with >3, then we don't need to continue and break operation
            }
        }

        if (procbreak == 1)
        {
            procbreak = 0;      // reset procbreak
            continue;           // skip to next cycle
        }
        //#############################################################################################

        // Next character of input added 
        newPrefix = prefix + set[i];

        // k is decreased, because  we have added a new character 
        printAllKLengthRec(set, setn, newPrefix, n, k - 1);
    }
}

void printAllKLength(char set[],int setn[], int k, int n)
{
    cout << "02. In printAllKLength function" << endl;
    printAllKLengthRec(set, setn, "", n, k);
}

// Main code 
int main()
{
    cout << "Start" << endl;
    char set1[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'G' };
    int setn[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
    int k = 8;                                                  // string length
    printAllKLength(set1, setn, k, 8);                          // 8 = n => number of characters in the set1
}

我的代码逻辑中的主要错误在哪里?

【问题讨论】:

  • 您正在寻找组合而不是排列。 8 个字符的排列都只包含 8 个字符中的每一个。
  • 我不同意,据我所知,这是“重复排列”。
  • @enki:C++甚至还有一个函数std::is_permutation,和idclev 463035818一致。

标签: c++ permutation


【解决方案1】:

您的问题的解决方案非常简单。

你要做的是取你的字符集:a、b、c、d、e、f、g、G

并构造一个“假”序列,每个字符重复三次。

std::string perm{"GGGaaabbbcccdddeeefffggg"};

这里的关键见解是您可以像往常一样计算排列,例如,使用std::next_permutation。您只需从该排列中获取前 8 个元素即可获得所需的结果。

[编辑:为了避免计算最右边 16 个值的排列,因为这些总是会产生最左边 8 个值的重复,因此在每一步之后将最右边的 16 个值设置为最后一个排列。对std::next_permutation 的下一次调用将置换前 8 个值。]

[Edit2:工作示例

#include <algorithm>
#include <chrono>
#include <iostream>

int main()
{
  // Initial state
  std::string perm{"GGGaaabbbcccdddeeefffggg"};

  using clock = std::chrono::steady_clock;
  auto start = clock::now();
  do
  {
    // Output permutation
    std::cout << perm.substr(0, 8) << "\n";

    // Now reverse the last 16 values, so that the call to the next_permutation would change the top 8
    std::reverse(std::next(perm.begin(), 8), perm.end());

  } while (std::next_permutation(perm.begin(), perm.end()));

  std::clog << "Elapsed: " << std::chrono::duration_cast<std::chrono::milliseconds>(clock::now() - start).count() << "ms\n";
  return 0;
}

]

【讨论】:

  • 如果你只取前 8 个,这会有很多重复,不是吗?
  • 如果我正确理解了您的三重解法,那么处理的排列数将从 16,777,216 增加到 110,075,314,176,超过 ~6500 倍。 => 处理它的时间显着增加。
  • 不确定您是如何得出这些数字的。他们对我没有多大意义。问题空间本身是 24 维的,任何从 S^24->S^8 的解映射都会进行相同的渐近运算数。你甚至可以计算你的普通 S^8 排列,但是后处理中的三重化本身必须重新映射到 S^24 并返回。
  • 哇,令人印象深刻。您的代码要快得多。 6 秒与我的代码 140 秒。说什么。 :-) 感谢您的指导和示例。
【解决方案2】:

我发现过滤的问题出在哪里...
整个排列是通过在循环内运行循环来完成的,换句话说,函数正在调用自己。

当从右手字符(最右边)传递到左手字符(向左一步)时,函数正在执行空的 'k' 循环(从位置 8 到 7 时,有 1 个空的 'k' 循环...... .. 从位置 2 到 1 时最多 7 个空的“k”循环)。

12345678

我的初始代码是在每个空的“k”个循环中评估每个字符的计数。
这就是问题所在。

在空的“k”周期中,每个字符的计数都在变化,当空周期结束时,字符的计数是真实的,并且应该是真实的。

因此解决方案是,对每个字符的计数进行评估,如果任何字符的计数 >3,则仅在 k = 1 时中断最后一个循环。 我在第一个空循环中打破了循环,其中字符串中的字符数不正确。

01. In for loop where one by one all chars are added. K = 1; I = 7; N = 8   <--- OK, loop when the last G was added to form string aaaaabGG
table in for loop
 | a | b | c | d | e | f | g | G |
 | 5 | 1 | 0 | 0 | 0 | 0 | 0 | 2 |
aaaaabGG                                    <--- aaaaabGG was formed
table in base                                   <--- aaaaabGG shown in the final output
 | a | b | c | d | e | f | g | G |
 | 5 | 1 | 0 | 0 | 0 | 0 | 0 | 2 |
02. In for loop where one by one all chars are added. K = 3; I = 2; N = 8   <--- going one character UP, next string after aaaaabGG should be aaaaacaa  
table in for loop
 | a | b | c | d | e | f | g | G |
 | 5 | 0 | 1 | 0 | 0 | 0 | 0 | 2 |                      <--- but as we can see, during the K = 3 empty loop, the string is aaaaacGG (updates only 3rd char from left)                       
03. In for loop where one by one all chars are added. K = 2; I = 0; N = 8   <--- second empty loop K = 2
table in for loop
 | a | b | c | d | e | f | g | G |
 | 6 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |                      <--- as we can see, during the K = 2 empty loop, the string is updating and is now aaaaacaG (now updates only 2nd char from left, 3rd is OK from previous empty loop)
04. In for loop where one by one all chars are added. K = 1; I = 0; N = 8   <--- Last loop K = 1 (string is updated 1st character in the left only, 2nd and 3rd were updated in previous empty loops respectively)
table in for loop
 | a | b | c | d | e | f | g | G |
 | 7 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
aaaaacaa                                    <--- we can see that now the string is as it should be aaaaacaa
table in base                                   <--- aaaaacaa shown in the final output
 | a | b | c | d | e | f | g | G |
 | 7 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |

【讨论】:

  • 这仍然是一种非常复杂的方法。我已经解决了这个问题,我建议使用std::next_permutation 进行三倍法,除了一个转折:因为我们只关心排列的最左边的 8 个值,我们可以通过设置最右边的 16 个值到最后一个排列(这可以通过反向排序或其他方式实现)。然后对next_permutation 的下一次调用将置换最左边的值等。此外,这也不会产生重复。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多