生成字符串所有可能排列的列表
-
08-06-2019 - |
题
我将如何生成长度为 x 和 y 字符之间的字符串的所有可能排列的列表,其中包含可变字符列表。
任何语言都可以,但它应该是可移植的。
解决方案
做这件事有很多种方法。常见的方法使用递归、记忆或动态编程。基本思想是,生成长度为 1 的所有字符串的列表,然后在每次迭代中,对于上次迭代中生成的所有字符串,分别添加与字符串中的每个字符连接的字符串。(下面代码中的变量索引跟踪上一次和下一次迭代的开始)
一些伪代码:
list = originalString.split('')
index = (0,0)
list = [""]
for iteration n in 1 to y:
index = (index[1], len(list))
for string s in list.subset(index[0] to end):
for character c in originalString:
list.add(s + c)
然后,您需要删除长度小于 x 的所有字符串,它们将是列表中的第一个 (x-1) * len(originalString) 条目。
其他提示
最好使用回溯
#include <stdio.h>
#include <string.h>
void swap(char *a, char *b) {
char temp;
temp = *a;
*a = *b;
*b = temp;
}
void print(char *a, int i, int n) {
int j;
if(i == n) {
printf("%s\n", a);
} else {
for(j = i; j <= n; j++) {
swap(a + i, a + j);
print(a, i + 1, n);
swap(a + i, a + j);
}
}
}
int main(void) {
char a[100];
gets(a);
print(a, 0, strlen(a) - 1);
return 0;
}
你会得到很多字符串,这是肯定的......
\sum_{i=x}^y{\frac{r!}{{(r-i)}!}} http://www.codecogs.com/eq.latex?%5Csum_%7Bi=x%7D%5Ey% 20%7B%20%5Cfrac%7Br!%7D%7B%7B(r-i)%7D!%7D%20%7D
其中,x 和 y 是您定义它们的方式,r 是我们从中选择的字符数 - 如果我理解正确的话。您绝对应该根据需要生成这些,而不是马虎地说,生成一个幂集,然后过滤字符串的长度。
以下绝对不是生成这些的最佳方法,但它仍然是一个有趣的旁白。
Knuth(第 4 卷,分册 2,7.2.1.3)告诉我们,(s,t)-组合相当于每次重复 t 取 s+1 个事物——(s,t)-组合是克努特等于 {t \选择 {s+t} http://www.codecogs.com/eq.latex?%7Bt%20%5Cchoose%20%7Bs+t%7D%7D. 。我们可以通过首先以二进制形式生成每个 (s,t) 组合(因此长度为 (s+t))并计算每个 1 左侧 0 的数量来计算出这一点。
10001000011101 --> 变为排列:{0, 3, 4, 4, 4, 1}
根据 Knuth、Python 示例的非递归解决方案:
def nextPermutation(perm):
k0 = None
for i in range(len(perm)-1):
if perm[i]<perm[i+1]:
k0=i
if k0 == None:
return None
l0 = k0+1
for i in range(k0+1, len(perm)):
if perm[k0] < perm[i]:
l0 = i
perm[k0], perm[l0] = perm[l0], perm[k0]
perm[k0+1:] = reversed(perm[k0+1:])
return perm
perm=list("12345")
while perm:
print perm
perm = nextPermutation(perm)
你可能会看看“高效枚举集合的子集”,它描述了一种算法来完成您想要的部分操作 - 快速生成从长度 x 到 y 的 N 个字符的所有子集。它包含 C 语言的实现。
对于每个子集,您仍然必须生成所有排列。例如,如果您想要“abcde”中的 3 个字符,该算法将为您提供“abc”、“abd”、“abe”...但你必须对每一个进行排列才能得到“acb”、“bac”、“bca”等。
一些基于的工作 Java 代码 萨普的回答:
public class permute {
static void permute(int level, String permuted,
boolean used[], String original) {
int length = original.length();
if (level == length) {
System.out.println(permuted);
} else {
for (int i = 0; i < length; i++) {
if (!used[i]) {
used[i] = true;
permute(level + 1, permuted + original.charAt(i),
used, original);
used[i] = false;
}
}
}
}
public static void main(String[] args) {
String s = "hello";
boolean used[] = {false, false, false, false, false};
permute(0, "", used, s);
}
}
这是 C# 中的一个简单解决方案。
它仅生成给定字符串的不同排列。
static public IEnumerable<string> permute(string word)
{
if (word.Length > 1)
{
char character = word[0];
foreach (string subPermute in permute(word.Substring(1)))
{
for (int index = 0; index <= subPermute.Length; index++)
{
string pre = subPermute.Substring(0, index);
string post = subPermute.Substring(index);
if (post.Contains(character))
continue;
yield return pre + character + post;
}
}
}
else
{
yield return word;
}
}
这里有很多好的答案。我还建议在 C++ 中使用一个非常简单的递归解决方案。
#include <string>
#include <iostream>
template<typename Consume>
void permutations(std::string s, Consume consume, std::size_t start = 0) {
if (start == s.length()) consume(s);
for (std::size_t i = start; i < s.length(); i++) {
std::swap(s[start], s[i]);
permutations(s, consume, start + 1);
}
}
int main(void) {
std::string s = "abcd";
permutations(s, [](std::string s) {
std::cout << s << std::endl;
});
}
笔记: :具有重复字符的字符串不会产生唯一的排列。
我刚刚用 Ruby 快速完成了这个:
def perms(x, y, possible_characters)
all = [""]
current_array = all.clone
1.upto(y) { |iteration|
next_array = []
current_array.each { |string|
possible_characters.each { |c|
value = string + c
next_array.insert next_array.length, value
all.insert all.length, value
}
}
current_array = next_array
}
all.delete_if { |string| string.length < x }
end
您可能会研究内置排列类型函数的语言 API,并且您也许能够编写更优化的代码,但如果数字都那么高,我不确定是否有很多方法可以得到大量结果。
无论如何,代码背后的想法是从长度为 0 的字符串开始,然后跟踪长度为 Z 的所有字符串,其中 Z 是迭代中的当前大小。然后,遍历每个字符串并将每个字符附加到每个字符串上。最后,删除任何低于 x 阈值的值并返回结果。
我没有使用可能无意义的输入(空字符列表、奇怪的 x 和 y 值等)来测试它。
这是 Mike 的 Ruby 版本到 Common Lisp 的翻译:
(defun perms (x y original-string)
(loop with all = (list "")
with current-array = (list "")
for iteration from 1 to y
do (loop with next-array = nil
for string in current-array
do (loop for c across original-string
for value = (concatenate 'string string (string c))
do (push value next-array)
(push value all))
(setf current-array (reverse next-array)))
finally (return (nreverse (delete-if #'(lambda (el) (< (length el) x)) all)))))
另一个版本稍短并使用更多循环设施功能:
(defun perms (x y original-string)
(loop repeat y
collect (loop for string in (or (car (last sets)) (list ""))
append (loop for c across original-string
collect (concatenate 'string string (string c)))) into sets
finally (return (loop for set in sets
append (loop for el in set when (>= (length el) x) collect el)))))
下面是一个简单的 C# 单词递归解决方案:
方法:
public ArrayList CalculateWordPermutations(string[] letters, ArrayList words, int index)
{
bool finished = true;
ArrayList newWords = new ArrayList();
if (words.Count == 0)
{
foreach (string letter in letters)
{
words.Add(letter);
}
}
for(int j=index; j<words.Count; j++)
{
string word = (string)words[j];
for(int i =0; i<letters.Length; i++)
{
if(!word.Contains(letters[i]))
{
finished = false;
string newWord = (string)word.Clone();
newWord += letters[i];
newWords.Add(newWord);
}
}
}
foreach (string newWord in newWords)
{
words.Add(newWord);
}
if(finished == false)
{
CalculateWordPermutations(letters, words, words.Count - newWords.Count);
}
return words;
}
呼叫:
string[] letters = new string[]{"a","b","c"};
ArrayList words = CalculateWordPermutations(letters, new ArrayList(), 0);
在 Perl 中,如果你想限制自己使用小写字母,你可以这样做:
my @result = ("a" .. "zzzz");
这将使用小写字符给出 1 到 4 个字符之间的所有可能字符串。对于大写,更改 "a"
到 "A"
和 "zzzz"
到 "ZZZZ"
.
对于混合大小写,它会变得更加困难,并且可能无法使用像这样的 Perl 内置运算符之一来实现。
...这是 C 版本:
void permute(const char *s, char *out, int *used, int len, int lev)
{
if (len == lev) {
out[lev] = '\0';
puts(out);
return;
}
int i;
for (i = 0; i < len; ++i) {
if (! used[i])
continue;
used[i] = 1;
out[lev] = s[i];
permute(s, out, used, len, lev + 1);
used[i] = 0;
}
return;
}
排列 (ABC) -> A.perm(BC) -> A.perm[B.perm(C)] -> A.perm[(*BC),(C乙*)] -> [(*A公元前),(乙AC), (公元前A*), (*ACB),(CAB),(CBA*)]在插入每个字母检查时,要删除重复项,以查看上一个字符串是否以同一字母结尾(为什么?-锻炼)
public static void main(String[] args) {
for (String str : permStr("ABBB")){
System.out.println(str);
}
}
static Vector<String> permStr(String str){
if (str.length() == 1){
Vector<String> ret = new Vector<String>();
ret.add(str);
return ret;
}
char start = str.charAt(0);
Vector<String> endStrs = permStr(str.substring(1));
Vector<String> newEndStrs = new Vector<String>();
for (String endStr : endStrs){
for (int j = 0; j <= endStr.length(); j++){
if (endStr.substring(0, j).endsWith(String.valueOf(start)))
break;
newEndStrs.add(endStr.substring(0, j) + String.valueOf(start) + endStr.substring(j));
}
}
return newEndStrs;
}
打印所有排列,不重复
有效的 Ruby 答案:
class String
def each_char_with_index
0.upto(size - 1) do |index|
yield(self[index..index], index)
end
end
def remove_char_at(index)
return self[1..-1] if index == 0
self[0..(index-1)] + self[(index+1)..-1]
end
end
def permute(str, prefix = '')
if str.size == 0
puts prefix
return
end
str.each_char_with_index do |char, index|
permute(str.remove_char_at(index), prefix + char)
end
end
# example
# permute("abc")
C++中的递归解决方案
int main (int argc, char * const argv[]) {
string s = "sarp";
bool used [4];
permute(0, "", used, s);
}
void permute(int level, string permuted, bool used [], string &original) {
int length = original.length();
if(level == length) { // permutation complete, display
cout << permuted << endl;
} else {
for(int i=0; i<length; i++) { // try to add an unused character
if(!used[i]) {
used[i] = true;
permute(level+1, original[i] + permuted, used, original); // find the permutations starting with this string
used[i] = false;
}
}
}
以下 Java 递归打印给定字符串的所有排列:
//call it as permut("",str);
public void permut(String str1,String str2){
if(str2.length() != 0){
char ch = str2.charAt(0);
for(int i = 0; i <= str1.length();i++)
permut(str1.substring(0,i) + ch + str1.substring(i,str1.length()),
str2.substring(1,str2.length()));
}else{
System.out.println(str1);
}
}
以下是上述“permut”方法的更新版本,该方法使 n!与上述方法相比,(n 阶乘)递归调用更少
//call it as permut("",str);
public void permut(String str1,String str2){
if(str2.length() > 1){
char ch = str2.charAt(0);
for(int i = 0; i <= str1.length();i++)
permut(str1.substring(0,i) + ch + str1.substring(i,str1.length()),
str2.substring(1,str2.length()));
}else{
char ch = str2.charAt(0);
for(int i = 0; i <= str1.length();i++)
System.out.println(str1.substring(0,i) + ch + str1.substring(i,str1.length()),
str2.substring(1,str2.length()));
}
}
import java.util.*;
public class all_subsets {
public static void main(String[] args) {
String a = "abcd";
for(String s: all_perm(a)) {
System.out.println(s);
}
}
public static Set<String> concat(String c, Set<String> lst) {
HashSet<String> ret_set = new HashSet<String>();
for(String s: lst) {
ret_set.add(c+s);
}
return ret_set;
}
public static HashSet<String> all_perm(String a) {
HashSet<String> set = new HashSet<String>();
if(a.length() == 1) {
set.add(a);
} else {
for(int i=0; i<a.length(); i++) {
set.addAll(concat(a.charAt(i)+"", all_perm(a.substring(0, i)+a.substring(i+1, a.length()))));
}
}
return set;
}
}
这是我用 javascript 提出的非递归版本。它不是基于上面 Knuth 的非递归非递归,尽管它在元素交换方面有一些相似之处。我已经验证了其对于最多 8 个元素的输入数组的正确性。
一个快速的优化是预飞行 out
阵列和避免 push()
.
基本思想是:
给定一个源数组,生成第一组新数组,依次将第一个元素与每个后续元素交换,每次都保持其他元素不受干扰。例如:给定 1234,生成 1234、2134、3214、4231。
将上一张中的每个数组用作新通行证的种子,但不要交换第一个元素,而将第二个元素与每个后续元素交换。另外,这一次,不要在输出中包含原始数组。
重复步骤 2 直至完成。
这是代码示例:
function oxe_perm(src, depth, index)
{
var perm = src.slice(); // duplicates src.
perm = perm.split("");
perm[depth] = src[index];
perm[index] = src[depth];
perm = perm.join("");
return perm;
}
function oxe_permutations(src)
{
out = new Array();
out.push(src);
for (depth = 0; depth < src.length; depth++) {
var numInPreviousPass = out.length;
for (var m = 0; m < numInPreviousPass; ++m) {
for (var n = depth + 1; n < src.length; ++n) {
out.push(oxe_perm(out[m], depth, n));
}
}
}
return out;
}
我不确定你为什么要这样做。任何中等大的 x 和 y 值的结果集都将是巨大的,并且随着 x 和/或 y 变大而呈指数增长。
假设您的可能字符集是字母表中的 26 个小写字母,并且您要求应用程序生成长度 = 5 的所有排列。假设你没有耗尽内存,你将得到 11,881,376 (即26 的 5 次方)弦背。将长度增加到 6,您将得到 308,915,776 个字符串。这些数字很快就会变得非常大。
这是我用 Java 编写的一个解决方案。您需要提供两个运行时参数(对应于 x 和 y)。玩得开心。
public class GeneratePermutations {
public static void main(String[] args) {
int lower = Integer.parseInt(args[0]);
int upper = Integer.parseInt(args[1]);
if (upper < lower || upper == 0 || lower == 0) {
System.exit(0);
}
for (int length = lower; length <= upper; length++) {
generate(length, "");
}
}
private static void generate(int length, String partial) {
if (length <= 0) {
System.out.println(partial);
} else {
for (char c = 'a'; c <= 'z'; c++) {
generate(length - 1, partial + c);
}
}
}
}
我今天需要这个,尽管已经给出的答案为我指明了正确的方向,但它们并不是我想要的。
这是使用Heap方法的实现。数组的长度必须至少为 3,出于实际考虑,不能大于 10 左右,具体取决于您想要做什么、耐心和时钟速度。
在进入循环之前,初始化 Perm(1 To N)
通过第一个排列, Stack(3 To N)
带零*,以及 Level
和 2
**。在循环调用结束时 NextPerm
, ,完成后将返回 false。
* VB 会为你做这件事。
** 你可以稍微改变 NextPerm 来使这个变得不必要,但这样更清晰。
Option Explicit
Function NextPerm(Perm() As Long, Stack() As Long, Level As Long) As Boolean
Dim N As Long
If Level = 2 Then
Swap Perm(1), Perm(2)
Level = 3
Else
While Stack(Level) = Level - 1
Stack(Level) = 0
If Level = UBound(Stack) Then Exit Function
Level = Level + 1
Wend
Stack(Level) = Stack(Level) + 1
If Level And 1 Then N = 1 Else N = Stack(Level)
Swap Perm(N), Perm(Level)
Level = 2
End If
NextPerm = True
End Function
Sub Swap(A As Long, B As Long)
A = A Xor B
B = A Xor B
A = A Xor B
End Sub
'This is just for testing.
Private Sub Form_Paint()
Const Max = 8
Dim A(1 To Max) As Long, I As Long
Dim S(3 To Max) As Long, J As Long
Dim Test As New Collection, T As String
For I = 1 To UBound(A)
A(I) = I
Next
Cls
ScaleLeft = 0
J = 2
Do
If CurrentY + TextHeight("0") > ScaleHeight Then
ScaleLeft = ScaleLeft - TextWidth(" 0 ") * (UBound(A) + 1)
CurrentY = 0
CurrentX = 0
End If
T = vbNullString
For I = 1 To UBound(A)
Print A(I);
T = T & Hex(A(I))
Next
Print
Test.Add Null, T
Loop While NextPerm(A, S, J)
J = 1
For I = 2 To UBound(A)
J = J * I
Next
If J <> Test.Count Then Stop
End Sub
不同的作者描述了其他方法。高德纳描述了两种,一种给出了词汇顺序,但复杂且缓慢,另一种被称为简单变化的方法。高杰和王殿军也写了一篇有趣的论文。
在红宝石中:
str = "a"
100_000_000.times {puts str.next!}
虽然速度很快,但是需要一些时间=)。当然,如果您对短字符串不感兴趣,您可以从“aaaaaaaa”开始。
不过,我可能误解了实际的问题 - 在其中一篇文章中,听起来好像您只需要一个强力字符串库,但在主要问题中,听起来您需要排列特定的字符串。
您的问题与此有点相似: http://beust.com/weblog/archives/000491.html (列出所有没有数字重复的整数,这导致了很多语言解决这个问题,ocaml 人员使用排列,而一些 java 人员使用另一种解决方案)。
这段代码在 python 中调用时 allowed_characters
设置 [0,1]
最多 4 个字符,将生成 2^4 个结果:
['0000', '0001', '0010', '0011', '0100', '0101', '0110', '0111', '1000', '1001', '1010', '1011', '1100', '1101', '1110', '1111']
def generate_permutations(chars = 4) :
#modify if in need!
allowed_chars = [
'0',
'1',
]
status = []
for tmp in range(chars) :
status.append(0)
last_char = len(allowed_chars)
rows = []
for x in xrange(last_char ** chars) :
rows.append("")
for y in range(chars - 1 , -1, -1) :
key = status[y]
rows[x] = allowed_chars[key] + rows[x]
for pos in range(chars - 1, -1, -1) :
if(status[pos] == last_char - 1) :
status[pos] = 0
else :
status[pos] += 1
break;
return rows
import sys
print generate_permutations()
希望这对您有用。适用于任何字符,而不仅仅是数字
尽管这并不能完全回答您的问题,但这是一种从多个相同长度的字符串生成字母的每种排列的方法:例如,如果您的单词是“coffee”、“joomla”和“moodle”,您可以期待类似“coodle”、“joodee”、“joffle”等的输出。
基本上,组合的数量是(单词数)的(每个单词的字母数)次方。因此,在 0 和组合数 - 1 之间选择一个随机数,将该数字转换为基数(单词数),然后使用该数字的每个数字作为从哪个单词获取下一个字母的指示符。
例如:在上面的例子中。3 个单词,6 个字母 = 729 种组合。选择一个随机数:465.转换为基数 3:122020。从单词 1 中取出第一个字母,从单词 2 中取出第二个字母,从单词 2 中取出第三个字母,从单词 0 中取出第四个字母...然后你得到...“乔夫尔”。
如果您想要所有排列,只需从 0 循环到 728 即可。当然,如果你只是选择一个随机值,那么很多 更简单 不太令人困惑的方法是循环遍历字母。如果您想要所有排列,此方法可以让您避免递归,而且它让您看起来像是了解数学(Tm值)!
如果组合数量过多,您可以将其分解为一系列较小的单词并在最后将它们连接起来。
时间:2019-03-17 标签:c#iterative
public List<string> Permutations(char[] chars)
{
List<string> words = new List<string>();
words.Add(chars[0].ToString());
for (int i = 1; i < chars.Length; ++i)
{
int currLen = words.Count;
for (int j = 0; j < currLen; ++j)
{
var w = words[j];
for (int k = 0; k <= w.Length; ++k)
{
var nstr = w.Insert(k, chars[i].ToString());
if (k == 0)
words[j] = nstr;
else
words.Add(nstr);
}
}
}
return words;
}
有一个迭代的Java实现 罕见数学 (适用于对象列表):
/**
* Generate the indices into the elements array for the next permutation. The
* algorithm is from Kenneth H. Rosen, Discrete Mathematics and its
* Applications, 2nd edition (NY: McGraw-Hill, 1991), p. 284)
*/
private void generateNextPermutationIndices()
{
if (remainingPermutations == 0)
{
throw new IllegalStateException("There are no permutations " +
"remaining. Generator must be reset to continue using.");
}
else if (remainingPermutations < totalPermutations)
{
// Find largest index j with
// permutationIndices[j] < permutationIndices[j + 1]
int j = permutationIndices.length - 2;
while (permutationIndices[j] > permutationIndices[j + 1])
{
j--;
}
// Find index k such that permutationIndices[k] is smallest integer
// greater than permutationIndices[j] to the right
// of permutationIndices[j].
int k = permutationIndices.length - 1;
while (permutationIndices[j] > permutationIndices[k])
{
k--;
}
// Interchange permutation indices.
int temp = permutationIndices[k];
permutationIndices[k] = permutationIndices[j];
permutationIndices[j] = temp;
// Put tail end of permutation after jth position in increasing order.
int r = permutationIndices.length - 1;
int s = j + 1;
while (r > s)
{
temp = permutationIndices[s];
permutationIndices[s] = permutationIndices[r];
permutationIndices[r] = temp;
r--;
s++;
}
}
--remainingPermutations;
}
/**
* Generate the next permutation and return a list containing
* the elements in the appropriate order. This overloaded method
* allows the caller to provide a list that will be used and returned.
* The purpose of this is to improve performance when iterating over
* permutations. If the {@link #nextPermutationAsList()} method is
* used it will create a new list every time. When iterating over
* permutations this will result in lots of short-lived objects that
* have to be garbage collected. This method allows a single list
* instance to be reused in such circumstances.
* @param destination Provides a list to use to create the
* permutation. This is the list that will be returned, once
* it has been filled with the elements in the appropriate order.
* @return The next permutation as a list.
*/
public List<T> nextPermutationAsList(List<T> destination)
{
generateNextPermutationIndices();
// Generate actual permutation.
destination.clear();
for (int i : permutationIndices)
{
destination.add(elements[i]);
}
return destination;
}
def gen( x,y,list): #to generate all strings inserting y at different positions
list = []
list.append( y+x )
for i in range( len(x) ):
list.append( func(x,0,i) + y + func(x,i+1,len(x)-1) )
return list
def func( x,i,j ): #returns x[i..j]
z = ''
for i in range(i,j+1):
z = z+x[i]
return z
def perm( x , length , list ): #perm function
if length == 1 : # base case
list.append( x[len(x)-1] )
return list
else:
lists = perm( x , length-1 ,list )
lists_temp = lists #temporarily storing the list
lists = []
for i in range( len(lists_temp) ) :
list_temp = gen(lists_temp[i],x[length-2],lists)
lists += list_temp
return lists
def permutation(str)
posibilities = []
str.split('').each do |char|
if posibilities.size == 0
posibilities[0] = char.downcase
posibilities[1] = char.upcase
else
posibilities_count = posibilities.length
posibilities = posibilities + posibilities
posibilities_count.times do |i|
posibilities[i] += char.downcase
posibilities[i+posibilities_count] += char.upcase
end
end
end
posibilities
end
这是我对非递归版本的看法
python式的解决方案:
from itertools import permutations
s = 'ABCDEF'
p = [''.join(x) for x in permutations(s)]