康托展开
一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩。实质是计算当前排列在所有由小到大全排列中的顺序。 计算公式:。
例如,3 5 7 4 1 2 9 6 8 展开为 98884。因为
解释: 排列的第一位是3,比3小的数有两个,以这样的数开始的排列有8!个,因此第一项为 排列的第二位是5,比5小的数有1、2、3、4,由于3已经出现,因此共有3个比5小的数,这样的排列有7!个,因此第二项为 以此类推,直至
// 阶乘
vector<int> factorial{1,1,2,6,24,120,720,5040,40320,362880};
// 时间复杂度 O(n^2)
int cantor(vector<int> board) {
int x = 0, n = board.size();
for (int i = 0; i < n; i++) {
int smaller = 0;
for (int j = i + 1; j < n; j++) {
if (board[i] > board[j]) smaller++;
}
x += factorial[n - 1 - i] * smaller;
}
return x;
}
// 98884
cantor(vector<int>{3, 5, 7, 4, 1, 2, 9, 6, 8});
逆展开
如n=9,x=98884时:
98884 / 8! = 2, 说明2个数比第一个数小,第一位是3, x = 98884 % 8! = 18244
18244 / 7! = 3, 说明3个数比第二个数小,第二位是4, x = 18244 % 7! = 3124
… 以此类推。
// 这里n=9即考虑 1-9的数,如果考虑0的话再另行计算
vector<int> decantor(int x, int n) {
// avail 存放可选
vector<int> ret, avail;
for (int i = 1; i <= n; i++) avail.push_back(i);
for (int i = n; i >= 1; i--) {
int quotient = x / factorial[i - 1];
int remain = x % factorial[i - 1];
x = remain;
ret.push_back(avail[quotient]);
avail.erase(avail.begin() + quotient);
}
return ret;
}
// 3, 5, 7, 4, 1, 2, 9, 6, 8
decantor(98884, 9);
广度优先搜索
代码框架
BFS() {
初始化队列
while(队列非空 && 未找到目标节点) {
取队首节点,将扩展出的非重复节点放入队尾(注意去重,不然死循环了),
必要时记住每个节点的父节点(例如题目不只要求最小步数,还要求每一步如何走的)
}
}
N数码有解判断
移动0的位置不改变排列的奇偶性,所以奇排列和偶排列不能相互转换。
即如果当前状态和目标状态都为奇排列或者都为偶排列,那可能有解,但如果不同,则一定无解。
奇排列:排列中逆序数的个数为奇数。
一个问题:在n-puzzle问题中,求逆序数为什么不考虑0???
N数码状态存储
字符串
对于一个mn的数组,则存储一个状态就需要 mn 字节。存储代价高。
整数
直接看成一个m*n位整数,例如 3x3的数组,那这个9位数最大为 876543210。如果用字符数组存储,一个字符元素可以放8个状态(256,ASCII码)。这个字符数组仍然需要将近 876543210 / 8 的字节来存储。存储代价依然很高。
理解:因为有很多重复以及不存在的状态,比如 788888888 这个数并没有对应的状态。
n进制数
比如 3x3的数组,用9进制表示的话,最大为 ,转换为十进制 ,又减少了一定的空间。
理解:9进制相对于10进制又减少了一些不存在的状态的表示,比如 799999999 在9进制中不表示,减少的也是这些数的表示。
阶乘
最后,也就是用康托展开了,一一映射,没有浪费的空间来表示不存在的状态。
leetcode 773
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
using namespace std;
struct Node {
vector<int> board;
int x, y, hash;
int moves;
Node(vector<int> board, int x, int y, int hash, int moves)
:board(board), x(x), y(y), hash(hash), moves(moves){}
Node(){}
};
bitset<720> flags;
vector<vector<int>> dir{ {0, 1}, {0, -1}, {-1, 0}, {1, 0}};
vector<int> factorial{1,1,2,6,24,120,720,5040,40320,362880};
int cantor(vector<int> board) {
int x = 0, n = board.size();
for (int i = 0; i < n; i++) {
int smaller = 0;
for (int j = i + 1; j < n; j++) {
if (board[i] > board[j]) smaller++;
}
x += factorial[n - 1 - i] * smaller;
}
return x;
}
bool check(vector<int> board) {
int reverseNum = 0;
for (int i = 0; i < board.size(); i++) {
if (board[i] == 0) continue;
for (int j = i + 1; j < board.size(); j++) {
if (board[j] == 0) continue;
if (board[i] > board[j]) {
reverseNum++;
}
}
}
return reverseNum % 2 == 0;
}
int slidingPuzzle(vector<vector<int>>& board) {
vector<int> tmpboard;
int ret = 0, x = 0, y = 0, target = cantor({1,2,3,4,5,0});
for (int i = 0; i < board.size(); i++) {
for (int j = 0; j < board[0].size(); j++) {
if (board[i][j] == 0) {
x = i;
y = j;
}
tmpboard.push_back(board[i][j]);
}
}
if (!check(tmpboard)) return -1;
queue<Node> q;
q.push(Node(tmpboard, x, y, cantor(tmpboard), 0));
while (!q.empty()) {
Node cur = q.front();
q.pop();
if (cur.hash == target) {
return cur.moves;
}
for (int i = 0 ; i < 4; i++) {
int tmpX = cur.x + dir[i][0];
int tmpY = cur.y + dir[i][1];
if (tmpX < 0 || tmpX > 1 || tmpY < 0 || tmpY > 2) continue;
Node tmp = cur;
tmp.board[cur.x * 3 + cur.y] = tmp.board[tmpX * 3 + tmpY];
tmp.board[tmpX * 3 + tmpY] = 0;
tmp.x = tmpX;
tmp.y = tmpY;
tmp.hash = cantor(tmp.board);
tmp.moves++;
if (flags[tmp.hash]) continue;
flags.set(tmp.hash);
q.push(tmp);
}
}
return -1;
}
int main() {
vector<vector<int>> board{
{3,2,4},
{1,5,0}
};
cout << slidingPuzzle(board) << endl;
return 0;
}
POJ 1077
一点问题:
1.以下输入为啥不行?
vector<int> board(9);
for (int i = 0; i < 9; i++) {
cin >> board[i];
if (board[i] > 9) board[i] = 0;
}
2.一开始没引入 bitset,报错,xcode咋没错?
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <bitset>
using namespace std;
struct Node {
vector<int> board;
int x, y, hash;
string path;
Node(vector<int> board, int x, int y, int hash, string path)
:board(board), x(x), y(y), hash(hash), path(path){}
Node(){}
};
bitset<362880> flags;
vector<vector<int>> dir{ {0, 1}, {0, -1}, {-1, 0}, {1, 0}};
string pathDir = "rlud";
vector<int> factorial{1,1,2,6,24,120,720,5040,40320,362880};
int cantor(vector<int> board) {
int x = 0, n = board.size();
for (int i = 0; i < n; i++) {
int smaller = 0;
for (int j = i + 1; j < n; j++) {
if (board[i] > board[j]) smaller++;
}
x += factorial[n - 1 - i] * smaller;
}
return x;
}
bool check(vector<int> board) {
int reverseNum = 0;
for (int i = 0; i < board.size(); i++) {
if (board[i] == 0) continue;
for (int j = i + 1; j < board.size(); j++) {
if (board[j] == 0) continue;
if (board[i] > board[j]) {
reverseNum++;
}
}
}
return reverseNum % 2 == 0;
}
string slidingPuzzle(vector<int> board) {
int ret = 0, x = 0, y = 0, target = cantor({1,2,3,4,5,6,7,8,0});
for (int i = 0; i < board.size(); i++) {
if (board[i] == 0) {
x = i / 3;
y = i % 3;
break;
}
}
if (!check(board)) return "unsolvable";
queue<Node> q;
q.push(Node(board, x, y, cantor(board), ""));
while (!q.empty()) {
Node cur = q.front();
q.pop();
if (cur.hash == target) {
return cur.path;
}
for (int i = 0 ; i < 4; i++) {
int tmpX = cur.x + dir[i][0];
int tmpY = cur.y + dir[i][1];
if (tmpX < 0 || tmpX > 2 || tmpY < 0 || tmpY > 2) continue;
Node tmp = cur;
tmp.board[cur.x * 3 + cur.y] = tmp.board[tmpX * 3 + tmpY];
tmp.board[tmpX * 3 + tmpY] = 0;
tmp.x = tmpX;
tmp.y = tmpY;
tmp.hash = cantor(tmp.board);
tmp.path += pathDir[i];
if (flags[tmp.hash]) continue;
flags.set(tmp.hash);
q.push(tmp);
}
}
return "unsolvable";
}
int main() {
vector<int> board;
string s;
getline(cin, s);
for (int i = 0; i < s.size(); i++) {
if (s[i] == ' ') continue;
if (s[i] == 'x') board.push_back(0);
else board.push_back(s[i] - '0');
}
cout << slidingPuzzle(board) << endl;
return 0;
}
改进:
- 双向广搜(DBFS),从目标节点和起始节点同时进行扩展,直到一个扩展队列中出现另外一个队列中已经扩展的节点。即两个扩展方向出现了交点。
- 预处理,即一开始从目标节点彻底的广搜一遍,找出从目标节点所能到达的其它节点。
- A*算法。
- 分支限界法,给定节点以优先级,f(x) = 当前节点到根节点的路径的长度 + 排列X中的不在自然位置的数字的个数。f(x)小的优先级高。
HDU 1043
HDU 1043 有多组数据。而上面POJ只有一组数据,所以直接BFS在HDU上会超时。
以下这样写A不掉。。。不知到是不是输入写的不对。
int main() {
string s;
getline(cin, s);
vector<string> out;
while (!s.empty()) {
vector<int> board;
for (int i = 0; i < s.size(); i++) {
if (s[i] == ' ') continue;
if (s[i] == 'x') board.push_back(0);
else board.push_back(s[i] - '0');
}
out.push_back(slidingPuzzle(board));
getline(cin, s);
cout << s << endl;
}
for (string str : out) {
cout << str << endl;
}
return 0;
}
参考
- http://blog.csdn.net/wbin233/article/details/72998375
- https://www.coursera.org/learn/suanfa-jichu/lecture/kIxPh/yan-sou-yu-ba-shu-ma-wen-ti?authMode=login