排序算法总结分析(三)——吃货排序之烙饼排序

目录

排序算法总结分析(一)——开篇

排序算法总结分析(二)——常见八大排序算法

排序算法总结分析(三)——吃货排序之烙饼排序

今天先来个好玩点的,呃,确切说是好吃的点的问题。哈哈,就是如标题表明的烙饼排序。程序猿果然思维跟普通人就不一样,连吃个饼都想的这么多。问题描述是这样的:把一摞饼按照大小次序摆好,要求是小的在上面,大的在下面,只能通过翻转一摞饼进行排序,就像用铲子插入某个位置,把这个位置之上的所有饼进行翻转。那假设有N块大小不一的烙饼,最少要翻转几次才能达到最终有序排列呢?

排序算法总结分析(三)——吃货排序之烙饼排序_第1张图片

翻转演示图

与传统排序不同的是,不能一张张抽出来,然后插入进去;也不能任意交换两块饼。说明基本的排序算法都不太好用。是不是有点意思呢?想当年比尔·盖茨也研究过这个问题~

目前这个问题的答案只有一个范围,没有确切的值,完成排序需要的最少次数在(15/14)N与(18/11)N之间。2011年的时候这个问题被定义为NP-Hard。什么是NP-Hard?NP(Non-Deterministic)官方定义是非确定性多项式。而非确定性是指,可用一定数量的运算去解决多项式时间内可解决的问题。例如,著名的推销员旅行问题(Travel Saleman Problem or TSP):假设一个推销员需要从香港出发,经过广州,北京,上海,…,等 n 个城市,最后返回香港。任意两个城市之间都有飞机直达,但票价不等。假设公司只给报销C元钱,问是否存在一个行程安排,使得他能遍历所有城市,而且总的路费小于C?推销员旅行问题显然是NP的。因为如果你任意给出一个行程安排,可以很容易算出旅行总开销。但是,要想知道一条总路费小于C的行程是否存在,在最坏情况下,必须检查所有可能的旅行安排!这将是个天文数字。


好了介绍性的东西说的差不多了,下面主要讲一下怎么个排法~

由于每次操作都是针对最上面的饼,如果最底层的饼已经排好序,然后就只需要处理上面的N-1个饼了。


翻转图

首先,经过两次翻转,最大的饼已经在最下面了。接着次大的饼也需要两次翻转,最后剩两张饼的时候只需要1次就可以,所以总的至少翻转次数为2(n-1)-1即2n-3。

当然这是这个问题解的一种上限,非最优。

那么下限呢?这里也只说一种简单的优化,非最佳。每一次翻转最多使得一个烙饼与大小跟它相邻的烙饼排到一起。如果当前状态N个烙饼中,有M对相邻的烙饼它们不相邻,那么至少需要M次才能排好。

下面稍微介绍下优化方法及算法的实现。

假如这堆烙饼中有好几个不同的部分相对有序,就可以先把小一些的烙饼翻转使其有序。这样就会减少翻转次数。可以考虑每次翻转的时候,把两个本来应该相邻的烙饼尽可能换到一起。这样,当所有的烙饼都换到一起之后。实际上就完成了排序。这样的话就会想到使用动态规划或者递归的方法来实现。可以从不同的翻转策略开始,递归所有可能性。这样,肯定能找到最优解。

代码如下:

#include 
/************************************************************************/
/* 烙饼排序实现——By Sin_Geek 2014.04.12                               */
/************************************************************************/
class CPancakeSorting
{
public:
	CPancakeSorting()
	{
		m_nCakeCnt = 0;
		m_nMaxSwap = 0;
	}
	
	//计算烙饼翻转信息,pCakeArray 存储烙饼索引数组,nCakeCnt烙饼个数
	void Run(int* pCakeArray, int nCakeCnt)
	{
		Init(pCakeArray, nCakeCnt);
		m_nSearch = 0;
		Search(0);
	}

	//输出烙饼具体翻转的次数
	void Output()
	{
		for (int i = 0; i < m_nMaxSwap; i++)
		{
			printf("%d", m_arrSwap[i]);
		}

		printf("\nSearch Times : %d\n", m_nSearch);
		printf("Total Swap times = %d\n", m_nMaxSwap);
	}

private:
	//初始化数组信息,pCakeArray 存储烙饼索引数组,nCakeCnt烙饼个数
	void Init(int* pCakeArray, int nCakeCnt)
	{
		m_nCakeCnt = nCakeCnt;

        //初始化
		m_CakeArray = new int[m_nCakeCnt];

		for (int i = 0; i < m_nCakeCnt; i++)
		{
			m_CakeArray[i] = pCakeArray[i];
		}

		//设置最多交换次数信息
		m_nMaxSwap = UpBound(m_nCakeCnt);

		//初始化交换结果数组
		m_SwapArray = new int[m_nMaxSwap];

		//初始化中间交换结果信息
		m_ReverseCakeArray = new int[m_nCakeCnt];
		for (i = 0; i < m_nCakeCnt; i++)
		{
			m_ReverseCakeArray[i] = m_CakeArray[i];
		}
		m_ReverseCakeArraySwap = new int[m_nMaxSwap];
	}

	//寻找当前翻转的上界
	int UpBound(int nCakeCnt)
	{
		return nCakeCnt*2 - 3;
	}

	//寻找当前翻转的下界
	int LowerBound(int* pCakeArray, int nCakeCnt)
	{
		int t,ret = 0;

		//根据当前数组的排序信息情况判断最少需要交换多少次
		for (int i = 1; i < nCakeCnt; i++)
		{
			//判断位置相邻的两个烙饼是否为尺寸排序上相邻的
			t =  pCakeArray[i] - pCakeArray[i-1];
			if ((t == 1) || (t == -1))
			{
			}
			else
			{
				ret++;
			}
		}
		return ret;
	}

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

		//估算这次搜素所需的最小交换次数
		nEstimate = LowerBound(m_ReverseCakeArray, m_nCakeCnt);
		if (step + nEstimate > m_nMaxSwap) 
		return;

		//如果已经排好序,输出结果
		if (IsSorted(m_ReverseCakeArray, m_nCakeCnt)) 
		{
			if (step < m_nMaxSwap) 
			{
				m_nMaxSwap = step;
				for (i = 0; i < m_nMaxSwap; i++)
					m_arrSwap[i] = m_ReverseCakeArraySwap[i];
			}
			return;
		}

		//递归翻转
		for (i = 1; i < m_nCakeCnt; i++)
		{
			Revert(0,i);
			m_ReverseCakeArraySwap[step] = i;
			Search(step + 1);
			Revert(0,i);
		}

	}


	bool IsSorted(int* pCakeArray, int nCakeCnt)
	{
		for (int i = 1; i < m_nCakeCnt; i++)
		{
			if(pCakeArray[i-1] > pCakeArray[i])
				return false;
		}
		return true;
	}

	//翻转烙饼
	void Revert(int nBegin, int nEnd)
	{
		//ASSERT(nEnd > nBegin);
		int i,j,t;

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

private:
	int m_nCakeCnt;       //烙饼个数
	int m_nMaxSwap;       //最多交换次数
	int m_nSearch;        //当前搜索次数
	int* m_CakeArray;     //烙饼信息数组
	int* m_SwapArray;     //交换结果数组
	int* m_ReverseCakeArray;//当前翻转烙饼信息数组
	int* m_ReverseCakeArraySwap;//当前翻转烙饼交换结果数组
	int m_arrSwap[10];

};
void main()
{
	int nCakeCnt = 10;
	int arrSwap[10] = {3,2,1,6,5,4,9,8,7,0} ;
	CPancakeSorting pan;
	pan.Run(arrSwap, nCakeCnt);
	pan.Output();
}

当然还可以把这个问题更复杂化一点,假定每个烙饼都有一面是烤过的,在原来排序的结果上附加一个条件,就是让所有烤过的那一面都朝下~有兴趣的可以思考一下哦~~


你可能感兴趣的:(排序算法总结分析(三)——吃货排序之烙饼排序)