编程之美学习笔记--一摞烙饼的排序

问题:假设有n块大小不一的烙饼,翻烙饼时只能从最上面的烙饼开始,一次抓住最上面的几块饼,把它们上下颠倒个儿,那么最少要翻多少次,才能够达到最后的大小有序?

思路

先上一张图,可以很好的说明思路:
这里写图片描述
假设有四张无序的饼,那么问题就变成了找到使层数最小的结点。书中给出的思路是:
将烙饼从第二张开始一个一个的尝试去翻,采用深度优先搜索的策略。在搜索开始之前,先找到了一种完成任务的方式,最少需要2*(n_cake-1)步。这就是一个搜索的上界,如果搜索步骤超过了这个上界,则这一枝可以抛弃不用搜索了,如果搜索到了一种翻转方式,则将这种翻转方式的翻转次数更新为新的搜索上界值,以减小搜索范围;同时给出了计算搜索下界的计算方式:当前状态翻转到有序状态的步骤数目下界:若当前状态中,还有m对烙饼没有相邻,而每次翻转最多只能使得一个烙饼与大小跟它相邻的烙饼拍到一起,故至少还要m次才能排好,如果当前搜索步数加上这个下界的值超过了搜索上界值,则在该枝的搜索就不必进行了。
搜索时,循环进行每一个子树的搜索,循环中使用递归方式搜索;程序如下:

#include <stdio.h>
#include <stdlib.h>

class CakeSort
{
private:
    int* m_CakeArray;       //初始烙饼数组
    int m_CakeCount;        //烙饼数量
    int m_MaxSwap;          //交换上界
    int m_SwapTimes;        //已交换的次数
    int* m_SwapArray;       //交换信息
    int* m_ReverseCake;     //执行交换后的烙饼数组
    int* m_SwapReverseCake; //执行交换后的烙饼数组的交换信息
    int* m_SortArray;        //排序完成的烙饼数组结果

public:
    void Init(int* pCakeArray,int count);
    void Run(int* pCakeArray,int count);
    int UpperBound(int count);
    int LowerBound(int* pArray,int count);
    void Search(int step);
    bool IsSort(int* pArray,int count);
    void Reverse(int begin,int end);
    ~CakeSort(void);
};
void CakeSort::Init(int* pCakeArray,int count)
{
    m_CakeCount = count;
    m_CakeArray = new int[count];
    for(int i = 0 ; i < count ; i++)
        m_CakeArray[i] = pCakeArray[i];
    m_ReverseCake = new int[count];
    for(int i = 0 ; i < count ; i++)
        m_ReverseCake[i] = m_CakeArray[i];
    m_MaxSwap = UpperBound(count);
    m_SwapTimes = 0;
    m_SwapArray = new int[m_MaxSwap];
    m_SwapReverseCake = new int[m_MaxSwap];
    m_SortArray=new int [m_CakeCount];
}
int CakeSort::UpperBound(int count)
{
    return 2*(count - 1);
}

//当前状态翻转到有序状态的步骤数目下界:若当前状态中,还有m对烙饼没有相邻,而每次翻转最多只能
//使得一个烙饼与大小跟它相邻的烙饼拍到一起,故至少还要m次才能排好
int CakeSort::LowerBound(int* pArray,int count)
{
    int ret = 0 ;
    int t;
    for(int i = 1 ; i < count ; i++)
    {
        t = pArray[i] - pArray[i-1];
        if((t != 1) && ( t!= -1))
            ret++;
    }
    return ret;
}
void CakeSort::Run(int* pArray,int count)
{
    Init(pArray,count);

    Search(0);

    //打印最优排序方式
    printf("最优翻转方式:\n");
    for (int i = 0; i < m_MaxSwap; i++)  
        printf("%d\n", m_SwapArray[i]);  

    printf("Search Times : %d\n", m_SwapTimes);  
    printf("Total Swap times = %d\n", m_MaxSwap);  

    //打印排序结果
    for (int i=0;i<m_CakeCount;i++)
    {
        printf("cake:%d\n",m_SortArray[i]);
    }


}
void CakeSort::Reverse(int begin , int end)
{
    int i,j,temp;
    for(i = begin , j = end ; i < j ; i++ , j--)
    {
        temp = m_ReverseCake[i];
        m_ReverseCake[i] = m_ReverseCake[j];
        m_ReverseCake[j] = temp;
    }
}

//排列好后,返回真值
bool CakeSort::IsSort(int* pArray,int count)
{
    for(int i = 1 ; i < count ; ++i)
    {
        if(pArray[i] < pArray[i-1])
            return false;
    }
    return true;
}
void CakeSort::Search(int step)
{
    m_SwapTimes++;
    int Est = LowerBound(m_ReverseCake,m_CakeCount);
    if(Est + step >= m_MaxSwap)
        return;
    if(IsSort(m_ReverseCake,m_CakeCount))
    {
        if(step <= m_MaxSwap)
        {
            m_MaxSwap = step;
            //打印翻转方式
            for(int i = 0 ; i < m_MaxSwap ; i ++)
            {
                m_SwapArray[i] = m_SwapReverseCake[i];
                printf("%d\n",m_SwapArray[i]);
            }
            //打印该翻转方式下的排序结果
            for (int i=0;i<m_CakeCount;i++)
            {
                //想要保存正确的翻转结果,需要另建数组保存,不然m_ReverseCake还会在其他函数中修改,最后m_ReverseCake中不会保留正确的翻转结果
                m_SortArray[i]=m_ReverseCake[i];
                printf("cake:%d\n",m_ReverseCake[i]);
            }

        }
        return;
    }
    //没有排列好的情况
    for(int i = 1 ; i < m_CakeCount ; i++)
    {
        Reverse(0,i);
        //记录本次操作的下标
        m_SwapReverseCake[step] = i;
        Search(step+1);
        //若第step+1步的翻转失败,回复到第step步的翻转结果
        Reverse(0,i);
    }
}
int main()
{
    int a[] = {3, 2, 1, 6, 5, 4, 9, 8, 7, 0};  
    CakeSort s;
    s.Run(a,10);
    system("pause");
    return 0;  
}

CakeSort::~CakeSort(void)
{
    if(m_CakeArray!=NULL)delete []m_CakeArray;
    if(m_SwapReverseCake!=NULL)delete []m_SwapReverseCake;
    if(m_SwapArray!=NULL)delete []m_SwapArray;
    if(m_ReverseCake!=NULL)delete []m_ReverseCake;
    if(m_SortArray!=NULL)delete []m_SortArray;
}

从上述程序的输出结果中可以看出,其实程序还是搜索了很多不必要的路径,如刚刚将头两张烙饼翻转,下一步马上又把头两张饼翻转过来,显然是重复了,就没有再向下搜索的必要了,因此可以继续优化,进一步减小搜索范围
这里我们采用的想法是:上一步翻转过的前i张饼,在下一次迭代时,直接跳过翻转前i张饼的情况(因为这样又会恢复到上一步的状态),因此我们将:Search递归函数修改为:

void CakeSort::Search(int step,int flapIndex)
{
    m_SwapTimes++;
    int Est = LowerBound(m_ReverseCake,m_CakeCount);
    if(Est + step >= m_MaxSwap)
        return;
    if(IsSort(m_ReverseCake,m_CakeCount))
    {
        if(step <= m_MaxSwap)
        {
            m_MaxSwap = step;
            //打印翻转方式
            for(int i = 0 ; i < m_MaxSwap ; i ++)
            {
                m_SwapArray[i] = m_SwapReverseCake[i];
                printf("%d\n",m_SwapArray[i]);
            }
            //打印该翻转方式下的排序结果
            for (int i=0;i<m_CakeCount;i++)
            {
                //想要保存正确的翻转结果,需要另建数组保存,不然m_ReverseCake还会在其他函数中修改,最后m_ReverseCake中不会保留正确的翻转结果
                m_SortArray[i]=m_ReverseCake[i];
                printf("cake:%d\n",m_ReverseCake[i]);
            }

        }
        return;
    }
    //没有排列好的情况
    for(int i = 1 ; i < m_CakeCount ; i++)
    {
        if(flapIndex==i)continue;
        Reverse(0,i);
        //记录本次操作的下标
        m_SwapReverseCake[step] = i;
        Search(step+1,i);
        //若第step+1步的翻转失败,回复到第step步的翻转结果
        Reverse(0,i);
    }
}

相应的,run函数修改为:

void CakeSort::Run(int* pArray,int count)
{
    Init(pArray,count);

    Search(0,0);

    //打印最优排序方式
    printf("最优翻转方式:\n");
    for (int i = 0; i < m_MaxSwap; i++)  
        printf("%d\n", m_SwapArray[i]);  

    printf("Search Times : %d\n", m_SwapTimes);  
    printf("Total Swap times = %d\n", m_MaxSwap);  

    //打印排序结果
    for (int i=0;i<m_CakeCount;i++)
    {
        printf("cake:%d\n",m_SortArray[i]);
    }


}

虽然可以避免一部分无用搜索,但是还是会存在无用的搜索。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值