马走日

在一个8*8的空白棋盘上,马的行走规则是”日“字形,也就是有8个方向可以走。给定棋盘上的两个点P和Q,求解一条路线从P到Q。

这是一个经典的问题,最近炒冷饭,用了一种新的方法,越看越优雅(好自恋……)
ps,不要误会标题,这不是影评,而是马的走法问题。

思路

选择BFS来搜索,可以找到最短路。而使用BFS,一般有一下两个点需要注意:

1. 标记

在搜索过程,需要对已访问过的点标记为visited,然后已访问过的就不再访问了(不然很容易就死循环了,比如A->B->C->A->B->C->…),这个简单用跟棋盘一样的二维bool数组就好了;

2. 回溯路径

为了最后能够回溯路径,需要记录每一步的上一步是从哪里过来的!

这个的方法很多,可以直接记录整个点(x和y坐标),也可以只记录方向。

毫无疑问,只记录方向,一个值,比记录两个值要好一点吧?

2.1 坐标编码

还有另外一种做法,就是利用“进位制”,把两个坐标编码成一个整数,但是怎么编码呢?
code = x * RowSize + y吗?
那如何复原回去呢?(code / RowSize, code % RowSize)吗?

比如对于RowSize = 8,ColSize = 8,点(3, 7)被编码成3*8 + 7 = 31,然后解码时变成(31/8, 31%8),即(3, 7)。正确吗?

但是,如果ColSize > RowSize,会出现什么情况?
比如RowSize = 5, ColSize = 10,点(3, 7)被编码成3*5 + 7 = 22,解码后变成(22/5, 22%5),即(4, 2),不对吧?

那有人可能就说,我按最大者来作为乘法的因子不就行了吗……那你还得多加一些判断。比如还是上面的例子,点(3, 7)被编码成3 + 7*10 = 73,即:
编码时这样做:

int code;
if (RowSize >= ColSize) {
    code = x * RowSize + y;
} else {
    code = x + y * ColSize;
}

解码时这样做:

int newX, newY;
if (RowSize >= ColSize) {
    newX = code / RowSize;
    newY = code % RowSize;
} else {
    newY = code / ColSize;
    newX = code % ColSize;
}

个人觉得没什么意思,一点都不优雅~

2. 方向编码

这里总共有8个方向,只需要记录上一次是从哪个方向过来的就好了!!!

怎么变回去呢?比如是从(3, 5)通过加上方向2的偏移量(如{-1, -2})到达(2, 3)的,那么从点(2, 3),则要减去方向2的偏移量({-1, -2})。

是不是很简单?一个加,一个减,就是y = x + 1,而x = y - 1的道理~~~

个人觉得如此的编码方式十二分简洁优雅!

po代码啦

#include 
#include 
#include 
using namespace std;


const int RowSize = 8, ColSize = 8, NumOfDirs = 8, NoLastPoint = -1;
const int offset[NumOfDirs][2] = {
    {-1, -2}, {-2, -1}, {-2, 1}, {-1, 2}, 
    {1, 2},   {2, 1},   {2, -1}, {1, -2}
};

struct Point {
    int x, y;
    Point(int _x = 0, int _y = 0) {
        x = _x;
        y = _y;
    }
};

// 检查点的坐标是否合法
inline bool checkBound(const Point& p) {
    return p.x >= 0 && p.x < RowSize && p.y >= 0 && p.y < ColSize;
}


// 使用递归,回溯输出路径
void display(const vector<vector<int> >& last, const Point& now) {
    if (last[now.x][now.y] == NoLastPoint) {
        cout << '(' << now.x << ',' << now.y << ')';
        return;
    }

    // 之前是加,现在减回去
    int dir = last[now.x][now.y];
    Point p(now.x - offset[dir][0], now.y - offset[dir][1]);
    display(last, p);
    cout << " => " << '(' << now.x << ',' << now.y << ')';
}


// 用bfs或dfs搜索就好了,不过bfs能够保证是最短路!
void search(const Point& from, const Point& to) {
    if (!checkBound(from) || !checkBound(to)) {
        cout << "Invalid points." << endl;
        return;
    }
    vector<vector<bool> > visited(RowSize, vector<bool>(ColSize, false));
    vector<vector<int> > last(RowSize, vector<int>(ColSize, NoLastPoint));
    queue q;
    q.push(from);
    visited[from.x][from.y] = true;
    while (!q.empty()) {
        Point now = q.front();
        q.pop();
        if (now.x == to.x && now.y == to.y) {
            display(last, to);
            cout << "\nFound it!!!" << endl;
            return;
        }
        for (int dir = 0; dir < NumOfDirs; ++dir) {
            int newX = now.x + offset[dir][0];
            int newY = now.y + offset[dir][1];
            Point newPoint(newX, newY);
            if (checkBound(newPoint) && !visited[newX][newY]) {
                q.push(newPoint);
                visited[newX][newY] = true;
                last[newX][newY] = dir;
            }
        }
    }
    // 其实肯定是能够找到路的
    cout << "No way from P to Q." << endl;
}


int main() {
    Point from(1, 1);
    Point to(5, 4);
    search(from, to);

    return 0;
}

上面的简单测试例子的输出为:

(1,1) => (2,3) => (3,5) => (5,4)
Found it!!!

Enjoy it, thx for reading.

你可能感兴趣的:(算法,C++)