说实话,刚开始碰python怎么旋转这事儿,我脑子是有点懵的。旋转?听着简单,但具体是让啥转?是图片那个角儿扭过去,还是列表里头的数字排排队往边上挪?这俩可是天壤之别,代码写法完全不一样。别笑,我猜你肯定也迷糊过。今天咱就掰扯掰扯,python里头的“旋转”到底是怎么回事,给你讲清楚,以后再遇到,心里就有底了。
先说那个最直观的,眼睛看得见的——图片旋转。这玩意儿太常见了,什么照片拍歪了想扶正啊,做个啥海报需要素材扭个角度啊,都离不开它。在python里头处理图片,Pillow库(以前叫PIL,现在一般说Pillow)是绕不过去的坎儿。它简直是图片处理界的瑞士军刀,旋转当然也不在话下。
用Pillow让图片转起来,核心就是Image
对象的一个方法:.rotate()
。你拿到一张图片对象,直接 .rotate(角度)
,砰!它就转了。是不是听着挺傻瓜的?但这里面有个小门道,那个“角度”啊,它是逆时针方向的。比如,你想让图片顺时针转90度,那给的角度就得是-90或者270。我老是记不住这个正负,每次都得试一下或者翻翻文档,真是脑壳疼。
更让人头疼的是,图片转了,画布怎么办?你想啊,一张长方形的图,你让它转个45度,原来直直的边就斜过来了,原来的方形画布可兜不住它所有边角了。Pillow默认情况下,转完还是原来那么大的画布,边角超出去的地方就没了,里头空出来的地方就给你补个颜色(通常是黑的)。这不是我想要的啊!我想要它转了之后,画布能自动变大,把转过来的整个图都装进去,一个角都不能少!
这时候就得请出.rotate()
的另一个参数了:expand=True
。加上这个,Pillow就聪明了,它会自己算,转完需要多大的地方,就把画布扩展到多大。只不过嘛,转完的画布可能就不是你熟悉的长宽比例了。但至少图是全乎的。你看,一个简单的旋转,背后也有这点小麻烦。对于那些特殊的角度,比如90度、180度、270度,那倒是省心,因为转完还是个方方正正的形状(如果原图是正方形),或者长宽互换(如果是长方形),画布问题相对不那么突出。
除了Pillow,处理图片旋转,有时候会用到OpenCV库,特别是在搞计算机视觉那些高级玩意儿的时候。OpenCV的旋转通常涉及仿射变换,听着更专业更复杂一点。你可以指定旋转中心、角度、甚至缩放。对于简单的90度、180度旋转,它也有更直接的函数可以用。不过如果只是日常的图片调整,Pillow已经绰绰有余了,而且用起来更“Pythonic”,感觉跟图片打交道更顺手。
好了,图片说完了,是不是觉得好像也就那么回事儿?别急,python怎么旋转这问题还有另一面,更抽象,更烧脑筋的——数组旋转,或者更常见的说法是列表旋转(毕竟python里列表用得更多)。
这个“旋转”跟图片可完全不一样。它是说,把列表里头那些元素啊,整体往左或者往右挪几位。比如说你有个列表 [1, 2, 3, 4, 5]
,你想让它“左旋”两位,意思就是把前面俩数1和2挪到队尾去,变成 [3, 4, 5, 1, 2]
。要是“右旋”两位,就是把后面俩数4和5挪到队头去,变成 [4, 5, 1, 2, 3]
。听起来像玩儿扑克牌洗牌,只不过是有规律地洗。
搞列表旋转,python里有种方法简直不要太优雅,那就是用切片。你别看切片就是个中括号加冒号,它在处理序列数据的时候,简直强大到没朋友。
比如你想把列表lst
左旋k
位。你只需要把列表从第k
个位置切开,分成两截:lst[k:]
(从第k个到最后)和lst[:k]
(从开头到第k个)。然后把这两截的顺序颠倒一下,lst[k:]
放前面,lst[:k]
放后面,用加号把它们拼接起来:lst[k:] + lst[:k]
。搞定!一行代码的事儿。
右旋也一样道理。右旋k
位,其实就相当于左旋len(lst) - k
位。所以你也可以转化一下用左旋的方法。或者直接切片:把列表最后k
个元素lst[-k:]
切出来,把前面len(lst)-k
个元素lst[:-k]
切出来,然后把最后那k
个放前面,前面那些放后面:lst[-k:] + lst[:-k]
。你看,切片是不是很神奇?这种方法既简单又直观,而且通常够快。
但是,注意这个“但是”!有时候面试官或者特定场景会要求你做原地旋转。啥叫原地?意思就是你不能新建一个列表,然后把旋转后的结果放进去。你得在原来的那个列表上直接操作,挪过来挪过去,最后让它变成旋转后的样子,还不占用额外的内存空间(或者只用非常少的额外空间)。
这原地旋转就比用切片难搞多了。用切片多方便啊,直接切吧切吧拼起来就完事儿。原地?这就像让你在拥挤的小屋子里挪动家具,还得保证家具不碰坏,最后达到预定布局,而且不许把家具搬到屋外暂存。
原地旋转列表,方法有好几种,但其中一个比较巧妙、也常被提及的是“三次翻转法”。听着有点像武林秘籍的名字。它是这样操作的:
假设你要把列表lst
左旋k
位。
第一步:把整个列表全部翻转过来。比如[1, 2, 3, 4, 5]
变成[5, 4, 3, 2, 1]
。
第二步:把列表的前k
个元素翻转。比如左旋2位,k=2。翻转前2个元素,[5, 4, 3, 2, 1]
的前2个是[5, 4]
,翻转后是[4, 5]
。列表现在是[4, 5, 3, 2, 1]
。
第三步:把列表从第k
个元素到最后一个元素翻转。刚才列表是[4, 5, 3, 2, 1]
。从第2个元素(索引是2,值是3)到最后是[3, 2, 1]
。翻转后是[1, 2, 3]
。把这部分插回去,整个列表就变成了[4, 5, 1, 2, 3]
。
咦?等等!我一开始举例左旋2位是[3, 4, 5, 1, 2]
啊!三次翻转法得到的是[4, 5, 1, 2, 3]
?啊,不对不对,我犯迷糊了!这个三次翻转法,实际上是用来实现右旋k
位的!右旋k
位是把最后k
个搬到前面。
再来一遍,三次翻转法用于右旋k
位:
列表[1, 2, 3, 4, 5]
,右旋2位,目标是[4, 5, 1, 2, 3]
。
1. 翻转整个列表:[5, 4, 3, 2, 1]
。
2. 翻转前k
个元素(右旋k
位,是把最后k
个搬到前面,那么在翻转后的列表里,这k
个原本在最后但在翻转后跑到最前面的元素,正好是前k
个):翻转前2个元素:[5, 4]
变成[4, 5]
。列表变成[4, 5, 3, 2, 1]
。
3. 翻转后len(lst)-k
个元素(也就是从第k
个到最后):[3, 2, 1]
变成[1, 2, 3]
。列表变成[4, 5, 1, 2, 3]
。
Bingo! 这下对了。右旋2位[4, 5, 1, 2, 3]
搞定。
那左旋k
位呢?可以用右旋的思路转化一下,左旋k
位等于右旋len(lst) - k
位。或者,直接用三次翻转法实现左旋k
位:
1. 翻转整个列表。
2. 翻转列表的前len(lst)-k
个元素。
3. 翻转列表的后k
个元素(也就是从索引len(lst)-k
到最后)。
你看,这里面绕来绕去是不是很容易把自己绕晕?所以理解这个原地旋转的三次翻转法,关键在于想明白每次翻转的范围。python列表有方便的.reverse()
方法可以用来翻转整个列表或其切片(虽然对切片调用.reverse()
不是原地操作,但我们可以用切片+赋值或者循环+双指针的方式模拟原地翻转指定范围)。
另一种原地旋转的思路是用双指针或者说循环交换。设定两个指针,一个从头走,一个从尾走,不断交换元素,同时处理边界和旋转次数。这写起来代码可能有点复杂,需要小心翼翼地处理索引和循环条件,一不小心就可能死循环或者越界。不过它的核心思想就是一点一点把元素挪到正确的位置,像玩儿那种数字滑块游戏。
说到底,python怎么旋转这个事儿,得分具体情境。是要转图片,还是要转数组?图片用Pillow,简单直观;数组用切片,简洁高效。如果非得原地转数组,那就得搬出三次翻转法或者更底层的交换逻辑了。每种方法都有它的用武之地,没有哪个是万能的。理解它们各自的原理和适用场景,比死记硬背代码重要得多。别怕第一次搞不明白把自己绕进去,多练几次,多犯几次迷糊,总会理顺的。就像我,现在跟你侃侃而谈,也是当年一步一个坑趟过来的。遇到问题,停下来想想,它到底要“旋转”个啥?往哪个方向?需不需要原地?问题拆解开了,答案自然就浮现了。这就是用python解决问题,包括python怎么旋转,乐趣所在不是吗?
评论(0)