it-swarm.com.ru

Распечатать всю перестановку в лексикографическом порядке

Я хочу напечатать всю перестановку строки в лексикографическом порядке. Я пишу этот код:

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
Alexey Sharov

В С

Там довольно простое описание алгоритма (плюс реализация) в geeksforgeeks :

Получив строку, выведите все ее перестановки в отсортированном порядке. За Например, если входной строкой является «ABC», то выходной должен быть «ABC, ACB, BAC, BCA, CAB, CBA ».

Мы обсудили программу для печати всех перестановок в этом посте но здесь мы должны печатать перестановки в порядке возрастания.

Ниже приведены шаги для печати перестановок лексикографического союзника.

  1. Сортируйте данную строку в неубывающем порядке и распечатайте ее. Первая перестановка - это всегда строка, отсортированная в неубывающем порядке.

  2. Начните генерировать следующую более высокую перестановку. Делать это до следующей более высокой перестановки невозможно. Если мы достигнем перестановки, где все символы сортируются в порядке возрастания, затем эта перестановка это последняя перестановка.

Шаги для генерации следующей более высокой перестановки:
1. Возьмите ранее напечатанную перестановку и найдите в ней самый правый символ, который меньше, чем его следующий символ. Позвоним этот персонаж как «первый персонаж».

  1. Теперь найдите потолок «первого персонажа». Потолок - это самый маленький символ справа от «первого символа», который больше чем «первый персонаж». Давайте назовем символ ceil как. Second персонаж'.

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

Результат

азбука 
aCB 
bAC 
bCA 
такси 
сЬа

Живая Демо


В C++

std::next_permutation из библиотеки <algorithm> сделает это за вас, просто убедитесь, что вы сначала отсортировали свой контейнер:

Возвращаемое значение

истина, если функция может переставить объект в виде лексикографии большая перестановка. В противном случае функция возвращает 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)));

Результат:

а б в 
а б 
б а с 
б с 
такси 
с б а

Живая Демо

17
AndyG

Я предполагаю, что вы хотите рекурсивную версию, так как вы новичок.

Вот два решения.

Решение 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 работает и его легко понять, я подозреваю, что мы можем тратить время на сортировку. Это решение ближе к тому, что у вас есть.

Рекурсия - это в основном скрытая математическая индукция, и этот способ мышления действительно полезен для понимания того, как писать рекурсивные программы.

Например, предположим, что ваш метод перестановки всегда создает перестановки в лексикографическом порядке.

Вот рекурсивная версия, с этим предположением, пожалуйста, прочитайте комментарии, чтобы понять, что происходит.

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

Обратите внимание на сходство между этим и итеративной версией (указанной другими и приведенным ниже разделом), в которой вы переворачиваете фрагмент в конце и меняете местами два элемента.


кстати, общий итерационный алгоритм генерации перестановок в лексикографическом порядке - это алгоритм Нараяны Пандиты, упомянутый другими, но не по имени.

Смотрите эту ссылку: http://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order

Это то, что использует std :: next в C++ и множество других библиотек.

Этот алгоритм работает даже при наличии повторяющихся элементов и фактически может использоваться для генерации комбинаций! (Инициализируйте ваш массив с нулями и единицами).

4
Programmer Person

Основная идея состоит в том, чтобы начать с нашей основной строки как "", а оставшуюся строку как "abc" (или любую другую).

Теперь к основной строке добавляем символ за символом. Таким образом, чтобы не повторять используемые символы (для всех возможных символов).

Повторяйте то же самое, пока не получите длину 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
Shivam K Thakkar

Это добровольно ответ, который не отвечает на этот вопрос. 

Этот другой вопрос был помечен как дубликат к этому. Этот ответ был бы приемлем для другого вопроса, даже если это не имеет смысла здесь.

Это может быть простая рекурсивная реализация C для получения всех перестановок в лексикографическом порядке. Это не оптимизировано, но легко понять и реализовать:

  • получить массив для перестановки элементов
  • на любом этапе итерируйте элементы, которые не использовались до этого момента
    • добавить один из повторяющихся элементов в массив используемых элементов
    • рекурсию
  • когда массив используемого элемента заполнен, выведите его.

Конкретная реализация:

#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

ИМХО, было бы проще сначала отсортировать символы строки, потому что число перестановок (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"
0
Serge Ballesta

Еще один поворот в перестановках лексических строк заключается в сохранении перестановки в динамически распределенном массиве указателей на строку и передаче массива в 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)
0
David C. Rankin