旋转数组_Go实现C++ STL中的随机迭代器版本的rotate算法
一、旋转数组(向左)的实现方法一
先上图:
显然这个方式就是通过3遍reverse来实现旋转数组,这个方法比较简单,不是本文讨论的重点。
二、Go实现的STL中的随机迭代器版本的rotate算法
//辗转相除法求最大公约数
func Gcd(a int ,b int) int{
le := Max(a,b)
fo := Min(a,b)
for fo!=0 {
t := le % fo
le = fo
fo = t
}
return le
}
//从某个初始元素开始,依次将其替换成其后相隔固定距离的元素。
//如果后面没有足够的偏移距离了,则又返回头部继续计算(相当于求模)。
func RotateCycle(sli *[]int, first int, last int, initial int, shift int){
val := (*sli)[initial]
ptr1 := initial
ptr2 := initial+shift
for ptr2 != initial{
(*sli)[ptr1] = (*sli)[ptr2]
ptr1 = ptr2
if last - ptr2 > shift{
ptr2 += shift
}else{
ptr2 = first + (shift - (last - ptr2))
}
}
(*sli)[ptr1] = val
}
func Itb(i int) bool{
if i==0 {
return false
}else{
return true
}
}
func Rotate(sli *[]int, first int, middle int ,last int) []int{
//求长度与旋转的个数的最大公约数
n := Gcd(last-first,middle-first)
for Itb(n) {
//func RotateCycle(sli *[]int, first int, last int, initial int, shift int)
RotateCycle(sli, first, last, first+n, middle-first)
n--
}
return *sli
}
1、下面这段话引用自 atyuwen的博客
上面只涉及到三个函数:Rotate、Gcd、RotateCycle。
(1)Gcd:求最大公约数。
(2)RotateCycle:是从某个初始元素开始,依次将其替换成其后相隔固定距离的元素。如果后面没有足够的偏移距离了,则又返回头部继续计算(相当于求模)。直到最后形成一个置换圈为止。
(3)Rotate:先是求出偏移距离和串总长的最大公约数,然后循环n次,分别以串的前n个元素为起点进行RotateCycle操作,over。但这怎么就保证了能正确地完成对输入串的rotate操作呢?
这就涉及到数论中的一个小定理:若有两个正整数m、n,且Gcd(m,n)=d
,那么序列{m%n, 2m%n, 3m%n,..., nm%n}
一定是{0, d, 2d,..., n-d}
的某个排列并重复出现d次,其中%号代表求模操作。比如若m=6, n=8,d=Gcd(m,n)=2
,那么{6%8, 12%8, 18%8,..., 48%8}
即为{0,2,4,6}
的某个排列并重复两次,事实上也正是{6,4,2,0, 6,4,2, 0}
。特别地,若m、n互素,d=1
,那么序列{m%n,2m%n,3m%n,...,(n-1)m%n}
实际上就是{1, 2, 3,..., n-1}
的某个排列。这个定理的证明过程可以很多书中找到(比如具体数学4.8节),这里不再详述。
了解这个引理后,就很容易看出Rotate函数的内涵了。若第一步求得的最大公约数n为1,那么只需一次RotateCycle就可以遍历到所有的元素,并将每个元素正确的替换为其后相距某个距离的元素,于是也就完成了循环左移操作。若n大于1,那么每一次RotateCycle只能将t / n(数组长度除以最大公约数)
的元素正确的左移,其中t为串的总长度,而这些被移动的元素是以shift(middle-first)
为等间距的,所以循环n次,并分别以串的前n个元素为起点进行RotateCycle操作,就能保证将所有的元素都移动到正确的位置上。
在这个新的算法中,每次RotateCycle需要t/n+1
次赋值,n
次循环,所以总共只需要t+n
次赋值操作,显然是要比前面所说的三次反转的算法快上许多。
2、下面附图展示RotateCycle的过程
上面的图有一步循环画了2个图,实际上只需要6次赋值就能完成旋转数组,比3次循环的12次赋值效率高不少。PS:向右旋转N
个元素的算法相当于向左旋转T-N(数组长度-N)
个元素。
三、简单测试
package main
import (
"utils"
)
func main() {
var t []int = make([]int,5,20)
for i := 0; i < 5;i++{
t[i] = i+1
}
var tmp0 []int = utils.Rotate(&t,0,2,5)
//var tmp1 []int = utils.Rotate(&t,0,0,0)
for _,v := range tmp0{
print(v)
}
}
环境:GoLand/Go SDK 1.14.4