
煎饼排序指的是将大小不同的一摞煎饼(一个煎饼放在另一个煎饼上堆起来),按大小排序的数学问题,其中的操作是,只能用煎饼铲子从任意位置插入,铲起上方全部煎饼并翻面。学术化一点,就是只能把插入位置之前的数组翻转的排序方法–“前序翻转排序”。
这个问题我也是偶然看到知乎上讨论比尔盖茨的话题中了解到的。说他找到了更好的方法。一般来说煎饼排序最多要翻 2*n 次,但是他找到了最多翻(5n+5)/3 次的方法。我自己思考了一下这个问题,最后也找到两个种方法。
第一种:(也是普遍的做法,大家都能想得到)
因为只能翻转上面的,有序的放下面的就不会被干扰。我们可以一个个的找最大值,翻到前面,再翻到区间最下面,所以一次操作要翻2次。要操作n次,所以就是2*n次翻面。有点像选择排序。。。
每一操作的步骤:
第一次,我们从区间中找到最大值,翻到前面。
第二次,把这个最大值翻到下面。
比如 [1, 3, 4, 2, 0],
前5个数字区间,最大值 4,先把他翻到上面 [4, 3, 1, 2, 0]
然后翻转前5个数字区间 [0, 2, 1, 3, 4], 这样4就被放到底部啦。
接着,前4个数字区间,最大值 3,因为已经在区间最底部了,就不用操作啦
接着,前3个数字区间,最大值 2, 翻出来,[2, 0, 1, 3, 4],
再翻转区间,[1, 0, 2, 3, 4]
最后,前2个数字区间,最大值1,已经在头部了,不用翻,
再翻转区间, [0, 1, 2, 3, 4]
完成
python代码:
import random
def pancake_sort(arr):
l = len(arr)
for i in range(len(arr)):
maxidx = arr.index(max(arr[:l - i]))
if maxidx != l - i: # 如果已经在正确位置,就不用操作了
if maxidx != 0: # 如果不在头部,就要翻到头部
arr = arr[:maxidx + 1][::-1] + arr[maxidx + 1:]
arr = arr[:l - i][::-1] + arr[l - i:]
return arr
arr = list(range(10)) + list(range(5))
random.shuffle(arr)
print('before: ', arr)
arr = pancake_sort(arr)
print('after: ', arr)
# before: [5, 4, 6, 0, 2, 3, 8, 1, 7, 2, 9, 1, 4, 3, 0]
# after: [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9]
第二种
维持前序有序法。
从第三个数开始,看大小是否在前面区间之间,是得话就要插入。
否则,看是否变无序(原来递增的,然后遇到这个数就递减了,或者递减变递增),无序就要把前面的数组翻转一下,还是有序就不管了。这个数呢,比区间最小值小,或着比区间最大值大,所以最后要更新区间最小值,最大值。相等的就不管了,因为还是有序的。
插入操作需要翻3次,
第一次要把这个数翻到前面,
第二次再找合适的位置(断点),翻进序列中。
第三次还得把前面的翻回来,因为第二次的翻进操作,把原来有序的变成无序的了。
我看了下一些比尔盖茨的算法的评论,说也是基于这种找断点的想法的。。嗯。。和大佬不谋而合。。哈哈,但是我没有继续搞下去,要搞到那种(5n+5)/3次的算法太复杂了,我这里的算法我算了一下最糟糕要 (n-2) * 3 + 1 --> 3n-5 次。。。还是有点差距的。
python代码:
import random
def stop(a, b, order):
if order:
return b >= a
else:
return b <= a
def pancake_sort(arr):
length = len(arr)
minval, maxval = min(arr[:2]), max(arr[:2])
order = True if arr[0] <= arr[1] else False # 是否为正序
for i in range(2, length):
num = arr[i]
if minval < num < maxval:
arr = arr[:i + 1][::-1] + arr[i + 1:] # 翻出来
order = not order # 改变顺序
stopidx = 0 # 看正序还是逆序,找停下的位置
for j in range(1, i + 1):
if stop(num, arr[j], order):
stopidx = j
break
arr = arr[:stopidx][::-1] + arr[stopidx:] # 翻进去
arr = arr[:stopidx - 1][::-1] + arr[stopidx - 1:] # 翻回有序状态
elif order and num < maxval or not order and num > minval:
arr = arr[:i][::-1] + arr[i:] # 非有序状态就得翻回来
order = not order # 改变顺序
# 更新区间最大最小值
if num > maxval:
maxval = num
if num < minval:
minval = num
if not order: # 最后如果逆序的还要翻一次
arr = arr[::-1]
return arr
arr = list(range(5)) + list(range(2, 10))
random.shuffle(arr)
print('before', arr)
arr = pancake_sort(arr)
print('after', arr)
# before [3, 4, 0, 2, 4, 3, 1, 8, 7, 9, 6, 5, 2]
# after [0, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9]
煎饼排序是一种特殊的排序方法,通过翻转煎饼来实现。文章介绍了煎饼排序的基本思想,包括一般方法需要翻转2*n次,以及比尔·盖茨提出的最多翻(5n+5)/3次的优化方案。作者分享了两种自己的实现方法,一种是最常见的选择排序类似的操作,另一种是维持前序有序法,虽然未达到最优,但提供了理解问题的不同视角。
1862

被折叠的 条评论
为什么被折叠?



