web-dev-qa-db-ja.com

すべての順列を辞書式順序で印刷する

文字列のすべての順列を辞書式順序で出力したい。私はこのコードを書きました:

void permute(char *a, int i, int n) {
   if (i == (n-1)) printf("\"%s\"\n", a);
   else {
       for (int j = i; j < n; j++) {
           swap((a+i), (a+j));
           permute(a, i+1, n);
           swap((a+i), (a+j));
       }
   }
}

たとえば、文字列abcを考えてみます。すべての順列を左の列のように辞書式順序で受け取りたいのですが、右の列のように結果が得られます。

"abc"                   "abc"
"acb"                   "acb"
"bac"                   "bac"
"bca"                   "bca"
"cab"            <
"cba"                   "cba"
                 >      "cab"

誰かがこれを手伝ってくれますか?アルゴリズムをいくつか見ましたが、難しいようです。生成されたすべての文字列を配列に保存してからこの配列をソートできると思いますが、これを書くことはできません(私はCの初心者です)。

15
lorantalas

Cで

geeksforgeeks に、アルゴリズム(および実装)のかなり簡単な説明があります。

文字列を指定すると、そのすべての順列をソートされた順序で出力します。たとえば、入力文字列が「ABC」の場合、出力は「ABC、ACB、BAC、BCA、CAB、CBA」になります。

この投稿ではすべての順列を出力するプログラムについて説明しましたが、ここでは順列を昇順に出力する必要があります。

以下は、順列を辞書式に印刷する手順です。

  1. 与えられた文字列を降順に並べ替えて出力します。最初の順列は常に、非降順でソートされた文字列です。

  2. 次に高い順列の生成を開始します。次のより高い順列が不可能になるまでそれを行います。すべての文字が昇順に並べ替えられていない順列に到達すると、その順列が最後の順列になります。

次に高い順列を生成する手順:
1。以前に印刷された順列を取り、その中で右端の文字を見つけます。これは、次の文字よりも小さいです。このキャラクターを「最初のキャラクター」と呼びましょう。

  1. 次に、「最初のキャラクター」の天井を見つけます。天井は、「最初の文字」の右側にある最小の文字で、「最初の文字」よりも大きくなります。 ceilキャラクターを「2番目のキャラクター」と呼びます。

  2. 上記の2つの手順で見つかった2つの文字を入れ替えます。

  3. 「最初の文字」の元のインデックスの後に、部分文字列を(降順ではなく)ソートします。

以下に再実装しました:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void swap(char* left, char* right)
{
    char temp = *left;
    *left = *right;
    *right = temp;
}
int compare (const void * a, const void * b)
{
  return ( *(char*)a - *(char*)b );
}
void PrintSortedPermutations(char* inStr)
{
    // Re-implementation of algorithm described here:
    // http://www.geeksforgeeks.org/lexicographic-permutations-of-string/
    int strSize = strlen(inStr);
    // 0. Ensure input container is sorted
    qsort(inStr, strSize, sizeof(char), compare);


    int largerPermFound = 1;
    do{
        // 1. Print next permutation
        printf("%s\n", inStr);
        // 2. Find rightmost char that is smaller than char to its right
        int i;
        for (i = strSize - 2; i >= 0 && inStr[i] >= inStr[i+1]; --i){}

        // if we couldn't find one, we're finished, else we can swap somewhere
        if (i > -1)
        {
            // 3 find character at index j such that 
            // inStr[j] = min(inStr[k]) && inStr[k] > inStr[i] for all k > i
            int j = i+1;
            int k;
            for(k=j;k<strSize && inStr[k];++k)
            {
                if (inStr[k] > inStr[i] && inStr[k] < inStr[j])
                    j = k;
            }

            // 3. Swap chars at i and j
            swap(&inStr[i], &inStr[j]);

            // 4. Sort string to the right of i
            qsort(inStr+i+1, strSize-i-1, sizeof(char), compare);
        }
        else
        {
            largerPermFound = 0;
        }
    }while(largerPermFound);
}

int main(void) {
    char str[] = "abc";

    PrintSortedPermutations(str);
    return 0;
}

出力

aBC
acb
バク
bca
タクシー
cba

ライブデモ


C++の場合

std::next_permutation から<algorithm>ライブラリがこれを行います。最初にコンテナをソートしてください。

戻り値

関数がオブジェクトを辞書式に大きな順列として再配置できる場合はtrue。それ以外の場合、この関数はfalseを返し、配置が前の配置より大きくないが、可能な限り低い(昇順で並べ替えられている)ことを示します。

例えば:

std::string myStr = "abc";
std::stable_sort(std::begin(myStr), std::end(myStr));
do {
    for(auto&& element : myStr)
        std::cout << element << " ";
    std::cout << std::endl;
} while (std::next_permutation(std::begin(myStr), std::end(myStr)));

出力:

a b c
a c b
b a c
b c a
タクシー
c b a

ライブデモ

18
AndyG

あなたは初心者なので、あなたは再帰的なバージョンを望んでいると思います。

2つのソリューションがあります。

ソリューション1)

辞書式が必要なため、選択する必要がある場合は、可能な限り次に小さいものを選択するだけです。それでおしまい!

たとえば、これはpythonの再帰バージョンです

def permute(done, remaining):
  if not remaining:
    print done
    return

  sorted_rem = sorted(remaining)
  l = len(sorted_rem)

  for i in xrange(0, l):
    c = sorted_rem[i]

    # Move to c to done portion.
    done.append(c)
    remaining.remove(c)

    # Permute the remaining
    permute(done, remaining)

    # Put c back.
    remaining.append(c)
    # Remove from done.
    del done[-1]

permute([], [1,2,3,4])

それでおしまい。

ソリューション2)

ソリューション1は機能し、理解しやすいですが、並べ替えによって時間を浪費している可能性があります。このソリューションは、あなたが持っているものにより近いものです。

再帰は、基本的には変装した数学的帰納法であり、その考え方は、再帰プログラムの記述方法を理解する上で非常に役立ちます。

たとえば、permuteメソッドが常に辞書式順序で順列を作成するとします。

これは再帰的なバージョンですが、その前提で、コメントを読んで何が起こっているのかを理解してください。

// By induction assumption, permute(a, i, n)
// goes through all the permutations of a[i], ..., a[n-1]
// in lexicographic order, by modifying a itself.
void permute(char *a, int i, int n) {
    if (i == (n-1)) {
       printf("%s\n", a);
      return;
    }

    int j;
    // We pick the n-i posibilities for the position a+i, then recursively
    // compute the permutations of a[i+1], ..., a[n-1]
    // So first pick the smallest possible for a+i, recurse.
    // Then the next possible for a+i, then recurse etc.

    for (j = i; j < n; j++) {
      permute(a, i+1, n);
      // By our induction assumption, at this point, 
      // a[i+1], a[i+2], .., a[n-1]
      // must be the lexicographically the largest possible!

      // So now reverse that portion.
      reverse(a+i+1, a+n-1);

      // Now we need to pick the lexicographically next element for
      // position a+i. This is nothing but the element which is just
      // larger than the current a+i.

      int k = i+1;
      while(k < n && a[i] > a[k]) {
        k++;
      }

      if (k >= n) {
        continue;
      }
      // Choose the next value for a+i.
      swap(a+i, a+k);
    }
    // Notice that the portion a[i+1], ..., a[n-1]  is sorted increasing.
    // when the loop exits. Also a[i] will be the largest element.
    // We need to reverse so that a[i], .., a[n-1] is the lexicographically
    // largest permutation to  maintain the induction (recursion) assumption.
    reverse(a+i+1, a+n-1);
}

これと、最後のチャンクを逆にし、2つの要素を交換する反復バージョン(他のセクションと以下のセクションで指定)の類似性に注意してください。


ところで、辞書式順序で順列を生成するための一般的な反復アルゴリズムは、名前ではなく他の人が言及しているNarayana Panditaのアルゴリズムです。

このリンクを参照してください: http://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order

これは、C++のstd :: nextおよび他のライブラリのホストが使用するものです。

このアルゴリズムは、繰り返し要素がある場合でも機能し、実際には組み合わせを生成するために使用できます。 (ゼロと1で配列を初期化します)。

5

基本的な考え方は、メインの文字列を ""、残りの文字列を "abc"(または任意の文字列)から始めることです。

次に、メイン文字列に1文字ずつ追加します。 (可能なすべての文字に対して)使用されている文字を繰り返さないような方法。

長さn(主ストリングの長さ)が得られるまで、同じことを繰り返します。

わかりました、説明は明確ではありませんが、コードに注意してください。それはすべてを明確にします。

#include<iostream>

void perm(std::string sub, std::string rem, int n)
{
    if(n==0)
        //print if n is zero i.e length achieved
        std::cout<<sub<<"\n";

    for(int i=0; i<n; i++)
        //append a character and pass remaining to rem string
        perm(sub + rem[i] , rem.substr(0,i) + rem.substr(i+1,n-1), n-1);
}

int main()
{
    perm("", "abc", 3);
}

出力

abc
acb
bac
bca
cab
cba
1

私見、順列の数(n!)は常に文字数よりも多い(またはn = 1または2と等しい)ため、文字列の文字を最初にソートする方が簡単です。

そして、あなたはソリューションからそれほど遠くないですが、あなたはスワップの代わりに回転しなければなりません。次に、メインで出力される動的に割り当てられた文字列の配列を返すわずかなバリエーションを示します。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int char_compare(const void *a, const void *b) {
    return (*((char *) a)) - (*((char *) b));
}

int fact(int n) {
    int f = 1;
    while (n > 0) {
        f *= n--;
        if (f < 0) return 0;
    }
    return f;
}

void rotateback(char *a, int i, int j) {
    int k;
    char tmp;
    tmp = a[i];
    for(k=i; k<j; k++) {
        a[k] = a[k+1];
    }
    a[j] = tmp;
}

void rotate(char *a, int i, int j) {
    int k;
    char tmp;
    tmp = a[j];
    for(k=j; k>i; k--) {
        a[k] = a[k-1];
    }
    a[i] = tmp;
}

void permute(char *a, int i, int n, char ***permuts) {
    int j;
    if (i == (n-1)) {
        **permuts = strdup(a);  // store a copy of the string
        *permuts += 1;          // and point to next location
    }
   else {
       for (j = i; j < n; j++) {
           rotate(a, i, j);
           permute(a, i+1, n, permuts);
           rotateback(a, i, j);
       }
   }
}
char ** permutations(const char *str_orig) {
    int i, j;
    size_t n = strlen(str_orig);
    size_t fact_n = fact(n);
    char ** permuts, **work;

    char * str = strdup(str_orig); // get a local copy
    qsort(str, n, 1, char_compare); // and sort it

    permuts = work = calloc(fact_n, sizeof(char *)); // allocate n! pointers

    permute(str, 0, n, &work);
    return permuts;
}

int main() {
    char str[] = "cab";
    int i;

    char **permuts = permutations(str);

    for (i=0; i<fact(strlen(str)); i++) {
        printf("\"%s\"\n", permuts[i]);
        free(permuts[i]);
    }
    free(permuts);
    return 0;
}

出力は正しく:

"abc"
"acb"
"bac"
"bca"
"cab"
"cba"
1
Serge Ballesta

字句文字列の順列に関するもう1つの工夫は、文字列へのポインタの動的に割り当てられた配列に順列を格納し、その配列をqsortに渡して、字句順に出力を提供することです。順列は指数関数的に増加するため、各割り当て後のメモリ枯渇のチェックは特に重要です。以下の文字列サイズは16文字に制限されていますが、使用可能なメモリの量によっては、メモリ不足になる可能性があります。

更新再配置が再帰関数で機能するためには、文字列の置換を保持する配列のアドレスを渡す必要がありました。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXS 128
#define MAXC 16

size_t maxs;

void swap (char *x, char *y);
int cmp_pa (const void * a, const void * b);
char **realloc_char (char **sp, size_t *n);
void permute_pa (char ***pa, size_t *idx, char *a, int i, int n);

int main (int argc, char **argv)
{
    size_t i = 0;
    size_t idx = 0;
    size_t len = 0;
    char a[MAXC] = {0};
    char **pa = NULL;

    maxs = MAXS;                            /* initialize realloc counter   */

    if (argc > 1)                           /* set string to permute        */
        strcpy (a, argv[1]);
    else
        strcpy (a, "abc");

    len = strlen (a);                       /* lenght to permute or MAXC    */
    if (len > MAXC) len = MAXC;

    if (!(pa = calloc (MAXS, sizeof *pa)))  /* allocate MAXS pointers       */
        return 1;

    permute_pa (&pa, &idx, a, 0, len - 1);  /* call permute function        */

    printf ("\n no of permutations : %zu\n\n", idx);
    printf (" unsorted permutations of %s\n\n", a);

    for (i = 0; i < idx; i++)
        printf (" %s\n", pa[i]);

    qsort (pa, idx, sizeof *pa, cmp_pa);    /* sort array of permutations   */

    printf ("\n sorted permutations of %s\n\n", a);

    for (i = 0; i < idx; i++)
        printf (" %s\n", pa[i]);

    for (i = 0; i < idx; i++)               /* free all allocated memory    */
        free (pa[i]);
    free (pa);

    return 0;
}

/* Function to swap values at two pointers */
void swap (char *x, char *y)
{
    char temp;
    temp = *x;
    *x = *y;
    *y = temp;
}

/* qsort compare function */
int cmp_pa (const void * a, const void * b)
{   return strcmp (*(char**)a, *(char**)b); }

/* realloc an array of pointers to strings setting memory to 0. */
char **realloc_char (char **sp, size_t *n)
{
    char **tmp = realloc (sp, 2 * *n * sizeof *sp);
    if (!tmp) {
        fprintf (stderr, "Error: struct reallocation failure.\n");
        // return NULL;
        exit (EXIT_FAILURE);
    }
    sp = tmp;
    memset (sp + *n, 0, *n * sizeof *sp); /* memset new ptrs 0 */
    *n *= 2;

    return sp;
}

/* Function to store permutations of string in array of pointers-to-string
This function takes five parameters:
1. allocated array of pointers-to-string
2. pointer to array index
3. string to permute
4. starting index of the string (zero based)
5. ending index of the string. (zero based)
*/
void permute_pa (char ***pa, size_t *idx, char *a, int i, int n)
{
    int j;
    if (i == n) {
        (*pa)[*idx] = strdup (a);
        if (!(*pa)[*idx]) {
            fprintf (stderr, "%s() error: virtual memory exhausted.\n", __func__);
            exit (EXIT_FAILURE);
        }
        (*idx)++;
        if (*idx == maxs)
            *pa = realloc_char (*pa, &maxs);
    }
    else {
        for (j = i; j <= n; j++) {
            swap ((a+i), (a+j));
            permute_pa (pa, idx, a, i+1, n);
            swap ((a+i), (a+j));
        }
    }
}

出力

$ ./bin/str_permute_Lex

 no of permutations : 6

 unsorted permutations of abc

 abc
 acb
 bac
 bca
 cba
 cab

 sorted permutations of abc

 abc
 acb
 bac
 bca
 cab
 cba

メモリエラーチェック

$ valgrind ./bin/str_permute_Lex
==29709== Memcheck, a memory error detector
==29709== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==29709== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==29709== Command: ./bin/str_permute_Lex
==29709==

 no of permutations : 6

 <snip>

==29709==
==29709== HEAP SUMMARY:
==29709==     in use at exit: 0 bytes in 0 blocks
==29709==   total heap usage: 7 allocs, 7 frees, 1,048 bytes allocated
==29709==
==29709== All heap blocks were freed -- no leaks are possible
==29709==
==29709== For counts of detected and suppressed errors, rerun with: -v
==29709== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)
1
David C. Rankin

これは自発的にこの質問に答えない答えです。

この他の質問 は、この質問の重複としてマークされました。この回答は、ここでは意味がなくても、他の質問には受け入れられます。

これは、すべての順列を辞書式順序で取得する単純な再帰C実装である可能性があります。最適化されていませんが、理解と実装は簡単です。

  • 並べ替える要素の配列を取得する
  • どのステップでも、ここまで使用されなかった要素を反復処理します
    • 反復要素の1つを使用済み要素の配列に追加します
    • 再帰
  • 使用された要素の配列が完全であるとき、それを印刷します。

具体的な実装:

#include <stdio.h>

#define SIZE 4

void disp(int *fullarr, int n, int * begin, int pos) {
    int i, j;
    int found;
    if (pos == n) {
        for(i=0; i< n; i++) {
            printf("%2d", begin[i]);
        }
        printf("\n");
        return;
    }
    for (i=0; i<n; i++) {
        found = 0;
        for (j=0; j<pos; j++) {
            if (fullarr[i] == begin[j]) {
                found = 1;
                break;
            }
        }
        if (! found) {
            begin[pos] = fullarr[i];
            disp(fullarr, n, begin, pos+1);
        }
    }
}

int main() {
    int i;
    int fullarr[SIZE], begin[SIZE];
    for (i=0; i<SIZE; i++) {
        fullarr[i] = i;
    }
    disp(fullarr, SIZE, begin, 0);
    return 0;
}
0
Serge Ballesta
void permute(string str,int index)
{
 if(index ==  str.size())
 {
    cout<<str<<" ";
    return;
 }
 else
 {
    for(int j=index;j<str.size();j++)
    {
        if(str[index] <= str[j]) // swap only in sorted order 
        swap(str[j],str[index]);
        permute(str,index+1);
        if(str[index] <= str[j]) // swap only in sorted order 
        swap(str[j],str[index]);
    }
 }
}
int main()
{
 int t; 
 string str;
    scanf("%d",&t);
    while(t--){
        // scanf("%d",&n);
        cin>>str;
        sort(str.begin(),str.end());
        permute(str,0);
        cout<<endl;

    }
  return 0;
}
0
chandan