51
void permute(string elems, int mid, int end)
{
    static int count;
    if (mid == end) {
        cout << ++count << " : " << elems << endl;
        return ;
    }
    else {
    for (int i = mid; i <= end; i++) {
            swap(elems, mid, i);
            permute(elems, mid + 1, end);
            swap(elems, mid, i);
        }
    }
}

上面的函数显示了str(withstr[0..mid-1]作为一个稳定的前缀和str[mid..end]作为一个可置换的后缀) 的排列。所以我们可以用它permute(str, 0, str.size() - 1)来显示一个字符串的所有排列。

但是该函数使用递归算法;也许它的性能可以提高?

有没有更好的方法来置换字符串?

4

20 回答 20

64

这是 Wikipedia entry for unordered generation of permutations中的 C++ 中的非递归算法。s对于长度的字符串n,对于任何k0n! - 1包含,以下修改s以提供唯一的排列(即,不同于为该范围内的任何其他 k 值生成的排列)。要生成所有排列,请为所有 n 运行它!ks 的原始值的值。

#include <algorithm>

void permutation(int k, string &s) 
{
    for(int j = 1; j < s.size(); ++j) 
    {
        std::swap(s[k % (j + 1)], s[j]); 
        k = k / (j + 1);
    }
}

这里swap(s, i, j)交换字符串 s 的位置 i 和 j。

于 2010-01-09T00:18:57.623 回答
51

你为什么不试试std::next_permutation()std::prev_permutation()

链接:

std::next_permutation()
std::prev_permutation()

一个简单的例子:

#include<string>
#include<iostream>
#include<algorithm>

int main()
{
   std::string s="123";
   do
   {

      std::cout<<s<<std::endl;

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

输出:

123
132
213
231
312
321
于 2010-01-03T15:53:13.877 回答
24

我想支持Permaquid 的回答。他引用的算法与已经提供的各种排列枚举算法的工作方式完全不同。它不会生成 n 个对象的所有排列,它会生成一个不同的特定排列,给定一个介于0 and n!-1. 如果您只需要一个特定的排列,它比枚举它们然后选择一个要快得多。

即使您确实需要所有排列,它也提供了单个排列枚举算法不需要的选项。我曾经写过一个强力密码破解器,它尝试了所有可能的字母到数字的分配。对于base-10问题,这是足够的,因为只有10!排列可以尝试。但是对于base-11问题需要几分钟,而base-12问题需要将近一个小时。

i=0--to--N-1我使用 Permaquid 引用的算法,用一个简单的 for 循环替换了我一直使用的排列枚举算法。结果只是稍微慢了一点。但随后我将整数范围分成四等份,并同时运行四个 for 循环,每个循环都在一个单独的线程中。在我的四核处理器上,生成的程序运行速度几乎是原来的四倍。

正如使用排列枚举算法很难找到一个单独的排列一样,生成所有排列集合的描绘子集也很困难。Permaquid 引用的算法使这两个都变得非常简单

于 2010-01-13T22:47:20.583 回答
11

特别是,您需要std::next_permutation

void permute(string elems, int mid, int end)
{
  int count = 0;
  while(next_permutation(elems.begin()+mid, elems.end()))
    cout << << ++count << " : " << elems << endl;
}

... 或类似的东西...

于 2010-01-03T15:51:54.540 回答
4

任何生成排列的算法都将在多项式时间内运行,因为 n 长度字符串中字符的排列数为(n!). 也就是说,有一些非常简单的就地算法来生成排列。查看Johnson-Trotter 算法

于 2010-01-04T13:41:07.807 回答
4

Knuth 随机洗牌算法值得研究。

// In-place shuffle of char array
void shuffle(char array[], int n)
{
    for ( ; n > 1; n--)
    {
        // Pick a random element to move to the end
        int k = rand() % n;  // 0 <= k <= n-1  

        // Simple swap of variables
        char tmp = array[k];
        array[k] = array[n-1];
        array[n-1] = tmp;
    }
}
于 2010-01-14T00:35:30.020 回答
3

任何使用或生成所有排列的算法都将花费 O(N!*N) 时间,至少 O(N!) 来生成所有排列和 O(N) 来使用结果,这真的很慢。请注意,打印字符串也是 O(N) afaik。

无论您使用什么方法,您实际上只能在一秒钟内处理最多 10 或 11 个字符的字符串。由于 11!*11 = 439084800 次迭代(在大多数机器上在一秒钟内完成这么多的迭代)和 12!*12 = 5748019200 次迭代。因此,即使是最快的实现,12 个字符也需要大约 30 到 60 秒。

Factorial 增长太快,以至于您无法希望通过编写更快的实现来获得任何东西,您最多只能获得一个字符。所以我建议 Prasoon 的建议。它很容易编码,而且速度非常快。尽管坚持使用您的代码也完全没问题。

我只是建议您注意不要在字符串中无意中包含额外的字符,例如空字符。因为这会使您的代码变慢 N 倍。

于 2010-01-08T21:41:31.327 回答
1

我最近写了一个置换算法。它使用 T 类型的向量(模板)而不是字符串,而且它不是超快的,因为它使用递归并且有很多复制。但也许您可以从代码中汲取一些灵感。你可以在这里找到代码。

于 2010-01-10T20:14:21.773 回答
1

显着提高性能的唯一方法是首先找到一种避免遍历所有排列的方法!

置换是一个不可避免的缓慢操作(O(n!),或者更糟,取决于你对每个置换所做的事情),不幸的是你无能为力改变这个事实。

另外,请注意,任何现代编译器都会在启用优化时使您的递归变平,因此手动优化带来的(小)性能提升会进一步降低。

于 2010-01-11T15:25:22.787 回答
1

你想遍历所有的排列,还是计算排列的数量?

对于前者,请std::next_permutation按照其他人的建议使用。每个排列都需要 O(N) 时间(但摊销时间更少),除了它的调用帧之外没有内存,而递归函数需要 O(N) 时间和 O(N) 内存。整个过程是 O(N!),正如其他人所说,你不能做得比这更好,因为你不能在少于 O(X) 的时间内从程序中获得超过 O(X) 的结果!无论如何,如果没有量子计算机。

对于后者,您只需要知道字符串中有多少个唯一元素。

big_int count_permutations( string s ) {
    big_int divisor = 1;
    sort( s.begin(), s.end() );
    for ( string::iterator pen = s.begin(); pen != s.end(); ) {
        size_t cnt = 0;
        char value = * pen;
        while ( pen != s.end() && * pen == value ) ++ cnt, ++ pen;
        divisor *= big_int::factorial( cnt );
    }
    return big_int::factorial( s.size() ) / divisor;
}

速度受限于查找重复元素的操作,对于chars,可以使用查找表在 O(N) 时间内完成。

于 2010-01-14T01:23:01.843 回答
1

我不认为这更好,但它确实有效并且不使用递归:

#include <iostream>
#include <stdexcept>
#include <tr1/cstdint>

::std::uint64_t fact(unsigned int v)
{
   ::std::uint64_t output = 1;
   for (unsigned int i = 2; i <= v; ++i) {
      output *= i;
   }
   return output;
}

void permute(const ::std::string &s)
{
   using ::std::cout;
   using ::std::uint64_t;
   typedef ::std::string::size_type size_t;

   static unsigned int max_size = 20;  // 21! > 2^64

   const size_t strsize = s.size();

   if (strsize > max_size) {
      throw ::std::overflow_error("This function can only permute strings of size 20 or less.");
   } else if (strsize < 1) {
      return;
   } else if (strsize == 1) {
      cout << "0 : " << s << '\n';
   } else {
      const uint64_t num_perms = fact(s.size());
      // Go through each permutation one-by-one
      for (uint64_t perm = 0; perm < num_perms; ++perm) {
         // The indexes of the original characters in the new permutation
         size_t idxs[max_size];

         // The indexes of the original characters in the new permutation in
         // terms of the list remaining after the first n characters are pulled
         // out.
         size_t residuals[max_size];

         // We use div to pull our permutation number apart into a set of
         // indexes.  This holds what's left of the permutation number.
         uint64_t permleft = perm;

         // For a given permutation figure out which character from the original
         // goes in each slot in the new permutation.  We start assuming that
         // any character could go in any slot, then narrow it down to the
         // remaining characters with each step.
         for (unsigned int i = strsize; i > 0; permleft /= i, --i) {
            uint64_t taken_char = permleft % i;
            residuals[strsize - i] = taken_char;

            // Translate indexes in terms of the list of remaining characters
            // into indexes in terms of the original string.
            for (unsigned int o = (strsize - i); o > 0; --o) {
               if (taken_char >= residuals[o - 1]) {
                  ++taken_char;
               }
            }
            idxs[strsize - i] = taken_char;
         }
         cout << perm << " : ";
         for (unsigned int i = 0; i < strsize; ++i) {
            cout << s[idxs[i]];
         }
         cout << '\n';
      }
   }
}

有趣的是,它在排列之间使用的唯一状态是排列的数量、排列的总数和原始字符串。这意味着它可以很容易地封装在迭代器或类似的东西中,而无需仔细保留准确的正确状态。它甚至可以是一个随机访问迭代器。

当然 ::std::next_permutation 将状态存储在元素之间的关系中,但这意味着它不能处理无序的事物,我真的想知道如果序列中有两个相等的事物它会做什么。当然,您可以通过排列索引来解决这个问题,但这会增加一些复杂性。

只要它足够短,我的将适用于任何随机访问迭代器范围。如果不是,你将永远无法通过所有排列。

该算法的基本思想是可以枚举N个项目的每个排列。总数为N!或fact(N)。并且任何给定的排列都可以被认为是源索引从原始序列到新序列中的一组目标索引的映射。一旦枚举了所有排列,剩下要做的就是将每个排列数映射到实际排列中。

置换列表中的第一个元素可以是原始列表中的 N 个元素中的任何一个。第二个元素可以是 N - 1 个剩余元素中的任何一个,依此类推。该算法使用%运算符将​​排列数分解为一组具有这种性质的选择。首先,它以 N 对排列数取模,从 [0,N) 中得到一个数。它通过除以 N 来丢弃余数,然后将它与列表的大小取模 - 1 从 [0,N-1) 中获取一个数字,依此类推。这就是for (i =循环正在做的事情。

第二步是将每个数字转换为原始列表的索引。第一个数字很简单,因为它只是一个直索引。第二个数字是列表的索引,该列表包含除第一个索引处删除的元素以外的所有元素,依此类推。这就是for (o =循环正在做的事情。

residuals是一个连续更小的列表的索引列表。 idxs是原始列表中的索引列表。residuals和中的值之间存在一对一的映射idxs。它们各自代表不同“坐标空间”中的相同值。

您选择的答案所指向的答案具有相同的基本思想,但与我相当文字和蛮力的方法相比,完成映射的方法要优雅得多。这种方式会比我的方法稍微快一点,但它们的速度差不多,而且它们都具有随机访问排列空间的相同优势,这使得很多事情变得更容易,包括(正如你选择的答案所指出的)并行算法。

于 2010-01-15T03:26:36.623 回答
1

实际上,您可以使用 Knuth 改组算法来做到这一点!

// find all the permutations of a string
// using Knuth radnom shuffling algorithm!

#include <iostream>
#include <string>

template <typename T, class Func>
void permutation(T array, std::size_t N, Func func)
{
    func(array);
    for (std::size_t n = N-1; n > 0; --n)
    {
        for (std::size_t k = 0; k <= n; ++k)
        {
            if (array[k] == array[n]) continue;
            using std::swap;
            swap(array[k], array[n]);
            func(array);
        }
    }
}

int main()
{
    while (std::cin.good())
    {
        std::string str;
        std::cin >> str;
        permutation(str, str.length(), [](std::string const &s){ 
            std::cout << s << std::endl; });
    }
}
于 2012-04-29T21:35:05.500 回答
0

这篇文章:http ://cplusplus.co.il/2009/11/14/enumerating-permutations/处理几乎任何东西的置换,而不仅仅是字符串。帖子本身和下面的评论非常有用,我不想复制和粘贴..

于 2010-01-15T14:15:01.820 回答
0

如果你对置换生成感兴趣,我不久前就做了一篇研究论文:http ://www.oriontransfer.co.nz/research/permutation-generation

它带有完整的源代码,并实现了 5 种左右不同的方法。

于 2010-09-07T15:48:39.800 回答
0

甚至我第一次发现递归版本很难理解,我花了一些时间寻找一种berre方式。找到(我能想到的)更好的方法是使用Narayana Pandita提出的算法。基本思想是:

  1. 首先以非递减顺序对给定字符串进行排序,然后按字典顺序查找从末尾开始小于其下一个字符的第一个元素的索引。将此元素索引称为“firstIndex”。
  2. 现在找到比“firstIndex”处的元素更大的最小字符。将此元素索引称为“ceilIndex”。
  3. 现在交换“firstIndex”和“ceilIndex”处的元素。
  4. 将字符串中从索引“firstIndex+1”开始的部分反转到字符串的末尾。
  5. (而不是第 4 点)您还可以对从索引 'firstIndex+1' 到字符串末尾的字符串部分进行排序。

第 4 点和第 5 点做同样的事情,但第 4 点的时间复杂度为 O(n*n!),第 5 点的时间复杂度为 O(n^2*n!)。

上述算法甚至可以应用于字符串中有重复字符的情况。:

显示字符串所有排列的代码:

#include <iostream>

using namespace std;

void swap(char *a, char *b)
{
    char tmp = *a;
    *a = *b;
    *b = tmp;
}


int partition(char arr[], int start, int end)
{
    int x = arr[end];
    int i = start - 1;
    for(int j = start; j <= end-1; j++)
    {
        if(arr[j] <= x)
        {
            i = i + 1;
            swap(&arr[i], &arr[j]);
        }
    }
    swap(&arr[i+1], &arr[end]);
    return i+1;
}

void quickSort(char arr[], int start, int end)
{
    if(start<end)
    {
        int q = partition(arr, start, end);
        quickSort(arr, start, q-1);
        quickSort(arr, q+1, end);
    }
}

int findCeilIndex(char *str, int firstIndex, int n)
{
    int ceilIndex;
    ceilIndex = firstIndex+1;

    for (int i = ceilIndex+1; i < n; i++)
    {
        if(str[i] >= str[firstIndex] && str[i] <= str[ceilIndex])
            ceilIndex = i;
    }
    return ceilIndex;
}

void reverse(char *str, int start, int end)
{
    while(start<=end)
    {
        char tmp = str[start];
        str[start] = str[end];
        str[end] = tmp;
        start++;
        end--;
    }
}

void permutate(char *str, int n)
{
    quickSort(str, 0, n-1);
    cout << str << endl;
    bool done = false;
    while(!done)
    {
        int firstIndex;
        for(firstIndex = n-2; firstIndex >=0; firstIndex--)
        {
            if(str[firstIndex] < str[firstIndex+1])
                break;
        }
        if(firstIndex<0)
            done = true;
        if(!done)
        {
            int ceilIndex;
            ceilIndex = findCeilIndex(str, firstIndex, n);
            swap(&str[firstIndex], &str[ceilIndex]);
            reverse(str, firstIndex+1, n-1);
            cout << str << endl;
        }
    }
}


int main()
{
    char str[] = "mmd";
    permutate(str, 3);
    return 0;
}
于 2013-06-16T09:13:45.910 回答
0

这是我刚刚沙沙作响的一个!!

void permute(const char* str, int level=0, bool print=true) {

    if (print) std::cout << str << std::endl;

    char temp[30];
    for (int i = level; i<strlen(str); i++) {

        strcpy(temp, str);

        temp[level] = str[i];
        temp[i] = str[level];

        permute(temp, level+1, level!=i);
    }
}

int main() {
    permute("1234");

    return 0;
}
于 2015-02-02T22:13:49.583 回答
0

这不是最好的逻辑,但是,我是初学者。如果有人给我有关此代码的建议,我将非常高兴和义务

#include<iostream.h>
#include<conio.h>
#include<string.h>
int c=1,j=1;


int fact(int p,int l)
{
int f=1;
for(j=1;j<=l;j++)
{
f=f*j;
if(f==p)
return 1;

}
return 0;
}


void rev(char *a,int q)
{
int l=strlen(a);
int m=l-q;
char t;
for(int x=m,y=0;x<q/2+m;x++,y++)
{
t=a[x];
a[x]=a[l-y-1];
a[l-y-1]=t;
}
c++;
cout<<a<<"  ";
}

int perm(char *a,int f,int cd)
{
if(c!=f)
{
int l=strlen(a);
rev(a,2);
cd++;
if(c==f)return 0;
if(cd*2==6)
{
for(int i=1;i<=c;i++)
{
if(fact(c/i,l)==1)
{
rev(a,j+1);
rev(a,2);
break;
}
}
cd=1;
}
rev(a,3);
perm(a,f,cd);
}
return 0;
}

void main()
{
clrscr();
char *a;
cout<<"\n\tEnter a Word";
cin>>a;
int f=1;

for(int o=1;o<=strlen(a);o++)
f=f*o;

perm(a,f,0);
getch();
}
于 2017-02-20T15:10:05.843 回答
0
**// Prints all permutation of a string**

    #include<bits/stdc++.h>
    using namespace std;


    void printPermutations(string input, string output){
        if(input.length() == 0){
            cout<<output <<endl;
            return;
        }

        for(int i=0; i<=output.length(); i++){
            printPermutations(input.substr(1),  output.substr(0,i) + input[0] + output.substr(i));
        }
    }

    int main(){
        string s = "ABC";
        printPermutations(s, "");
        return 0;
    }
于 2018-07-16T18:30:24.533 回答
0

这里还有另一个用于字符串排列的递归函数:

void permute(string prefix, string suffix, vector<string> &res) {
    if (suffix.size() < 1) {
        res.push_back(prefix);
        return;
    }
    for (size_t i = 0; i < suffix.size(); i++) {
        permute(prefix + suffix[i], suffix.substr(0,i) + suffix.substr(i + 1), res);
    }
}


int main(){
    string str = "123";
    vector<string> res;
    permute("", str, res);
}

该函数收集向量 res 中的所有排列。可以使用模板和迭代器将这个想法推广到不同类型的容器:

template <typename Cont1_t, typename Cont2_t>
void permute(typename Cont1_t prefix,
    typename Cont1_t::iterator beg, typename Cont1_t::iterator end,
    Cont2_t &result)
{
    if (beg == end) {
        result.insert(result.end(), prefix);
        return;
    }
    for (auto it = beg; it != end; ++it) {
        prefix.insert(prefix.end(), *it);
        Cont1_t tmp;
        for (auto i = beg; i != end; ++i)
            if (i != it)
                tmp.insert(tmp.end(), *i);

        permute(prefix, tmp.begin(), tmp.end(), result);
        prefix.erase(std::prev(prefix.end()));
    }
}

int main()
{
    string str = "123";
    vector<string> rStr;
    permute<string, vector<string>>("", str.begin(), str.end(), rStr);

    vector<int>vint = { 1,2,3 };
    vector<vector<int>> rInt;
    permute<vector<int>, vector<vector<int>>>({}, vint.begin(), vint.end(), rInt);

    list<long> ll = { 1,2,3 };
    vector<list<long>> vlist;
    permute<list<long>, vector<list<long>>>({}, ll.begin(), ll.end(), vlist);
}

这可能是一个有趣的编程练习,但在生产代码中,您应该使用 permutation 的非递归版本,例如 next_permutation。

于 2018-12-15T22:56:55.927 回答
-1
  //***************anagrams**************//


  //************************************** this code works only when there are no   
  repeatations in the original string*************//
  #include<iostream>
  using namespace std;

  int counter=0;

  void print(char empty[],int size)
  {

  for(int i=0;i<size;i++)
  {
    cout<<empty[i];
  }
  cout<<endl;
  }


  void makecombination(char original[],char empty[],char comb[],int k,int& nc,int size)
{
nc=0;

int flag=0;
for(int i=0;i<size;i++)
{
    flag=0;                                                                   // {
    for(int j=0;j<k;j++)
    {
        if(empty[j]==original[i])                                                                // remove this code fragment
        {                                                                                        // to print permutations with repeatation
            flag=1;
            break;
        }
    }
    if(flag==0)                                                                // }
    {
        comb[nc++]=original[i];
    }
}
//cout<<"checks  ";
//    print(comb,nc);
}


void recurse(char original[],char empty[],int k,int size)
{
char *comb=new char[size];


int nc;


if(k==size)
{
    counter++;
    print(empty,size);
    //cout<<counter<<endl;

}
else
{
    makecombination(original,empty,comb,k,nc,size);
    k=k+1;
    for(int i=0;i<nc;i++)
    {
        empty[k-1]=comb[i];

        cout<<"k = "<<k<<" nc = "<<nc<<" empty[k-1] = "<<empty[k-1]<<endl;//checks the  value of k , nc, empty[k-1] for proper understanding
        recurse(original,empty,k,size);
    }
}

}

int main()
{
const int size=3;
int k=0;
char original[]="ABC";

char empty[size];
for(int f=0;f<size;f++)
empty[f]='*';

recurse(original,empty,k,size);

cout<<endl<<counter<<endl;
return 0;
}
于 2012-11-14T21:41:33.043 回答