中国象棋将帅问题
题目要求
假设棋盘上只有“将”和“帅”二子(为了方便描述,我们约定A表示“将”,B表示“帅”)。A,B二子被限制在己方3×3的格子里运动。每一步,A,B分别可以横向或纵向移动一格,但不能沿对角线移动。另外,A不能面对B,即A和B不能处于同一纵向直线上。
请写出一个程序,输出A,B所有合法位置。要求在代码中只能使用一个字节存储变量。
分析
本题大体可以理解为A,B各自在一个3×3的格子里上下左右的移动,要想输出A,B的位置,必须要遍历A,B的位置。例如:先遍历A的位置,在遍历B的位置,输出符合条件的位置。然而这题有趣就在于只能使用一个字节存储变量。
如果不要求一个字节的话,那么可以代码可以写成这样,无非就是遍历两次,记录符合条件的坐标。
#include <iostream>
#include <vector>
#include <utility>
using namespace std;
int main(int argc, char const *argv[])
{
vector<pair<int, char>> vecA;
vector<pair<int, char>> vecB;
for(int i = 9; i > 6; --i) {
for(char j = 'd'; j < 'g'; j++) {
vecA.push_back(make_pair(i, j));
}
}
for(int i = 0; i < 3; i++) {
for(char j = 'd'; j < 'g'; j++) {
vecB.push_back(make_pair(i, j));
}
}
for(auto i = vecA.begin(); i != vecA.end(); i++) {
cout << "A (" << (*i).first << "," << (*i).second << ")" << endl;
for(auto j = vecB.begin(); j != vecB.end(); j++) {
if((*i).second != (*j).second) {
cout << "B (" << (*j).first << "," << (*j).second << ")" << endl;
}
}
cout << endl;
}
return 0;
}
当然这么写相当复杂,也不符合题目的要求。
我们可以把“将”和“帅”的坐标看成一个3×3的数组,分别放置1到9。例如“将”在1的位置,“帅”就可以在2,3,5,6,8,9的位置。
对于1到9的数字转换为2进制时最多只需要4位(即9(1001)),在C/C++中我们可以使用占位符(冒号“:”)来节省空间。
在《编程之美》一书中利用结构中包含两个只有4位的char类型,存储位置
struct {
unsigned char a : 4;
unsigned char b : 4;
} i;
代码
代码1如下
#include <iostream>
using namespace std;
struct {
unsigned char a : 4;
unsigned char b : 4;
} i;
int main()
{
for(i.a = 1; i.a <= 9; i.a++) {
for(i.b = 1; i.b <= 9; i.b++) {
if(i.a % 3 != i.b % 3) {
cout << "(A:" << (int)(i.a) << ", B:" << (int)(i.b) << ")" << endl;
}
}
cout << endl;
}
return 0;
}
原书中还写出了另一种算法,并提出了证明上面的算法比下面的算法效率更高的提问。
代码2如下
#include <iostream>
using namespace std;
int main()
{
int i = 81;
while(i--) {
if(i / 9 % 3 == i % 9 % 3)
continue;
printf("A = %d, B = %d\n", i / 9 + 1, i % 9 + 1);
}
return 0;
}
一摞烙饼的排序
排序算法的变种
题目要求
假设有n块大小不一的烙饼,每次只能抓住最上面的一块烙饼,最少要翻几次,才能达到大小有序的结果?输出最优化的翻饼过程。
分析
我们首先需要搞清楚操作–“单手每次抓几块饼,全部颠倒”
常规思路来说,只要每次翻转,找出最大的烙饼放在最下面,那么最终全部烙饼将达到大小有序。基于这种方法,我们至多需要2(n-1)次翻转
另一种思路,每次翻转的时候,把两个本来应该相邻的烙饼尽可能地换在一起,这样,等所有的烙饼都换到一起之后,实际上就是完成排序了。
代码
源书代码如下
#include <iostream>
#include <cassert>
using namespace std;
class CPrerfixSorting
{
public:
CPrerfixSorting()
{
m_nCakeCnt = 0;
m_nMaxSwap = 0;
}
~CPrerfixSorting()
{
if(m_CakeArray) {
delete[] m_CakeArray;
}
if(m_SwapArray) {
delete[] m_SwapArray;
}
if(m_ReverseCakeArray) {
delete[] m_ReverseCakeArray;
}
if(m_ReverseCakeArraySwap) {
delete[] m_ReverseCakeArraySwap;
}
}
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;
cout << "|Search Times| : " << m_nSearch << endl;
cout << "Total Swap times = " << m_nMaxSwap << endl;
}
private:
void Init(int *pCakeArray, int nCakeCnt)
{
assert(pCakeArray != NULL);
assert(nCakeCnt > 0);
m_nCakeCnt = nCakeCnt;
m_nMaxSwap = UpperBound(m_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_SwapArray = new int[m_nMaxSwap + 1];
assert(m_SwapArray != NULL);
m_ReverseCakeArray = new int[m_nCakeCnt];
assert(m_ReverseCakeArray != NULL);
for(int i = 0; i < m_nCakeCnt; ++i) {
m_ReverseCakeArray[i] = m_CakeArray[i];
}
m_ReverseCakeArraySwap = new int[m_nMaxSwap];
assert(m_ReverseCakeArraySwap != NULL);
}
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)) {
// empty
}
else {
ret++;
}
}
return ret;
}
// 主程序
void Search(int step)
{
int 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(int 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);
}
}
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);
for(int i = nBegin, j = nEnd; i < j; ++i, --j) {
int 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()
{
int matrix[10] = {3, 2, 1, 6, 5, 4, 9, 8, 7, 0};
CPrerfixSorting* psort = new CPrerfixSorting;
psort->Run(matrix, 10);
psort->Output();
return 0;
}
当我们每次搜索的时候,我们使用的是全搜索,效率会变的特别差。如果能过控制搜索的范围,那么我们就可以提高我们的效率,而变量m_nMaxSwap派上了用场,在前面我们知道,最大步为2 ×(n -1)所以我们通过对比,如果步数超过了最大步,那么继续搜索只是浪费时间。总的来说,我们应该将上界变得越小越好,下界变得越大越好,这样我们就可以减少需要搜索的空间了
总结
中国将帅问题中,知道了自己的局限性。在开发过程中我们可以通过利用占位符,节约内存资源,以便处理简单。
一摞烙饼的排序问题中,巧妙利用上下界进行缩放,可以很好的提高搜索效率。