web-dev-qa-db-ja.com

文字列の置換を行うより良い方法はありますか?

_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つの文字列のすべての順列を表示できます。

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

文字列を置換するより良い方法はありますか?

50
Jichao

順不同の順列生成 のWikipediaエントリからのC++の非再帰アルゴリズムです。長さsの文字列n、_0_から_n! - 1_までのkの場合、以下はsを一意の順列を提供します(つまり、その範囲の他のk値に対して生成された順列とは異なります)。すべての順列を生成するには、すべてのn!に対して実行します。 sの元の値のk値。

_#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を交換します。

64
Permaquid

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
50
Prasoon Saurav

2番目に Permaquidの答え をお願いします。彼が引用したアルゴリズムは、提供されているさまざまな置換列挙アルゴリズムとは根本的に異なる方法で機能します。 n個のオブジェクトのすべての順列を生成するわけではなく、0 and n!-1の間の整数を指定すると、明確な特定の順列を生成します。特定の順列のみが必要な場合は、すべてを列挙してから1つを選択するよりもはるかに高速です。

すべての順列が必要な場合でも、単一の順列列挙アルゴリズムにはないオプションが提供されます。私はかつて、ブルートフォース暗号解読プログラムを作成しました。 base-10の問題の場合、試してみるのは10!の順列しかないため、これで十分でした。ただし、base-11の問題には数分かかり、base-12の問題には1時間近くかかりました。

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

順列列挙アルゴリズムを使用して個々の順列を見つけることが困難であるように、すべての順列のセットの詳細なサブセットを生成することも困難です。 Permaquidが引用したアルゴリズムにより、これらの両方が非常に簡単になります。

23
Jeff Dege

特に、 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;
}

...またはそのようなもの...

11

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;
    }
}
4
David R Tribble

N長の文字列内の文字の順列の数は(n!)であるため、順列を生成するためのアルゴリズムはすべて多項式時間で実行されます。とはいえ、順列を生成するための非常に単純なインプレースアルゴリズムがいくつかあります。 Johnson-Trotterアルゴリズム を確認してください。

4
JohnE

すべての順列を使用または生成するアルゴリズムは、すべての順列を生成するために少なくともO(N!* N)時間、O(N!)を使用し、使用するためにO(N)文字列の印刷もO(N) afaikであることに注意してください。

どんな方法を使用しても、現実的には最大10文字または11文字までの文字列しか処理できません。 11!* 11 = 439084800の反復(ほとんどのマシンでこれを1秒間に行うとプッシュされる)および12!* 12 = 5748019200の反復からです。そのため、最速の実装でも、12文字で約30〜60秒かかります。

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

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

3
JPvdMerwe

私はこれが優れているとは思わないが、それは動作し、再帰を使用しません:

_#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つの等しいものがある場合はどうなるのでしょうか。もちろん、インデックスを並べ替えることでそれを解決できますが、それは少し複雑になります。

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

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

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

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

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

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

1
Omnifarious

すべての順列を実行しますか、または順列の数をカウントしますか?

前者については、std::next_permutationを他の人が提案したように使用します。各順列は、O(N)時間(ただし、償却時間は少ない)およびコールフレーム以外のメモリなし、vs 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)時間で実行できます。

1
Potatoswatter

パフォーマンスを大幅に向上させるonlyの方法は、そもそもすべての順列の反復を回避する方法を見つけることです!

並べ替えは避けられないほど遅い操作です(O(n!)、さらに悪いことに、各並べ替えで何をするかによって異なります)、残念ながら、この事実を変えることはできません。

また、最適化が有効になっている場合、最新のコンパイラーは再帰をフラット化するため、手動最適化による(わずかな)パフォーマンスの向上はさらに低下します。

1
James

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

1
StackedCrooked

実際には、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; });
    }
}
1
Reza Toghraee

この投稿: http://cplusplus.co.il/2009/11/14/enumerating-permutations/ は、文字列だけでなく、あらゆるものの置換を扱います。投稿自体と以下のコメントは非常に有益であり、コピーして貼り付けたくありません。

0
rmn

これは私がざわめいたものです!!

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;
}
0
Rich

ここで、文字列置換のためのさらに別の再帰関数:

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

これは興味深いプログラミングの練習になるかもしれませんが、製品コードではnext_permutationのような非再帰バージョンのpermutationを使用する必要があります。

**// 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;
    }
0
Deepak Singh

順列生成に興味がある場合は、しばらく前にそれに関する研究論文を作成しました: http://www.oriontransfer.co.nz/research/permutation-generation

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

0
ioquatix

これは最良の論理ではありませんが、私は初心者です。誰かがこのコードに関する提案をくれたら、私は非常に幸せで義務付けられます

#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();
}
0
Ayosh Maitra