Перестановка строковых букв: как удалить повторяющиеся перестановки?

Вот стандартная функция для печати перестановок символов строки:

void permute(char *a, int i, int n)
{
   int j;
   if (i == n)
     printf("%s\n", a);
   else
   {
        for (j = i; j < n; j++) //check till end of string
       {
          swap((a+i), (a+j));
          permute(a, i+1, n);
          swap((a+i), (a+j)); //backtrack
       }
   }
} 

void swap (char *x, char *y)
{
    char temp;
    temp = *x;
    *x = *y;
    *y = temp;
}

Он отлично работает, но есть проблема, он также печатает некоторые повторяющиеся перестановки, например:

если строка "AAB"

вывод:

AAB
ABA
AAB
ABA
BAA
BAA

Это также имеет 3 повторяющихся записи.

Можно ли как-то предотвратить это?

--

Спасибо

Алок Кр.


person Kumar Alok    schedule 02.08.2011    source источник
comment
использует std::set нежелательные накладные расходы?   -  person André Puel    schedule 03.08.2011
comment
Звучит как домашнее задание. Вы должны пометить его как таковой, если это так.   -  person bitmask    schedule 03.08.2011
comment
Сэр, это не домашнее задание, я просто работаю над некоторыми стандартными алгоритмами, и я столкнулся с этим вопросом. Также спасибо за std::set, так как я не очень хорошо разбираюсь в С++, поэтому не знал об этом.   -  person Kumar Alok    schedule 03.08.2011


Ответы (10)


Запишите, какие символы вы поменяли местами ранее:

 char was[256];
 /*
 for(j = 0; j <= 255; j++)
    was[j] = 0;
 */
 bzero(was, 256);
 for (j = i; j <= n; j++)
 {
    if (!was[*(a+j)]) {
      swap((a+i), (a+j));
      permute(a, i+1, n);
      swap((a+i), (a+j)); //backtrack
      was[*(a+j)] = 1;
    }
 }

Это должно быть самое быстрое из всех записей на данный момент, какой-то тест на "AAAABBBCCD" (100 циклов):

native C             - real    0m0.547s
STL next_permutation - real    0m2.141s
person Karoly Horvath    schedule 02.08.2011
comment
Вы уверены, что сравниваете подобное с подобным? После того, как вы удалите печать и позволите версии STL изменить массив символов на месте, ваша версия все еще быстрее? - person fizzer; 03.08.2011
comment
Я не буду убирать печать, потому что понятия не имею, какие оптимизации сделает тогда компилятор. вы можете заменить stream out на printf, и вы правы, это имеет огромное значение. также, как и как: мой код просто временно изменяет массив символов, в конце он восстанавливает исходное состояние. - person Karoly Horvath; 03.08.2011
comment
@Кумар: конечно. не видишь логики? ваш код дает дубликаты, потому что он использует все повторяющиеся символы для каждой позиции. - person Karoly Horvath; 03.08.2011
comment
Я вижу логику, но использование этого фрагмента кода не дает мне правильного результата. Возможно, я сделал что-то не так, но вы уверены, что мне не нужно менять какую-либо другую часть кода? - person Kumar Alok; 03.08.2011
comment
Нужен ли второй обмен? - person Vishnu Ks; 13.02.2016
comment
Логически это выглядит правильно, но я не могу заставить его работать: codepad.org/WCSndUao. Любая идея, что я делаю неправильно? Вывод просто печатает две строки, а затем делается. - person SexyBeast; 11.04.2016
comment
@AttitudeMonger: m должен быть локальным, а не глобальным. - person Karoly Horvath; 12.04.2016
comment
Ах да, мой плохой. Это работает как шарм и, безусловно, самое простое из всех решений! Спасибо! - person SexyBeast; 12.04.2016

В стандартной библиотеке есть то, что вам нужно:

#include <algorithm>
#include <iostream>
#include <ostream>
#include <string>
using namespace std;

void print_all_permutations(const string& s)
{
    string s1 = s;
    sort(s1.begin(), s1.end()); 
    do {
        cout << s1 << endl;
    } while (next_permutation(s1.begin(), s1.end()));
}

int main()
{
    print_all_permutations("AAB");
}

Результат:

$ ./a.out
AAB
ABA
BAA
person fizzer    schedule 02.08.2011

Другой подход может быть:

  1. Предварительно отсортируйте массив.

  2. Это гарантирует, что все дубликаты теперь будут последовательными.

  3. Итак, нам просто нужно увидеть предыдущий элемент, который мы исправили (и переставили другие)

  4. если текущий элемент такой же, как и предыдущий, не переставлять.

person ashish_b    schedule 19.01.2014

Я бы сделал это следующим образом: во-первых, я генерирую «группы» символов (т. е. AABBBC дает две группы: (AA) and (BBB) and (C).

Во-первых, мы перебираем все распределения AA по n символам. Для каждого найденного распределения мы перебираем все распределения BBB по оставшимся n-2 символам (не занятым A). Для каждого из этих распределений, включающих As и Bs, мы перебираем все распределения C на оставшиеся свободные позиции символов.

person phimuemue    schedule 02.08.2011
comment
Мне это очень нравится, потому что вы вообще не создаете дубликаты. - person bitmask; 03.08.2011
comment
Это была и моя мысль. Однако на самом деле его реализация может стать немного громоздкой. - person phimuemue; 03.08.2011
comment
Нет, я думаю, вы даже можете сделать это на месте и довольно эффективно, если вы пройдете по массиву пустых слотов (если вы заполните слот, вы переместите последний элемент массива в соответствующий слот в индексном массиве). - person bitmask; 03.08.2011

Вы можете использовать std::set для обеспечения уникальности результатов. То есть, если это С++ (потому что вы пометили его как таковой).

В противном случае - просмотрите список результатов вручную и удалите дубликаты.

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

person littleadv    schedule 02.08.2011
comment
Спасибо, это поможет, но я бы хотел, чтобы код работал и на c, так как я также пометил его тегом c. Так что другой способ меня устраивает. - person Kumar Alok; 03.08.2011
comment
@Kumar - это C или C++? Код C++ может не работать при компиляции с компилятором C, код C может не работать с компилятором C++. Написание кода C не делает его C++, решите, какой язык вы используете. - person littleadv; 03.08.2011

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

Итак, у вас будет массив переставленных строк.

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

Надеюсь, это поможет.

person user872895    schedule 02.08.2011
comment
это создаст n! перестановок, а затем сделает нетривиальный фильтр - person Karoly Horvath; 03.08.2011

@Kumar, я думаю, ты хочешь что-то вроде следующего:

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

/* print all unique permutations of some text. */
void permute(int offset, int* offsets, const char* text, int text_size)
{
    int i;

    if (offset < text_size) {
            char c;
            int j;

            /* iterate over all possible digit offsets. */
            for (i=0; i < text_size; i++) {
                    c=text[i];
                    /* ignore if an offset further left points to our
                       location or to the right, with an identical digit.
                       This avoids duplicates. */
                    for (j=0; j < offset; j++) {
                            if ((offsets[j] >= i) &&
                                (text[offsets[j]] == c)) {
                                    break;
                            }
                    }

                    /* nothing found. */
                    if (j == offset) {
                            /* remember current offset. */
                            offsets[offset]=i;
                            /* permute remaining text. */
                            permute(offset+1, offsets, text, text_size);
                    }
            }
    } else {
            /* print current permutation. */
            for (i=0; i < text_size; i++) {
                    fputc(text[offsets[i]], stdout);
            }
            fputc('\n', stdout);
    }
}

int main(int argc, char* argv[])
{
    int i, offsets[1024];

    /* print permutations of all arguments. */
    for (i=1; i < argc; i++) {
            permute(0, offsets, argv[i], strlen(argv[i]));
    }

    return 0;
}

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

EDIT: Кто-нибудь пробовал это? Есть ли более простое или быстрое решение? Это разочаровывает, что никто не прокомментировал дальше!

person hochl    schedule 03.08.2011

void permute(string set, string prefix = ""){
    if(set.length() == 1){
            cout<<"\n"<<prefix<<set;
    }
    else{
            for(int i=0; i<set.length(); i++){
                    string new_prefix = prefix;
                    new_prefix.append(&set[i], 1);
                    string new_set = set;
                    new_set.erase(i, 1);
                    permute(new_set, new_prefix);
            }
    }
}

И просто используйте его как permute("word");

person AmbuSreedharan    schedule 11.01.2013

Не переставляйте один и тот же символ в другой позиции string.

В Питоне:

def unique_permutation(a, l, r):
    if l == r:
        print ''.join(a)
        return
    for i in range(l, r+1):
        if i != l and a[i] == a[l]:
            continue
        a[i], a[l] = a[l], a[i]
        unique_permutation(a, l+1, r)
        a[i], a[l] = a[l], a[i]
person Wasim Raza    schedule 16.04.2016

Шаги алгоритма:

  1. Сохраните данную строку во временную строку, скажем, "temp"
  2. Удалить дубликаты из временной строки
  3. И, наконец, вызовите функцию «void permute(char *a, int i, int n)», чтобы напечатать все перестановки данной строки без дубликатов.

Я думаю, это лучшее и эффективное решение.

person Black_Rider    schedule 12.01.2013
comment
Это влияет на длину результатов, что явно неправильно. - person th3an0maly; 20.07.2015