使用Ruby和递归查找所有可能的排列

时间:2014-08-10 00:12:23

标签: ruby arrays

我一直试图解决一个简单的测验问题,找到使用Ruby和递归的字符串的所有可能的排列。

我有以下Ruby代码:

def permutation(string)
  return [string] if string.size < 2

  chr    = string.chars.first
  perms  = permutation(string[1..-1])

  result = []

  for perm in perms
    for i in (0..perm.size)
      result << (perm[0..i] + chr + perm[i..-1])
    end
  end

  return result  
end

每当我尝试使用 puts permutation(“abc”)测试代码时,我会得到以下输出:

cacbc
cbabc
cbcac
cbca
cacb
cbab
cba

从理论上讲,它应该是一个非常简单和直接的问题,但我确信我做错了什么。最有可能的是它与循环范围有关。我知道Ruby Array类有实例方法 permutation 来做到这一点,但我正在努力解决它的实践。

请注意,当前实现的复杂度为O(N!)。反正有进一步提升性能吗?

2 个答案:

答案 0 :(得分:12)

要了解困难可能是什么,让我们尝试一个更简单的例子:

string = "ab"

您想要的结果是["ab", "ba"]。让我们看看你得到了什么:

string.size #=> 2

所以我们不会在

时回来
return [string] if string.size < 2
  #=> return ["ab"] if "ab".size < 2

已执行。

接下来我们计算:

chr = string.chars.first #=> "a"

请注意,进行此计算的更直接方法如下:

chr = string[0]  #=> "a"

或者更好,使用String#chr

chr = string.chr #=> "a"

后者说明了为什么chr不是变量名的最佳选择。

下一步

perms = permutation(string[1..-1])
  #=> = permutation("b")

我现在将缩进返回值以强调我们第二次调用permutationpermuation的论点是:

  string #=> "b"

现在我们执行:

  return [string] if string.size < 2
  #=> return ["b"] if "b".size < 2

我们返回["b"],所以(回到permutation的原始号召):

perms = ["b"]

与之前计算的chr => "a"一起使用。下一个:

result = []

for perm in perms
  for i in (0..perm.size)
    result << (perm[0..i] + chr + perm[i..-1])
  end
end

由于perms仅包含单个元素"b",因此两个for循环简化为:

for i in (0.."b".size)
  result << ("b"[0..i] + "a" + "b"[i..-1])
end

是:

for i in (0..1)
  result << ("b"[0..i] + "a" + "b"[i..-1])
end

请注意"b"[0..0]"b"[0..1]"b"[0..-1]都等于"b"[0],只有"b""b"[1..-1] #=> ''。因此,当i => 0时,我们执行:

result << ("b"[0..0] + "a" + "b"[0..-1])
  #=> result << ("b" + "a" + "b")
  #=> result << "bab"

以及i => 1

result << ("b"[0..1] + "a" + "b"[1..-1])
  #=> result << ("b" + "a" + "")
  #=> result << "ba"

这样:

result => ["bab" + "ba"]

显然不是你想要的。

您需要做的是将双for循环更改为:

for perm in perms
  result << chr + perm
  for i in (1..perm.size-1)
    result << (perm[0..i-1] + chr + perm[i..-1])
  end
  result << perm + chr
end

可以通过采用String#insert方法更紧凑地编写:

for perm in perms
  for i in (0..perm.size)
    result << perm.dup.insert(i,chr)
  end
end
你通常会看到这样写的

perms.each_with_object([]) do |perm, result|
  (0..perm.size).each { |i| result << perm.dup.insert(i,chr) }
end

请注意,在发送.dup之前我们必须insert字符串,因为insert会修改字符串。

这样做,你不需要result = []。您也不需要return result,因为parms.each_with_object会返回result,如果没有return语句,则该方法会返回最后计算的数量。此外,如果需要,您也不需要临时变量perms(或ch。)

完全放上这个,我们有:

def permutation(string)
  return [string] if string.size < 2
  ch = string[0]
  permutation(string[1..-1]).each_with_object([]) do |perm, result|
    (0..perm.size).each { |i| result << perm.dup.insert(i,ch) }
  end
end

让我们试一试:

permutation("ab")
  #=> ["ab", "ba"]
permutation("abc")
  #=> ["abc", "bac", "bca", "acb", "cab", "cba"]
permutation("abcd")
  #=> ["abcd", "bacd", "bcad", "bcda", "acbd", "cabd",
  #    "cbad", "cbda", "acdb", "cadb", "cdab", "cdba",
  #    "abdc", "badc", "bdac", "bdca", "adbc", "dabc",
  #    "dbac", "dbca", "adcb", "dacb", "dcab", "dcba"]
Eki,你在图片中有哪一个?

答案 1 :(得分:6)

您可以使用Array#permutation

def permutation(string)
  string.permutation(string.size).to_a
end

permutation('abc'.chars)
# => [["a", "b", "c"], ["a", "c", "b"], ["b", "a", "c"], ["b", "c", "a"],
#     ["c", "a", "b"], ["c", "b", "a"]]

UPDATE 不使用Array#permutation

def permutation(string)
  return [''] if string.empty?

  chrs = string.chars
  (0...string.size).flat_map { |i|
    chr, rest = string[i], string[0...i] + string[i+1..-1]
    permutation(rest).map { |sub|
      chr + sub
    }
  }
end

permutation('abc')
# => ["abc", "acb", "bac", "bca", "cab", "cba"]