Есть ли какие-либо лучшие методы для перестановки строк?
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
Вот нерекурсивный алгоритм в С++ из записи 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!)
. Тем не менее, есть некоторые довольно простые алгоритмы на месте для генерации перестановок. Проверьте алгоритм Джонсона-Троттера.
Ответ 6
алгоритм случайного случайного воспроизведения 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;
}
}
Ответ 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();
}