图解旋转数组_Go实现C++ STL中的随机迭代器版本的rotate算法

本文介绍了一种使用Go语言实现的高效数组旋转算法。该算法基于辗转相除法求最大公约数,通过循环置换的方式完成数组的旋转,相较于传统的三次反转方法,在效率上有显著提升。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

旋转数组_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
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值