【编程之美】游戏之乐——一摞烙饼的排序

一摞烙饼的排序


问题描述

假设有n个大小不一的烙饼,那最少要翻几次,才能达到大小有序的结果呢?写一个程序,对于n块大小不一的烙饼,输出最有的翻饼过程。

问题分析与解法

如果我们最底层的饼已经排序完毕,那么我们只处理上面的n-1个饼就行,同样的之后我们再把n-1个饼的排序转化为n-2,n-3直到最后的两个饼排好顺序。

也就是说?第一轮:我们先找到这n个饼中的最大的饼,然后从该处进行翻饼,把这个最大的饼翻到最上面,然后再把所有的饼进行一次翻滚,这样    最大的饼就翻到了最下面。

第二轮:我们上一轮已经把最大的饼翻到最下面了,接着我们用同样的方法对待上面的n-1个饼,我们在这n-1个饼中找到最大的饼,然后    用同样的方法把其翻滚到最底层的上面那一层(倒数第二层)。

   。。。。。。

 一直到最后一轮只剩下两个。

上面的那个方案注意,每一轮都翻滚了两次,一共需要n-1轮(最后一轮剩下两个,最后小的不需要再进行翻滚),那么这个方案最多需要2(n-1)次翻滚。

如果在算法中,需要翻转的次数多于2(n-1)次,就应该放弃该算法,直接退出。

另外,既然是一个排序问题,也应该利用排序的信息来处理,同样,在翻转的过程中,可以看看当前的烙饼数组排序情况如何,然后利用这些信息来减少翻转次数。

代码如下:

// CPrefixSorting.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//烙饼排序实现

#include 
#include 
using namespace std;

class CPrefixSorting
{
public:
	CPrefixSorting() {
		m_nCakeCnt = 0;
		m_nMaxSwap = 0;
	}
	~CPrefixSorting() {
		if (m_CakeArray != NULL)
		{
			delete m_CakeArray;
		}
		if (m_SwapArray != NULL)
		{
			delete m_SwapArray;
		}
		if (m_ReverseCakeArray != NULL)
		{
			delete m_ReverseCakeArray;
		}
		if (m_ReverseCakeArraySwap != NULL)
		{
			delete m_ReverseCakeArraySwap;
		}
	}
	/*
	计算烙饼翻转信息
	@param
	pCakeArray 存储烙饼索引数组
	pCakeCnt 烙饼数量
	*/
	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_SwapArray[i]);
		}
		printf("\n|Search Times|: %d\n", m_nSearch);
		printf("Total Swap times = %d\n", m_nMaxSwap);
	}
private:
	//初始化数组信息
	//@param
	//pCakeArray 存储烙饼索引数组
	//nCakeCnt 烙饼个数
	void Init(int* pCakeArray, int nCakeCnt) {
		assert(pCakeArray != NULL);
		assert(nCakeCnt > 0);
		m_nCakeCnt = nCakeCnt;

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

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

		//初始化交换结果数组
		m_SwapArray = new int[m_nMaxSwap + 1];
		assert(m_SwapArray != NULL);
		//初始化中间交换结果信息
		m_ReverseCakeArray = new int[m_nCakeCnt];
		for (int i = 0; i < m_nCakeCnt; i++)
		{
			m_ReverseCakeArray[i] = m_CakeArray[i];
		}
		m_ReverseCakeArraySwap = new int[m_nMaxSwap];
	}
	/*
	寻找当前翻转的上届
	*/
	int UpperBound(int nCakeCnt) {
		return nCakeCnt * 2;
	}
	/*
	寻找当前翻转的下届
	*/
	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_SwapArray[i] = m_ReverseCakeArraySwap[i];
				}
			}
			return;
		}
		//递归进行翻转
		for (int i = 1; i < m_nCakeCnt; i++)
		{
			Reverse(0, i);
			m_ReverseCakeArraySwap[step] = i;
			Search(step + 1);
			Reverse(0, i);
		}
	}
	/*
	true:已经排好序
	false:未排序
	*/
	bool IsSorted(int* pCakeArray, int nCakeCnt) {
		for (int i = 1; i < nCakeCnt; 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 = m_ReverseCakeArray[i];
			m_ReverseCakeArray[i] = m_ReverseCakeArray[j];
			m_ReverseCakeArray[j] = t;
		}
	}
private:
	int* m_CakeArray;	//烙饼信息数组
	int m_nCakeCnt;		//烙饼个数
	int m_nMaxSwap;		//最多交换次数。最多为m_nCakeCnt*2
	int* m_SwapArray;	//交换结果数组
	int* m_ReverseCakeArray;	//当前翻转烙饼信息数组
	int* m_ReverseCakeArraySwap; //当前翻转烙饼交换结果数组
	int m_nSearch;		//当前搜索次数信息
};
int main()
{
	CPrefixSorting pfs;

	int m_CakeArray[] = {3, 2, 1, 6, 5, 4, 9, 8, 7, 0};
	int m_nCakeCount = 10;
	pfs.Run(m_CakeArray, m_nCakeCount);
	pfs.Output();
	return 0;
}

每一次翻转我们最多使得一个烙饼与大小跟它相邻的烙饼排到一起。如果当前n个烙饼中,有m对相邻的烙饼半径不相邻,那么我们至少需要。m次才能排好序。

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

你可能感兴趣的:(数据结构与算法)