1. 程式人生 > >《程式設計之美》1.3一摞烙餅的排序

《程式設計之美》1.3一摞烙餅的排序

#include<iostream>
#include<assert.h>
using namespace std;

/****************/
//
//烙餅排序的實現
//
/****************/
class CPrefixSorting {
private:
	int* m_CakeArray;     //烙餅資訊陣列
	int m_nCakeCnt;       //烙餅個數
	int m_nMaxSwap;       //最多交換次數。根據前面的推斷,這裡最多為(m_nCakeCnt-2)*2+1
	int* m_SwapArray;     //交換結果陣列
	int* m_ReverseCakeArray;      //當前翻轉烙餅資訊陣列
	int* m_ReverseCakeArraySwap;  //當前翻轉烙餅交換結果陣列
	int m_nSearch;                //當前搜尋次數資訊

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  儲存烙餅索引陣列
	//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++) {
			cout << m_SwapArray[i] << " ";
		}
		cout << endl << " |Search Times| : " << m_nSearch << endl;
		cout << "Total Swap Times = " << m_nMaxSwap << endl;
	}

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 = UpBound(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 UpBound(int nCakeCnt) {
		return (nCakeCnt - 2) * 2 + 1;//原先return (nCakeCnt-1)*2也可以,
									  //程式碼這麼寫也沒問題,只不過不是最優解而已
	}
	
	//
	//尋找當前翻轉的下界
	//
	int LowerBound(int* pCakeArray, int nCakeCnt) {
		int t, ret = 0;

		//根據當前陣列排序資訊情況判斷至少需要交換多少次
		for (int i = 1; i < nCakeCnt; i++) {
			//判斷位置相鄰的兩個烙餅,是否為尺寸排序上相鄰的
			//此處應該考慮順序問題,若烙餅的次序從上往下數是從大到小的,即t==-1
			//翻轉次數應該是1而非0,即要整個翻轉一次。
			t = pCakeArray[i] - pCakeArray[i - 1];
			if ((t == 1) || (t == -1)) {
			}
			else {
				ret++;
			}
		}
		//判斷下界時,如果最大的烙餅不在最後一個位置,則要多翻轉一次(包含了t==-1的情況)
		//能有效減少無效搜尋次數,雖然還是會包含無效搜尋。。
		if (pCakeArray[nCakeCnt - 1] != nCakeCnt - 1)
			ret++;
		return ret;
	}

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

		//估算這次搜尋所需要的最小交換次數nEstimate
		nEstimate = LowerBound(m_ReverseCakeArray, m_nCakeCnt);
		//根節點(最原始陣列)呼叫search時step是0,第一層子節點step是1,
		//第二層子節點step是2,以此類推可知step是從0開始計數的。因為nElimate可能為0,
		//所以當step等於m_nMaxSwap時候,會造成下面的m_reverseCakeArraySwap[step]=i;
		//的陣列越界。所以判斷條件應改為>=
		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 (i = 1; i < m_nCakeCnt; i++) {
			Revert(0, i);
			m_ReverseCakeArraySwap[step] = i;
			Search(step + 1);
			Revert(0, i);
		}
	}

	//
	//true:已經排好序
	//false:未排序
	//
	bool IsSorted(int* pCakeArray, int nCakeCnt) { //若陣列內容從小到大有序,返回true
		for (int i = 1; i < 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;
		}
	}
};

//主函式,供測試
int main() {
	int Cake[10] = {3,2,1,6,5,4,9,8,7,0};
	CPrefixSorting TestA;
	TestA.Run(Cake, 10);
	TestA.Output();
	return 0;
}
      執行結果如下圖所示: