煎饼排序

煎饼排序是一种特殊的排序方法,通过翻转煎饼来实现。文章介绍了煎饼排序的基本思想,包括一般方法需要翻转2*n次,以及比尔·盖茨提出的最多翻(5n+5)/3次的优化方案。作者分享了两种自己的实现方法,一种是最常见的选择排序类似的操作,另一种是维持前序有序法,虽然未达到最优,但提供了理解问题的不同视角。

在这里插入图片描述
煎饼排序指的是将大小不同的一摞煎饼(一个煎饼放在另一个煎饼上堆起来),按大小排序的数学问题,其中的操作是,只能用煎饼铲子从任意位置插入,铲起上方全部煎饼并翻面。学术化一点,就是只能把插入位置之前的数组翻转的排序方法–“前序翻转排序”。

这个问题我也是偶然看到知乎上讨论比尔盖茨的话题中了解到的。说他找到了更好的方法。一般来说煎饼排序最多要翻 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]

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值