Есть ли какие-либо лучшие методы для перестановки строк? - программирование
Подтвердить что ты не робот

Есть ли какие-либо лучшие методы для перестановки строк?

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

Вышеуказанная функция показывает перестановки strstr[0..mid-1] в качестве устойчивого префикса и str[mid..end] в качестве перестановочного суффикса). Поэтому мы можем использовать permute(str, 0, str.size() - 1), чтобы показать все перестановки одной строки.

Но функция использует рекурсивный алгоритм; возможно, его производительность может быть улучшена?

Есть ли какие-нибудь лучшие методы для перестановки строки?

4b9b3361

Ответ 1

Вот нерекурсивный алгоритм в С++ из записи Wikipedia для неупорядоченное поколение перестановок. Для строки s длины n для любого k от 0 до n! - 1 включительно следующее модифицирует s, чтобы обеспечить уникальную перестановку (то есть отличную от тех, которые сгенерированы для любого другого k значение в этом диапазоне). Чтобы сгенерировать все перестановки, запустите его для всех n! k значения для исходного значения s.

#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) меняет положение я и j строки s.

Ответ 2

Почему вы не пытаетесь использовать 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

Ответ 3

Я хочу второй Permaquid answer. Алгоритм, который он цитирует, работает принципиально иным образом от различных алгоритмов перечисления перестановок, которые были предложены. Он не генерирует все перестановки из n объектов, он генерирует определенную конкретную перестановку, заданную целым числом между 0 and n!-1. Если вам нужна только определенная перестановка, она намного быстрее, чем перечисление всех, а затем выбор одного.

Даже если вам понадобятся все перестановки, он предоставляет параметры, которые нет в одном алгоритме перечисления перестановок. Однажды я написал взломанный криптоватр, который пробовал всевозможные присвоения букв цифрам. Для задач base-10 это было достаточно, так как есть только 10! перестановки. Но для base-11 проблемы заняли пару минут, а проблемы base-12 заняли почти час.

Я заменил алгоритм перечисления перестановок, который я использовал с простым i=0--to--N-1 for-loop, используя алгоритм Permaquid. Результат был лишь немного медленнее. Но потом я разделил целочисленный диапазон в четверти и одновременно выполнял четыре цикла for-loops, каждый в отдельном потоке. На моем четырехъядерном процессоре полученная программа работала почти в четыре раза быстрее.

Точно так же, как найти индивидуальную перестановку с использованием алгоритмов перестановочных перечислений сложно, генерировать разграниченные подмножества множества всех перестановок тоже сложно. Алгоритм, который цитирует Пермакуд, делает обе эти очень легкие

Ответ 4

В частности, вы хотите 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;
}

... или что-то в этом роде...

Ответ 5

Любой алгоритм генерации перестановок будет выполняться в полиномиальное время, так как число перестановок для символов в строке n-длины составляет (n!). Тем не менее, есть некоторые довольно простые алгоритмы на месте для генерации перестановок. Проверьте алгоритм Джонсона-Троттера.

Ответ 7

Любой алгоритм, который использует или генерирует все перестановки, будет принимать O (N! * N) время, O (N!), по крайней мере, для создания всех перестановок и O (N) для использования результата, и что на самом деле медленный, Обратите внимание, что печать строки также O (N) afaik.

Через секунду вы можете реалистично обрабатывать строки не более 10 или 11 символов, независимо от того, какой метод вы используете. Так как 11! * 11 = 439084800 итераций (делает это много в секунду на большинстве машин, толкает его) и 12! * 12 = 5748019200 итераций. Таким образом, даже самая быстрая реализация займет от 30 до 60 секунд на 12 символов.

Факториал просто слишком быстро растет, чтобы вы могли надеяться получить что-либо, написав более быструю реализацию, вы бы больше всего получили один символ. Поэтому я бы предложил рекомендацию Прасуна. Это легко кодировать, и это довольно быстро. Хотя придерживаться вашего кода также отлично.

Я бы просто рекомендовал, чтобы вы позаботились о том, чтобы у вас не было лишних символов в вашей строке, таких как нулевой символ. Так как это сделает ваш код фактором N медленнее.

Ответ 8

Недавно я написал алгоритм перестановок. Он использует вектор типа T (шаблон) вместо строки, и он не супербыстро, потому что он использует рекурсию и там много копий. Но, возможно, вы можете нарисовать вдохновение для кода. Вы можете найти код здесь.

Ответ 9

Единственный способ значительно повысить производительность - найти способ избежать итерации во всех перестановках в первую очередь!

Перенос - это неизбежно медленная операция (O (n!), или, что еще хуже, в зависимости от того, что вы делаете с каждой перестановкой), к сожалению, ничего, что вы можете сделать, изменит этот факт.

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

Ответ 10

Вы хотите запустить все перестановки или подсчитать количество перестановок?

Для первого используйте std::next_permutation, как это было предложено другими. Каждая перестановка занимает время O (N) (но меньше времени амортизации) и не содержит памяти, кроме ее callframe, 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;
}

Скорость ограничена операцией поиска повторяющихся элементов, которая для char может быть выполнена в O (N) раз с помощью таблицы поиска.

Ответ 11

Я не думаю, что это лучше, но он работает и не использует рекурсию:

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

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

Основная идея этого алгоритма состоит в том, что можно перечислить каждую перестановку N элементов. Общее число N! или fact(N). И любую данную перестановку можно рассматривать как отображение исходных индексов из исходной последовательности в набор индексов назначения в новой последовательности. Как только у вас есть перечисление всех перестановок, осталось только перечислить каждый номер перестановки в фактическую перестановку.

Первым элементом в списке с перестановкой может быть любой из N элементов из исходного списка. Второй элемент может быть любым из N - 1 оставшихся элементов и т.д. Алгоритм использует оператор %, чтобы разделить число перестановок на набор выбранных таким образом. Сначала он по модулю номера перестановок на N, чтобы получить число из [0, N). Он отбрасывает остаток, деля на N, тогда он по модулю по размеру списка - 1, чтобы получить число от [0, N-1) и так далее. Это то, что делает цикл for (i =.

Второй шаг - перевод каждого числа в индекс в исходный список. Первое число легко, потому что это просто прямой индекс. Второе число - это индекс в список, содержащий каждый элемент, но тот, который удаляется при первом индексе, и так далее. Это то, что делает цикл for (o =.

residuals - это список индексов в последовательно меньшие списки. idxs - список индексов в исходный список. Между значениями в residuals и idxs имеется одно-одно отображение. Каждый из них представляет одно и то же значение в разных "координатных пространствах".

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

Ответ 12

На самом деле вы можете сделать это с помощью Knuth shuffling algo!

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

Ответ 13

Это сообщение: http://cplusplus.co.il/2009/11/14/enumerating-permutations/ имеет дело с перестановкой практически всего, а не только строк. Сам пост и комментарии ниже довольно информативны, и я бы не хотел копировать и вставлять.

Ответ 14

Если вы заинтересованы в поколении подстановок, я некоторое время назад написал исследовательскую статью: http://www.oriontransfer.co.nz/research/permutation-generation

Он поставляется в комплекте с исходным кодом, и реализовано 5 или более различных методов.

Ответ 15

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

Ответ 16

Даже мне было трудно понять эту рекурсивную версию в первый раз, и мне потребовалось некоторое время для поиска метода berre. Метод BETTER, который я могу придумать, - использовать алгоритм, предложенный Нараяна Пандита. Основная идея:

  • Сначала отсортируйте заданную строку в порядке убывания, а затем найдите индекс первого элемента с конца, который меньше его следующего символа лексикографически. Вызовите этот элемент index 'firstIndex'.
  • Теперь найдите наименьший символ, который больше элемента в 'firstIndex'. Вызовите этот элемент index "ceilIndex".
  • Теперь замените элементы на 'firstIndex' и 'ceilIndex'.
  • Отмените часть строки, начиная с индекса 'firstIndex + 1' до конца строки.
  • (Вместо пункта 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;
}

Ответ 17

Здесь я просто шумел!!

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

Ответ 18

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

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