1.3 一摞烙饼的排序

1.3 一摞烙饼的排序

参考《编程之美–1.3 一摞烙饼的排序》
问题描述:
一摞乱序摆放的烙饼,每次只能抓取最上面几块烙饼并翻转,多次翻转后能够实现烙饼的从小到大(从上往下)的有序摆放。
问题分析:
这里我们使用回溯法解决这个问题。直接用回溯法效率是低下的,因此要进行剪枝。这里的剪枝条件是利用翻转次数的上界和下界完成的。
上界:
[4,2,1,5,3] -> [5,1,2,4,3] -> [3,4,2,1,5]
两步可以按大小顺序将某块饼放到它应该在的位置。
同理,可以把其他四块烙饼摆放好,要注意最后一块烙饼会在最后第二块烙饼摆放正确后位于正确位置。假设烙饼个数为n,则翻转次数上界为2(n-1)。
下界:
上界我们已经得出了,下面考虑下界。在试验翻转的时候,我们可以发现,当烙饼堆部分有序时,翻转次数较少。若一摞烙饼中有m对相邻的烙饼半径不相邻,理想情况下需要m次翻转来排序,[3,4,2,1,5,6],此时就需要两次来翻转。

代码如下:

#include <iostream>
#include <windows.h>
#include <math.h>
#include <assert.h>

using namespace std;

class PreFixSorting
{
public:
    PreFixSorting()
    {
        CakeCount = 0;
        MaxSwap = 0;
    }

    ~PreFixSorting()
    {
        if(CakeArray != NULL)
        {
            delete CakeArray;
        }
        if(SwapArray != NULL)
        {
            delete SwapArray;
        }
        if(ReverseCakeArray != NULL)
        {
            delete ReverseCakeArray;
        }
        if(ReverseCakeArraySwap != NULL)
        {
            delete ReverseCakeArraySwap;
        }
    }

    //计算烙饼翻转信息
    //@param
    //pCakeArray    存储烙饼索引数组,索引大的烙饼个头大
    //pCakeCount    烙饼个数

    void Run(int* pCakeArray, int pCakeCount)
    {
        Init(pCakeArray, pCakeCount);
        nSearch = 0;
        Search(0);
    }

    void Output()
    {
        for(int i = 0; i < MaxSwap; i++)
        {
            cout<<SwapArray[i]<<" ";
        }
        cout<<'\n'<<"|Search Times|: "<<nSearch<<endl;
        cout<<"Total Swap Times = "<<MaxSwap<<endl;

    }


private:
    //初始化数组信息
    //@param
    //pCakeArray    存储烙饼索引数组,索引大的烙饼个头大
    //pCakeCount    烙饼个数

    void Init(int *pCakeArray, int pCakeCount)
    {
        assert(pCakeArray != NULL);
        assert(pCakeCount > 0);

        CakeCount = pCakeCount;

        //初始化烙饼数组
        CakeArray = new int[CakeCount];
        assert(CakeArray != NULL);
        for(int i = 0; i < CakeCount; i++)
        {
            CakeArray[i] = pCakeArray[i];
        }

        //获取最多交换次数
        MaxSwap = UpperBound(CakeCount);
        //初始化交换结果数组
        SwapArray = new int[MaxSwap + 1];
        assert(SwapArray != NULL);
        //初始化中间交换结果数组
        ReverseCakeArray = new int[CakeCount];
        for(int i = 0; i < CakeCount; i++)
        {
            ReverseCakeArray[i] = CakeArray[i];
        }
        ReverseCakeArraySwap = new int[MaxSwap];


    }

    //寻找当前翻转上界

    int UpperBound(int pCakeCount)
    {
        return (pCakeCount-1)*2;
    }


    //寻找当前翻转下界
    int LowerBound(int* pCakeArray, int pCakeCount)
    {
        int t, ret = 0;
        //根据当前数组排序情况判断最少需要交换的次数
        for(int i = 1; i < pCakeCount; i++)
        {
            //判断位置相邻的两个烙饼是否索引相邻
            t = pCakeArray[i] - pCakeArray[i-1];
            if((t == 1) || (t == -1))
            {

            }
            else
            {
                ret++ ;
            }
        }
        return ret;
    }


    //排序主函数
    void Search(int step)
    {
        int i, nEstimate;
        nSearch++;

        //估算交换次数下界
        nEstimate = LowerBound(ReverseCakeArray, CakeCount);
        if(step + nEstimate > MaxSwap )
        {
            return;
        }

        //若已排好序则输出结果
        if(IsSorted(ReverseCakeArray, CakeCount))
        {
            if(step < MaxSwap)
            {
                MaxSwap = step;
                for(int i = 0; i < MaxSwap; i++)
                {
                    SwapArray[i] = ReverseCakeArraySwap[i];
                }
                return;
            }
        }


        //回溯法递归翻转
        for(i = 1; i < CakeCount; i++)
        {
            Reverse(0, i);
            ReverseCakeArraySwap[step] = i;
            Search(step + 1);
            Reverse(0, i);
        }


    }


    //true:     已排序
    //false:    未排序
    bool IsSorted(int* pCakeArray, int pCakeCount)
    {
        for(int i = 1; i < pCakeCount; i++)
        {
            if(pCakeArray[i-1] > pCakeArray[i])
            {
                return false;
            }
        }
        return true;
    }


    //翻转烙饼数组
    void Reverse(int nBegin, int nEnd)
    {
        assert(nEnd > nBegin);
        int i, j, t;

        for(i = nBegin, j = nEnd; i < j; i++, j--)
        {
            t = ReverseCakeArray[i];
            ReverseCakeArray[i] = ReverseCakeArray[j];
            ReverseCakeArray[j] = t;
        }

    }


private:
    int* CakeArray;                 //烙饼数组
    int CakeCount;                  //烙饼个数
    int MaxSwap;                    //交换次数上界
    int* SwapArray;                 //翻转烙饼结果数组(翻转前几层烙饼,用于输出)
    int* ReverseCakeArray;          //当前转烙饼数组(中间状态)
    int* ReverseCakeArraySwap;      //当前翻转烙饼结果数组
    int nSearch;                    //当前搜索次数
};



int main()
{
    PreFixSorting pfs;

    int CakeArray[] = {4,2,1,5,3};
    int CakeCount = 5;
    pfs.Run(CakeArray,CakeCount);
    pfs.Output();



    return 0;

}

注意: 关于这里的上下界,其实目前研究的结果是上界最小为(5n+5)/3向上取整,下界最大为15n/14向上取整。用这个上下界的搜索次数更少,效率更高(n较大时)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值