本文简述了 序列 rotate 的一些知识

基础

本篇文章中所说的 序列 rotate(旋转) 可能跟我们平常理解的 图像 rotate(旋转) 不太相同,所谓 序列 rotate,其实就是一种调整序列元素顺序的方法,而要理解这种方法之所以称为 rotate(旋转),我们需要将序列想象为一个环形结构,而 rotate 操作其实就是在这个环形结构上对序列进行旋转.

举个简单的例子,假设我们有序列 l = { 1 , 2 , 3 , 4 , 5 } l = \{ 1, 2, 3, 4, 5 \} l={1,2,3,4,5},我们对其执行 rotate 操作:

rotate(l, 3)

其中 rotate 函数的第一个参数即是我们需要旋转的列表(即 l l l),第二个参数 3 则是旋转的 o f f s e t offset offset,表示我们要旋转序列的前 3 个元素, rotate 之后的结果则为 l = { 4 , 5 , 1 , 2 , 3 } l = \{ 4, 5, 1, 2, 3 \} l={4,5,1,2,3}

下面的示意图展示了这个过程:


实现

实现 rotate 的方法很多,比较简单的一种方法就是借助临时缓冲区:首先将需要旋转的序列元素拷贝至临时缓冲区,再将序列的剩余元素移动至序列前部,再将临时缓冲区的元素拷贝回来(至序列尾部)即可.

function rotate(list, offset)local buffer = {}for i = 1, offset dotable.insert(buffer, list[i])endlocal index = 1for i = offset + 1, #list dolist[index] = list[i]index = index + 1endfor i = 1, #buffer dolist[index] = buffer[i]index = index + 1end
end

当然,使用临时缓冲区实现 rotate 的方法还有很多,这些方法自然也都需要临时缓冲区的帮助,那有没有不使用临时缓冲区的实现方式呢?

最简单的一种(不使用临时缓冲区)实现方式就是将旋转元素依次移动至序列尾部,并整体前移其余的序列元素:

function rotate(list, offset)for i = 1, offset dolocal t = list[1]for j = 2, #list dolist[j - 1] = list[j]endlist[#list] = tend
end

上面这种实现方式的时间复杂度比较高(平均情况下为 O ( n 2 ) O(n^2) O(n2)),另一种技巧性比较强但时间复杂度比较低(为 O ( n ) O(n) O(n))的实现方式则是借助 序列 revert 来实现 序列 rotate.

所谓 序列 revert, 其实就是将序列的元素反序,还是拿之前 l = { 1 , 2 , 3 , 4 , 5 } l = \{ 1, 2, 3, 4, 5 \} l={1,2,3,4,5} 举例,经过 revert 操作之后(revert 函数的三个参数分别为: 操作列表, 起始索引 和 结束索引):

revert(l, 1, 5)

l l l 中的元素顺序变为 { 5 , 4 , 3 , 2 , 1 } \{ 5, 4, 3, 2, 1 \} {5,4,3,2,1}.

而要实现 序列 rotate, 我们仅需要对序列进行 3 次 revert 即可:

function rotate(list, offset)revert(list, 1, offset)revert(list, offset + 1, #list)revert(list, 1, #list)
end

对于这种实现方式,我个人觉得很难从直观上进行理解,所以我从数学证明的角度出发进行了一点验证:

  • 首先,我们可以证明,在索引范围为 [ i , j ] [i, j] [i,j] 上执行 序列 revert 操作,原始索引 a a a 和变换后(revert 后)索引 a ′ a' a′ 存在以下关系:

a ′ = i + j − a a' = i + j - a a′=i+j−a

  • 接着,我们可以证明,对于 序列 rotate 操作,旋转元素索引 a a a 的最终索引 a ′ a' a′ 和 剩余元素索引 b b b 的最终索引 b ′ b' b′ 存在以下关系(其中 n n n 为序列长度, o f f s e t offset offset 为旋转偏移):

a ′ = n − o f f s e t + a b ′ = − o f f s e t + b \begin{aligned} & a' = n - offset + a \\ & b' = -offset + b \end{aligned} ​a′=n−offset+ab′=−offset+b​

  • 最后,对于旋转元素索引 a a a,可以证明经过两步 序列 revert 后,可以正确变换为最终索引 a ′ a' a′( b b b 可以正确变换为 b ′ b' b′ 同证):

t ′ = 1 + o f f s e t − a ← ∵ r e v e r t ( l i s t , 1 , o f f s e t ) a ′ = 1 + n − t ′ ← ∵ r e v e r t ( l i s t , 1 , n ) = 1 + n − 1 − o f f s e t + a = n − o f f s e t + a \begin{aligned} & t' = 1 + offset - a \leftarrow \space \because revert(list, 1, offset) \\ & a' = 1 + n - t' \leftarrow \space \because revert(list, 1, n) \\ &\ \ \ = 1 + n - 1 - offset + a \\ &\ \ \ = n - offset + a \end{aligned} ​t′=1+offset−a← ∵revert(list,1,offset)a′=1+n−t′← ∵revert(list,1,n)   =1+n−1−offset+a   =n−offset+a​

更新

上面基于索引计算的验证方式比较繁琐,也不直观,更好的证明方式其实是对 r e v e r t revert revert 操作进行抽象:

我们将原始序列按照 r o t a t e rotate rotate 的旋转偏移分为两个子序列 A B AB AB,我们想做的就是通过 r e v e r t revert revert 操作把 A B AB AB 变换为 B A BA BA.

对于 r e v e r t revert revert 操作(以下简写为 r r r),我们有( S S S 代表序列):

r ( r ( S ) ) = S r ( S 1 S 2 ) = r ( S 2 ) r ( S 1 ) r(r(S)) = S \\ r(S_1S_2) = r(S_2)r(S_1) r(r(S))=Sr(S1​S2​)=r(S2​)r(S1​)

于是对于之前的目标序列 B A BA BA,我们有:

B A = r ( r ( B ) ) r ( r ( A ) ) = r ( r ( A ) r ( B ) ) BA = r(r(B))r(r(A)) = r(r(A)r(B)) BA=r(r(B))r(r(A))=r(r(A)r(B))

原命题即得证


除了上面这种实现方法,我们还可以使用递归的方法来实现 序列 rotate, 仍然考虑序列 l = { 1 , 2 , 3 , 4 , 5 } l = \{ 1, 2, 3, 4, 5 \} l={1,2,3,4,5}, 我们要进行 o f f s e t offset offset 为 3 的旋转操作,我们首先将序列中的元素 2 , 3 2, 3 2,3 和元素 4 , 5 4, 5 4,5 交换位置,则原序列变为:

l = { 1 , 4 , 5 , 2 , 3 } l = \{ 1, 4, 5, 2, 3 \} l={1,4,5,2,3}

此时, 元素 2 , 3 2, 3 2,3 已经在目标位置上了,此时我们要做的就是对 l l l 进行一次更小范围的 rotate 操作(对元素 1 , 4 , 5 1, 4, 5 1,4,5 进行一次 o f f s e t offset offset 为 1 的 rotate),依次类推,我们自然会得到 rotate 的递归实现(我们也可以将递归转为循环实现):

local function rotate_recur(list, start_index, end_index, offset_index)local left_count = offset_index - start_index + 1local right_count = end_index - offset_indexif left_count <= 0 or right_count <= 0 or left_count + right_count <= 1 then-- do nothingelseif left_count == right_count thenlocal index = start_indexfor i = 1, left_count dolocal t = list[index]list[index] = list[offset_index + i]list[offset_index + i] = tindex = index + 1endelseif left_count < right_count thenlocal index = start_indexfor i = 1, left_count dolocal t = list[index]list[index] = list[offset_index + i]list[offset_index + i] = tindex = index + 1endrotate_recur(list, offset_index + 1, end_index, offset_index + left_count)elselocal index = end_indexfor i = 1, right_count dolocal t = list[index]list[index] = list[offset_index - i + 1]list[offset_index - i + 1] = tindex = index - 1endrotate_recur(list, start_index, offset_index, offset_index - right_count)end
endfunction rotate(list, offset)rotate_recur(list, 1, #list, offset)
end

编程小知识 之 序列 rotate相关推荐

  1. c语言代码游戏跳一跳,微信小程序《跳一跳》游戏里的编程小知识,你知道吗?...

    微信小程序<跳一跳>游戏里的编程小知识,你知道吗? 今日你跳了吗? 玩过的朋友都知道,跳一跳里的游戏操作非常简单,就用手指按住屏幕按住施放进行跳跃,整个游戏是个人都可以很轻松玩起来! 游戏 ...

  2. Linux网络编程小知识(字节序、IP格式、函数、子网掩码、DNS域名解析代码实现)

    参考:网络编程前的一些小知识–Linux笔记 作者:一只青木呀 发布时间: 2021-04-12 23:19:10 网址:https://blog.csdn.net/weixin_45309916/a ...

  3. 编程小知识之 Dithering

    本文简单介绍了 Dithering(抖动) 的一些知识 图形后处理有一种操作称为 Dithering(抖动),所谓 Dithering,就是一种能够在较小色彩空间上"模拟出"较大色 ...

  4. python编程小知识_分享Python开发中要注意的十个小贴士

    大家请注意:这篇文中假设我们都用的是Python 3 1. 列表推导式 你有一个list:bag = [1, 2, 3, 4, 5] 现在你想让所有元素翻倍,让它看起来是这个样子:[2, 4, 6, ...

  5. 编程小知识之时间显示

    本文简述了编程中常见的时间显示问题 开发中总会在各种场景下遇到需要显示时间的情况,显示的格式要求又往往五花八门,正常的譬如: "2018年12月29日20点30分15秒", 简洁一 ...

  6. 编程小知识:文件扩展名的作用是什么?通俗易懂的文件扩展名详解

    介绍 文件扩展名(filename extension)也称为文件的后缀名,是操作系统用来标记文件类型的一种机制,在Windows系统下,扩展名还可以告诉操作系统默认用什么软件打开文件.通常来说,一个 ...

  7. 编程小知识之 CanvasScaler 的一点知识

    本文简述了 Unity 中 CanvasScaler 的一点知识 制作 UI 时,一般都需要进行多分辨率适配,基本的方法大概有以下几种: UI 参照单一的分辨率(参考分辨率)进行制作,实际显示时按照某 ...

  8. python编程小知识tips 20220720

    1.格式化是如输出 def p(n):print("f(%d)=%d%s素数" % n % f(n) % ("是" if isprime(n) else &qu ...

  9. 8条嵌入式C语言编程小知识总结

    1. 流水线被指令填满时才能发挥最大效能,即每时钟周期完成一条指令的执行(仅指单周期指令).如果程序发生跳转,流水线会被清空,这将需要几个时钟才能使流水线再次填满.因此,尽量少的使用跳转指令可以提高程 ...

最新文章

  1. IDC中国AI服务器报告出炉:浪潮继续蝉联第一,份额超过其他玩家总和
  2. windows下sse性能对比
  3. 程序员应当学会“偷懒”
  4. 只能选择分卷文件的第一部分_文件太大如何分块压缩?BetterZip简单搞定
  5. 1.0jpa 2.0_JPA 2.1:不同步的持久性上下文
  6. oracle impdp导入时卡住,Oracle:impdp导入等待statement suspended, wait error to be cleared
  7. 编程开发之--java多线程学习总结(3)类锁
  8. python自动化运维之python2.6升级2.7和集中病毒扫描
  9. Android开发中使用startActivityForResult()方法从Activity A跳转Activity B出现B退出时A也同时退出的解决办法...
  10. 设置 cell点击 背景色
  11. Hibernate多表关联查询记录映射
  12. 全局异常捕捉用法解析
  13. 关于BIO和NIO的理解
  14. paip.sql2k,sql2005,sql2008,sql2008 r2,SQL2012以及EXPRESS版本的区别
  15. python开发简单计算器下载_python简单计算器
  16. 【PID优化】基于樽海鞘算法PID控制器优化设计含Matlab源码
  17. 一个bug看一天,写代码像cxk
  18. 蓝牙音乐和导航语音播放混音卡顿问题
  19. linux的rm命令和 rmdir命令
  20. Genymotion ova官方下载地址

热门文章

  1. 小米对鸿蒙的态度,在提及是否支持鸿蒙时,中兴的态度强硬!小米的立场在意料之外...
  2. 高中教学有计算机课吗,高中计算机课教学的思考
  3. mysql cluster 备份_WaveCN.com - 站长手记 - 站长手记 - 解决MySQL Cluster 备份总是失败,提示文件已存在的问题...
  4. 二分查找求最大值用master公式
  5. c语言15除以2得到8,C语言程序设计100例之(15):除法算式
  6. 12.权重衰退+QA
  7. 怎么在win7中增加“显示桌面”快速启动栏
  8. 如何解决七牛云图片链接失效问题?
  9. 前端环境安装遇到的问题
  10. matlabR2016a版本下载容易走进的错误(软件管家下的