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(str[0..mid-1]安定したプレフィックスとして、および置換可能なstr[mid..end]サフィックスとして) の順列を示しています。したがってpermute(str, 0, str.size() - 1)、1 つの文字列のすべての順列を表示するために使用できます。

ただし、関数は再帰アルゴリズムを使用します。多分そのパフォーマンスは改善されるでしょうか?

文字列を並べ替えるより良い方法はありますか?

4

20 に答える 20

64

これは、順序付けられていない順列の生成に関するウィキペディアのエントリからの C++ の非再帰アルゴリズムです。s長さの文字列の場合、からまでnのいずれかを含む場合、次のように変更して一意の順列を提供します (つまり、その範囲の他の k 値に対して生成されたものとは異なります)。すべての順列を生成するには、すべての n! に対して実行します。s の元の値に対する値。k0n! - 1sk

#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の答えを2番目にしたいと思います。彼が引用するアルゴリズムは、提供されているさまざまな順列列挙アルゴリズムとは根本的に異なる方法で機能します。n 個のオブジェクトのすべての順列を生成するわけではありません0 and n!-1。特定の順列のみが必要な場合は、それらをすべて列挙してから 1 つを選択するよりもはるかに高速です。

すべての順列が必要な場合でも、単一の順列列挙アルゴリズムでは提供されないオプションが提供されます。私はかつて、あらゆる可能な文字の数字への割り当てを試みた総当りの暗号クラッカーを書きました。問題については、試行する順列base-10しかないため、これで十分でした。10!しかし、base-11問題には数分、base-12問題には 1 時間近くかかりました。

i=0--to--N-1Permaquid が引用したアルゴリズムを使用して、単純な for ループで使用していた順列列挙アルゴリズムを置き換えました。結果はわずかに遅くなりました。しかし、その後、整数範囲を 4 分の 1 に分割し、4 つの for ループをそれぞれ別のスレッドで同時に実行しました。私のクアッドコア プロセッサでは、結果のプログラムはほぼ 4 倍の速さで実行されました。

順列列挙アルゴリズムを使用して個々の順列を見つけるのが難しいのと同様に、すべての順列のセットの線引きされたサブセットを生成することも困難です。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

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!)、結果を使用するのに O(N) の O(N!*N) 時間かかります。文字列の出力も O(N) afaik であることに注意してください。

どの方法を使用しても、1 秒で処理できるのは最大 10 文字または 11 文字までの文字列のみです。11!*11 = 439084800 回の反復 (ほとんどのマシンで 1 秒間にこれだけの回数を実行するのは大変です) であり、12!*12 = 5748019200 回の反復です。したがって、最速の実装でも、12 文字で約 30 ~ 60 秒かかります。

Factorial は成長が速すぎて、より高速な実装を作成して何かを得ることができません。せいぜい 1 文字しか得られません。だから私はPrasoonの推薦を提案したいと思います. コーディングは簡単で、非常に高速です。ただし、コードに固執することもまったく問題ありません。

null 文字などの余分な文字が文字列に誤って含まれないように注意することをお勧めします。それはあなたのコードをN倍遅くするからです。

于 2010-01-08T21:41:31.327 に答える
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は状態を要素間の関係に格納しますが、それは順序付けられていないものでは機能しないことを意味し、シーケンスに2つの等しいものがある場合はどうなるのでしょうか。もちろん、インデックスを並べ替えることでこれを解決できますが、少し複雑になります。

十分に短い場合、鉱山は任意のランダムアクセスイテレータ範囲で動作します。そうでない場合は、とにかくすべての順列を通過することはありません。

このアルゴリズムの基本的な考え方は、N個のアイテムのすべての順列を列挙できるということです。総数はNです!またはfact(N)。また、任意の順列は、元のシーケンスから新しいシーケンスの宛先インデックスのセットへのソースインデックスのマッピングと考えることができます。すべての順列の列挙ができたら、あとは各順列番号を実際の順列にマップするだけです。

並べ替えられたリストの最初の要素は、元のリストのN個の要素のいずれでもかまいません。2番目の要素は、残りのN-1個の要素のいずれかになります。アルゴリズムは、%演算子を使用して、順列番号をこの性質の選択のセットに分解します。まず、[0、N)から数値を取得するために、Nによる順列数をモジュロします。Nで割って余りを破棄し、リストのサイズでモジュロして-1、[0、N-1)などから数値を取得します。それがfor (i =ループが行っていることです。

2番目のステップは、各数値を元のリストのインデックスに変換することです。最初の数値は単純なインデックスであるため、簡単です。2番目の番号は、最初のインデックスで削除された要素を除くすべての要素を含むリストへのインデックスです。それがfor (o =ループが行っていることです。

residuals連続して小さいリストへのインデックスのリストです。 idxs元のリストへのインデックスのリストです。との値の間には1対1のマッピングがresidualsありidxsます。それらはそれぞれ、異なる「座標空間」で同じ値を表します。

あなたが選んだ答えが指し示す答えは同じ基本的な考え方を持っていますが、私のかなり文字通りの力ずくの方法よりもはるかにエレガントなマッピングを達成する方法があります。その方法は私の方法よりもわずかに高速ですが、どちらもほぼ同じ速度であり、順列空間へのランダムアクセスという同じ利点があります。これにより、次のような多くのことが容易になります(あなたが選んだ答えが指摘したように)並列アルゴリズム。

于 2010-01-15T03:26:36.623 に答える
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;
}

速度は、重複要素を見つける操作によって制限されます。これcharは、ルックアップ テーブルを使用して O(N) 時間で実行できます。

于 2010-01-14T01:23:01.843 に答える
1

最近、順列アルゴリズムを書きました。文字列の代わりに T (テンプレート) 型のベクトルを使用し、再帰を使用し、コピーが多いため、超高速ではありません。しかし、コードのインスピレーションを引き出すことができるかもしれません。コードはこちらにあります。

于 2010-01-10T20:14:21.773 に答える
1

パフォーマンスを大幅に改善する唯一の方法は、最初からすべての順列を繰り返すことを避ける方法を見つけることです!

順列は必然的に遅い操作です (各順列で何をするかによって、O(n!)、またはさらに悪いことになります)。残念ながら、この事実を変えることはできません。

また、最適化が有効になっている場合、最新のコンパイラは再帰を平坦化するため、手動最適化による(わずかな)パフォーマンスの向上はさらに減少することに注意してください。

于 2010-01-11T15:25:22.787 に答える
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

初めての再帰バージョンを理解するのは難しく、ベレの方法を探すのに時間がかかりました。見つけるためのより良い方法 (私が考えることができる) は、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

順列生成に興味があるなら、私は少し前に研究論文を書きました: http://www.oriontransfer.co.nz/research/permutation-generation

ソースコードが付属しており、5 つほどの異なるメソッドが実装されています。

于 2010-09-07T15:48:39.800 に答える
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 に答える
-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 に答える