使用Go生成所有排列

18

我正在寻找一种生成给定元素列表的所有可能排列的方法。类似于Python中的itertools.permutations(arr)

permutations ([])
[]

permutations ([1])
[1]

permutations ([1,2])
[1, 2]
[2, 1]

permutations ([1,2,3])
[1, 2, 3]
[1, 3, 2]
[2, 1, 3]
[2, 3, 1]
[3, 1, 2]
[3, 2, 1]

与生成器(Python中的生成器)一样,我不关心排列是否按需生成,也不关心它们是否按字典顺序排序。我所需要的仅仅是以某种方式获得这些n!个排列。

8个回答

31

有很多算法可以生成排列。我发现其中最简单的一个是Heap算法:

它通过选择一对元素进行交换来从前一个排列生成每个排列。

上面的链接概述了这个想法和打印排列的伪代码。这是我实现的该算法,它返回所有的排列。

func permutations(arr []int)[][]int{
    var helper func([]int, int)
    res := [][]int{}

    helper = func(arr []int, n int){
        if n == 1{
            tmp := make([]int, len(arr))
            copy(tmp, arr)
            res = append(res, tmp)
        } else {
            for i := 0; i < n; i++{
                helper(arr, n - 1)
                if n % 2 == 1{
                    tmp := arr[i]
                    arr[i] = arr[n - 1]
                    arr[n - 1] = tmp
                } else {
                    tmp := arr[0]
                    arr[0] = arr[n - 1]
                    arr[n - 1] = tmp
                }
            }
        }
    }
    helper(arr, len(arr))
    return res
}

以下是如何使用它的示例(Go playground):

arr := []int{1, 2, 3}
fmt.Println(permutations(arr))
[[1 2 3] [2 1 3] [3 2 1] [2 3 1] [3 1 2] [1 3 2]]

需要注意的是,排列并不按字典顺序排序(就像您在 itertools.permutations 中看到的那样)。如果出于某种原因您需要对其进行排序,我发现一种方法是从阶乘数系统生成它们(在排列部分中有描述,并允许快速找到第n个字典序排列)。

P.S. 您也可以查看其他人的代码 这里这里


17

以下是一个可以遍历所有排列而不必提前生成它们的代码。切片 p 将中间状态保存为 Fisher-Yates 洗牌算法中的偏移量。这具有很好的特性,即 p 的零值描述了恒等排列。

package main

import "fmt"

func nextPerm(p []int) {
    for i := len(p) - 1; i >= 0; i-- {
        if i == 0 || p[i] < len(p)-i-1 {
            p[i]++
            return
        }
        p[i] = 0
    }
}

func getPerm(orig, p []int) []int {
    result := append([]int{}, orig...)
    for i, v := range p {
        result[i], result[i+v] = result[i+v], result[i]
    }
    return result
}

func main() {
    orig := []int{11, 22, 33}
    for p := make([]int, len(orig)); p[0] < len(p); nextPerm(p) {
        fmt.Println(getPerm(orig, p))
    }
}

2
优秀的实现(+1)。谢谢你提供另一个算法。只有一个小错误:当切片为空时会出现恐慌。您还可以添加排列按字典顺序排列的信息。 - Salvador Dali
2
我已经将这个项目转化为一个库,用于我的个人项目。我将函数转化为方法,修复了对空切片的处理,并添加了从通道读取的选项。请看一下。你介意我将其开源吗? - Schwern
4
据我所知,这是按字典顺序排列的排列中最快的算法!如果将getPerm()第一行更改为result:=make([]int, len(orig)); copy(result, orig);,则可以再加速约10%。原因是附加调用会导致重新分配内存,以根据附加源的利用情况分配额外的内存(附加操作时间关键)。 (go1.10.3 darwin / amd64) - ABri

2
var res [][]int
func permute(nums []int) [][]int {
    res=make([][]int,0)
    n:=len(nums)
    var backTrack func(int)
    backTrack=func(first int){
        if first == n{
            temp:=make([]int, n)
            copy(temp,nums)
            res = append(res, temp)
        }
        for i:=first;i<n;i++{
            nums[first],nums[i] = nums[i],nums[first]
            backTrack(first+1)
            nums[first],nums[i] = nums[i],nums[first]
        }
    }
  
    backTrack(0)
    return res
}

0

这里是另一种变化:

// heap algorithm
func permutations(arr []int, l int, p [][]int) [][]int {
  if l == 1 { p = append(p, append([]int{}, arr...)) }
  for i := 0 ; i < l ; i++ {
    p = permutations(arr, l-1, p)
    if l % 2 == 1 {
      arr[0], arr[l-1] = arr[l-1], arr[0]
    } else {
      arr[i], arr[l-1] = arr[l-1], arr[i]
    }
  }
  return p
}

0
在我的情况下,我有一个数组的引用,然后我对你的例子做了一些更改:
func generateIntPermutations(array []int, n int, result *[][]int) {
    if n == 1 {
        dst := make([]int, len(array))
        copy(dst, array[:])
        *result = append(*result, dst)
    } else {
        for i := 0; i < n; i++ {
            generateIntPermutations(array, n-1, result)
            if n%2 == 0 {
                // Golang allow us to do multiple assignments
                array[0], array[n-1] = array[n-1], array[0]
            } else {
                array[i], array[n-1] = array[n-1], array[i]
            }
        }
    }
}
numbers := []int{0, 1, 2}
var result [][]int
generateIntPermutations(numbers, len(numbers), &result)

// result -> [[0 1 2] [1 0 2] [2 1 0] [1 2 0] [2 0 1] [0 2 1]]

谢谢@derricw,我忘记在函数中插入那个副本了。我的原始函数使用了一个字符串切片,所以在那个点上我不需要复制数组。我将来会尝试找到更好的方法。 - AndreDurao

0

另一个可用的代码

package permutations

import "fmt"

func AllPermutation(a []int) {
    var res [][]int
    calPermutation(a, &res, 0)
    fmt.Println(res)
}
func calPermutation(arr []int, res *[][]int, k int) {
    for i := k; i < len(arr); i++ {
        swap(arr, i, k)
        calPermutation(arr, res, k+1)
        swap(arr, k, i)
    }
    if k == len(arr)-1 {
        r := make([]int, len(arr))
        copy(r, arr)
        *res = append(*res, r)
        return
    }
}
func swap(arr []int, i, k int) {
    arr[i], arr[k] = arr[k], arr[i]
}
//result [[1 2 3] [1 3 2] [2 1 3] [2 3 1] [3 2 1] [3 1 2]]

0

使用简单算法的golang排列代码

package main

import "fmt"

func permute(arr[] int, start int, end int) {
        if start == end {
                fmt.Println(arr)
                return
        }
        for i:=start;i<end;i++ {
                arr[i],arr[start] = arr[start],arr[i]
                permute(arr,start+1,end)
                arr[i],arr[start] = arr[start],arr[i]
        }
}

func main() {
        arr := []int{1,2,3}
        end := len(arr)
        start := 0
        permute(arr,start,end)
}

0
你可以使用Iterium包来完成这个任务:https://github.com/mowshon/iterium 你的问题的示例代码如下:
permutations := iterium.Permutations([]string{"A", "B", "C", "D"}, 2)
toSlice, _ := permutations.Slice()

fmt.Println("Total:", permutations.Count())
fmt.Println(toSlice)

结果:

Total: 12

[
    [A, B] [A, C] [A, D] [B, A] [B, C] [B, D]
    [C, B] [C, A] [C, D] [D, B] [D, C] [D, A]
]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接