什么是全排列
从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。当m=n时所有的排列情况叫全排列。 (来自百度百科)
排列公式
全排列公式:
含有重复元素全排列数量:
生成全排列的方法
以集合{1,2,3}为例
1.递归(穷举的思路)
获取一组数据的全排列,即为n个元素放在n个位置,有多少种不同的放置方式
- 只有1个元素,那么只有1中放置方式
- 我们首先固定n个位置,先思考第一个位置可以放置哪些元素
- 从集合中取出1个元素放在第一个位置后,将放置的元素从集合中移除
- 考虑剩余的元素和剩余的位置,重复上述过程(重复的过程及递归的过程)
2. 字典序
如果集合中的所有元素间都是可以比较的,规定两个集合的比较方式为从第一个元素开始,相同位置的两个元素进行比较;若不等,则集合的大小即为此位置两个元素的大小;若相等,继续比较下一个位置的元素,若所有位置元素都相等,则两个集合相等
PS:这里用集合其实不太恰当,数学里集合中的元素是没有顺序的,而这里的集合是有序的
集合元素递增排列即为该集合的最小排列,递减排列即为最大排列;因此求解集合全排列的过程为:给定一个排列,找到所有排列中大于此排列的最小排列,依次迭代,直到找到的排列为递增排列。
PS1: 如果当前排列已是递减排列,则无新排列
- 从右向左找到第一个非递增(是指从右向左递增)元素的位置:descIndex,非递增元素:descElem
- 从右向左找到第一个大于descElem的元素位置:greaterIndex,元素:greaterElem
- 交换descElem和greaterElem
- descIndex后的元素按照升序排列
完整代码
package alg
import (
"sort"
"testing"
)
// 阶乘计算
func sliceArrayCount(count int) int {
if count == 0 {
return 1
}
var result = 1
for ; count > 0; count-- {
result *= count
}
return result
}
func Test_sliceArrayCount(t *testing.T) {
t.Log(sliceArrayCount(2), sliceArrayCount(3), sliceArrayCount(4))
}
// 生成全排列
func GenIntSlicePermutation(ori []int) [][]int {
resSlice := make([][]int, 0, sliceArrayCount(len(ori)))
permutation(&resSlice, ori, 0)
return resSlice
}
// 递归思想: f(n)=n*f(n-1)
// 缩小问题规模:确定首位,对剩余元素进行全排列;首位与其他位元素交换,再对剩余元素进行全排列
// 即for i:=0;i<len(ori);i++ ori[0],ori[i]=ori[i],ori[0]; 排列ori[1:]
// 递归出口:1个元素的排列数为1
// 对于序列[1,2,3]
// 首位为1,对[2,3]进行全排列
// 交换1<->2,对[2,3]进行全排列
// 交换1<->3,对[2,1]进行全排列
// 对[2,3]进行全排列的过程与上相同,
// 递归出口: 只有一个元素时,排列数为1
func permutation(permutationSlice *[][]int, seq []int, index int) {
if seq == nil || len(seq) == 0 {
return
}
// 判断递归出口
if index == len(seq) {
res := make([]int, len(seq))
copy(res, seq)
*permutationSlice = append(*permutationSlice, res)
return
}
// 循环过程
for i := index; i < len(seq); i++ {
seq[i], seq[index] = seq[index], seq[i] // 选定元素放在当次递归的第一个位置
// 递归过程
permutation(permutationSlice, seq, index+1)
seq[i], seq[index] = seq[index], seq[i] // 使用的原数组进行的交换,保证在循环过程中元素的顺序不变
}
}
// 升序排列->降序排列及过程中的所有排列构成元素的全排列
func GenIntSlicePermutationWithDict(ori []int) [][]int {
resultSlice := make([][]int, 0, sliceArrayCount(len(ori)))
// 升序排列
sort.SliceStable(ori, func(i, j int) bool {
return ori[i] < ori[j]
})
// 首个排列
copyElem := make([]int, len(ori))
copy(copyElem, ori)
resultSlice = append(resultSlice, copyElem)
for {
hasNext, nextPermutation := intSliceNextPermutation(ori)
if !hasNext {
return resultSlice
}
resultSlice = append(resultSlice, nextPermutation)
}
}
// 如何找出字典序升序的所有排序
// 从右向左找到第一个非递减(递减是指从左向右)元素的位置:descIndex,非递增元素:descElem
// 从右向左找到第一个大于descElem的元素位置:greaterIndex,元素:greaterElem
// 交换descElem和greaterElem
// descIndex后的元素按照升序排列
// 循环出口:元素降序排列
func intSliceNextPermutation(ori []int) (hasNext bool, next []int) {
elemNum := len(ori)
// 从右向左寻找第一组非递减的元素
descIndex := -1
for rIndex := elemNum - 1; rIndex > 0; rIndex-- {
if ori[rIndex-1] <= ori[rIndex] {
descIndex = rIndex - 1
break
}
}
if descIndex == -1 {
return false, nil
}
// 从右向左寻找第一个大于ori[descIndex]的元素
var greaterIndex int
for rIndex := elemNum - 1; rIndex > descIndex; rIndex-- {
if ori[rIndex] > ori[descIndex] {
greaterIndex = rIndex
break
}
}
// 交换元素
ori[descIndex], ori[greaterIndex] = ori[greaterIndex], ori[descIndex]
// index + 1升序排列
sort.SliceStable(ori[descIndex+1:], func(i, j int) bool {
return ori[descIndex+1+i] < ori[descIndex+1+j]
})
next = make([]int, len(ori))
copy(next, ori)
return true, next
}
/**** for test ***********/
func TestGenIntSlicePermutation(t *testing.T) {
resSlice := GenIntSlicePermutation([]int{1, 2, 3, 4})
t.Logf("len:%d, permutation:%v\n", len(resSlice), resSlice)
}
func TestGenIntSlicePermutationWithDict(t *testing.T) {
resSlice := GenIntSlicePermutationWithDict([]int{1, 2, 3, 4})
t.Logf("dict len:%d, permutation:%v\n", len(resSlice), resSlice)
}
// for benchmark
func BenchmarkGenIntSlicePermutation(b *testing.B) {
var ori = []int{1, 2, 3, 4}
b.ResetTimer()
for i := 0; i < b.N; i++ {
GenIntSlicePermutationWithDict(ori)
}
}
func BenchmarkGenIntSlicePermutationWithDict(b *testing.B) {
var ori = []int{1, 2, 3, 4}
b.ResetTimer()
for i := 0; i < b.N; i++ {
GenIntSlicePermutationWithDict(ori)
}
}
/*
➜ permutation go test -v -run=none -bench=. -benchmem
goos: darwin
goarch: amd64
pkg: demo/alg/permutation
cpu: Intel(R) Core(TM) i7-1068NG7 CPU @ 2.30GHz
BenchmarkGenIntSlicePermutation
BenchmarkGenIntSlicePermutation-8 421508 2435 ns/op 2304 B/op 61 allocs/op
BenchmarkGenIntSlicePermutationWithDict
BenchmarkGenIntSlicePermutationWithDict-8 483420 2494 ns/op 2304 B/op 61 allocs/op
PASS
ok demo/alg/permutation 2.587s
*/