《编程之美》 -- 中国象棋将帅问题和一摞烙饼的排序

中国象棋将帅问题

题目要求

假设棋盘上只有“将”和“帅”二子(为了方便描述,我们约定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)所以我们通过对比,如果步数超过了最大步,那么继续搜索只是浪费时间。总的来说,我们应该将上界变得越小越好,下界变得越大越好,这样我们就可以减少需要搜索的空间了

总结

中国将帅问题中,知道了自己的局限性。在开发过程中我们可以通过利用占位符,节约内存资源,以便处理简单。

一摞烙饼的排序问题中,巧妙利用上下界进行缩放,可以很好的提高搜索效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值