This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Solution { | |
public: | |
int slidingPuzzle(vector<vector<int>>& board) { | |
int m = board.size(), n = board[0].size(), layer = 0, pos = 0; | |
string target = "123450"; | |
for(int i = 0; i < m; ++i) | |
for(int j = 0; j < n; ++j) | |
if(!board[i][j])pos = i * n + j; | |
//BFS | |
queue<pair<string, int>> q; | |
q.push(make_pair(getKey(board), pos)); | |
unordered_set<string> visited; | |
while(q.size()) | |
{ | |
int len = q.size(), i = 0; | |
while(i++ < len) | |
{ | |
auto p = q.front(); | |
q.pop(); | |
visited.insert(p.first); | |
if(p.first == target)return layer; | |
auto neighbors = adj(p.first, p.second, m, n); | |
for(auto& neighbor : neighbors) | |
{ | |
if(visited.find(neighbor.first) == visited.end()) | |
q.push(neighbor); | |
} | |
} | |
++layer; | |
} | |
return -1; | |
} | |
private: | |
string getKey(vector<vector<int>>& board) | |
{ | |
string key; | |
for(auto& row : board) | |
for(auto& cell : row) | |
key += to_string(cell); | |
return key; | |
} | |
vector<pair<string, int>> adj(string state, int pos, int m, int n) | |
{ | |
int i = pos / n, j = pos % n; | |
pair<int, int> dirs[4] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; | |
vector<pair<string, int>> neighbors; | |
for(int k = 0; k < 4; ++k) | |
{ | |
int x = i + dirs[k].first, y = j + dirs[k].second; | |
if(x >= 0 && x < m && y >= 0 && y < n) | |
{ | |
string s = state; | |
swap(s[pos], s[x * n + y]); | |
neighbors.push_back(make_pair(s, x * n + y)); | |
} | |
} | |
return neighbors; | |
} | |
}; |
另外一种做法是A* search,在bfs或者其他非启发式搜索的时候我们keep track of的都是当前状态从开始状态转移过来已经travel的距离g(n)。但是A* search,作为启发式搜索算法,会在搜索的时候考虑估算从当前节点到目标节点的距离,也就是启发方程,heuristic fucniton h(n)。我们在搜索的时候,不再是单纯地考虑已经走过的距离,而是将两者结合在一起:
- f(n) = g(n) + h(n)
其中g(n)代表的是从初始节点到达当前节点走过的距离,h(n)代表的是当前节点到目标节点的估算距离。这里h(n)有几个性质:
- 如果h(n)从来不高估从当前节点到目标节点的距离的话,可以保证让我们找到最优解。我们称这种h(n)为admissble heuristic
- 如果h(n)满足h(n) <= h(n') + w(n, n'),其中n和n'为相邻节点,w(n, n')为两个节点边的权重,我们称h(n)为consistent或者monotone。那么当我们跑类似dijkstra算法从priority queue上展开节点的时候,到当前节点的最短路径就被找到了。因为f(n) = g(n) + h(n) <= g(n) + h(n') + w(n, n') = g(n') + h(n') = f(n'),也就是说,f(n)在任意一条路径上都是递增的,那么我们只需要用类似dijkstra的证明方法证明即可,这里不做赘述
- 如果h是consistent的并且在目标节点处为0,那么其一定是admissible的。假设n节点经过v1,v2...组成的最短路径到达目标节点t,我们有h(n) <= h(v1) + w(v1, n) <= h(v2) + w(v1, v2) + w(v1, n) <= ... <= shortestPath(n, t) + h(t) = shortestPath(n, t),这对任意n都成立,证明完毕
回归到这一题,我们可以用所有除了0以为的tile距离目标位置的曼哈顿距离的和做heuristic function,那么h(n)一定是consistent并且admissible的:
- 在目标节点处h为0
- 我们每次只能讲一个tile和0互换,从n状态变为n',cost为1,这样最多只能把h(n)到h(n')减小1。所以满足h(n) <= h(n') + w(n, n')
所以我们只需要按照这个f(n)进行A star search即可,实现的话用priority queue,时间复杂度O(d log d),d为最短路径,因为所有f(n)小于d的节点都会被展开。代码如下:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Solution { | |
public: | |
int slidingPuzzle(vector<vector<int>>& board) { | |
int m = board.size(), n = board[0].size(), pos = 0; | |
string target = "123450"; | |
for(int i = 0; i < m; ++i) | |
for(int j = 0; j < n; ++j) | |
if(!board[i][j])pos = i * n + j; | |
//A* search | |
priority_queue<pair<int, pair<string, int>>, vector<pair<int, pair<string, int>>>, greater<pair<int, pair<string, int>>>> pq; | |
string state = getState(board); | |
int estimatedDist = h(state, m, n); | |
pq.push({estimatedDist, {state, pos}}); | |
unordered_set<string> visited; | |
while(pq.size()) | |
{ | |
auto p = pq.top(); | |
pq.pop(); | |
if(visited.find(p.second.first) != visited.end())continue; | |
visited.insert(p.second.first); | |
if(p.second.first == target)return p.first; | |
auto neighbors = adj(p.second.first, p.second.second, m, n, p.first + 1); | |
for(auto& neighbor : neighbors) | |
{ | |
if(visited.find(neighbor.second.first) == visited.end()) | |
pq.push(neighbor); | |
} | |
} | |
return -1; | |
} | |
private: | |
string getState(vector<vector<int>>& board) | |
{ | |
string key; | |
for(auto& row : board) | |
for(auto& cell : row) | |
key += to_string(cell); | |
return key; | |
} | |
int h(string state, int m, int n) | |
{ | |
int dist = 0; | |
for(int k = 0; k < state.size(); ++k) | |
{ | |
if(state[k] != '0') | |
{ | |
int i = k / n, j = k % n; | |
int targetPos = state[k] - '0' - 1, x = targetPos / n, y = targetPos % n; | |
dist += abs(x - i) + abs(y - j); | |
} | |
} | |
return dist; | |
} | |
vector<pair<int, pair<string, int>>> adj(string state, int pos, int m, int n, int distTravelled) | |
{ | |
int i = pos / n, j = pos % n; | |
pair<int, int> dirs[4] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; | |
vector<pair<int, pair<string, int>>> neighbors; | |
for(int k = 0; k < 4; ++k) | |
{ | |
int x = i + dirs[k].first, y = j + dirs[k].second; | |
if(x >= 0 && x < m && y >= 0 && y < n) | |
{ | |
string s = state; | |
swap(s[pos], s[x * n + y]); | |
int estimatedDist = h(s, m, n); | |
neighbors.push_back({distTravelled - h(state, m, n) + estimatedDist, {s, x * n + y}}); | |
} | |
} | |
return neighbors; | |
} | |
}; |
No comments:
Post a Comment