我们希望在特殊网格中找到两点之间的最短路径。我们可以在一次移动中在相邻的方块之间移动,但是我们也可以在一次移动中在相同类型的单元格之间移动(有10种类型),无论它们之间的距离如何。
我们怎样才能找到在两个点之间移动所需的步数,最大尺寸为100x100?
答案 0 :(得分:3)
我在比赛期间使用BFS解决了这个问题。
可以将问题建模为图表。每个单元是一个节点,并且每个单元都有一个边。我们可以通过根据需要计算网格坐标来访问每个单元格及其邻居,而不是显式地构建图形。
每个细胞也具有相同颜色的所有细胞的边缘。我们可以通过保留每种颜色的单元格列表并访问与当前单元格颜色相同的所有单元格,将其添加到图表中。
像Dijkstra或A *这样的东西可以工作(它们本质上是一个具有优先级队列/启发式的BFS),但是对于这样一个简单的问题实现它将是严重的过度杀伤力。
代码如下(在C ++中):
#include <iostream>
#include <queue>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
char grid[101][101];
int cost[101][101];
vector<pair<int,int> > colour[10]; // lists of same coloured cells
//used to compute adjacent cells
int dr[]={ 0, 0, 1,-1};
int dc[]={ 1,-1, 0,0};
int rows,cols; // total rows and coloumns
int bfs(pair<int,int> start){
memset(cost,-1,sizeof(cost)); // all unvisited nodes have negative cost to mark them
cost[start.first][start.second]=0; // start node has cost 0
queue<pair<int,int> > Q;
Q.push(start);
while (!Q.empty()){
pair<int,int> node=Q.front();Q.pop();
int r=node.first;
int c=node.second;
int cst=cost[r][c];
if (grid[r][c]=='E'){
return cst;
}
// search adjacent cells
for(int i=0;i<4;i++){
int nr=r+dr[i];
int nc=c+dc[i];
if (nr>=0 && nr<rows && nc>=0 && nc<cols && cost[nr][nc]<0 && grid[nr][nc]!='W'){
cost[nr][nc]=cst+1;
Q.push(make_pair(nr,nc));
}
}
// search cells of the same colour
if (grid[r][c]>='1' && grid[r][c]<='9'){
vector<pair<int,int> > &trans=colour[grid[r][c]-'0'];
for(int i=0;i<trans.size();i++){
pair<int,int> next=trans[i];
if (cost[next.first][next.second]<0){
cost[next.first][next.second]=cst+1;
Q.push(next);
}
}
}
}
return -1;
}
int main(){
int N;
cin>>N;
while(N--){
cerr<<"cases left"<<N<<endl;
cin>>rows>>cols;
pair<int,int> start;
for(int i=0;i<10;i++) colour[i].clear();
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
cin>>grid[i][j];
if (grid[i][j]=='S'){
start=make_pair(i,j);
}
else if (grid[i][j]>='1' && grid[i][j]<='9'){
colour[grid[i][j]-'0'].push_back(make_pair(i,j));
}
}
}
cout<<bfs(start)<<"\n";
}
return 0;
}
答案 1 :(得分:2)
制作10个数组,每个数组包含相应类型的单元格。现在运行Dijkstra(或BFS),但是当您访问类型为i
的单元格时,将所有类型为i
的单元格添加到队列中并清除相应的数组。这样您就不必访问相同类型的单元格之间的每个边缘,复杂度为O(n ^ 2)而不是O(n ^ 4)
答案 2 :(得分:1)
我认为最简单的解决方法是将网格建模为图形。在2个邻居之间创建边,也在相同类型的两个节点之间创建一条边。之后,从源到dest的简单BFS可以回答两个节点之间的最短路径。
http://en.wikipedia.org/wiki/Breadth-first_search
复杂性为O(V + E)。 V是节点数,100x100,E是边数
这里的人们提到Dijkstra。这解决了,但它是必要的,因为所有边缘都花费一个。 Dijkstra是A-star算法的一个特例,所以A *对于这个问题也是太多了。
保持简单,BFS!
这是O(R * C)时间和O(R * C)空间解决方案:
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;
public class DanceBattle {
static final int INF = (int)(Integer.MAX_VALUE * 0.5);
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int N = in.nextInt();
for(int i = 0; i < N ; ++i) {
int R = in.nextInt();
int C = in.nextInt();
int V = R*C;
int node = 0, start = 0, end = 0;
int nodeColor[] = new int[V];
List<Integer>[] colorMap = new List[10];
for(int color=0;color<10;++color)
colorMap[color] = new LinkedList<Integer>();
for(int r = 0; r < R; ++r) {
String row = in.next();
for(int c = 0; c<row.length(); ++c) {
char v = row.charAt(c);
if(v == 'S' ) start = node;
else if (v == 'E') end = node;
else if (v == 'W') nodeColor[node] = -1;
else {
int color = (int)(v - '0');
nodeColor[node] = color;
colorMap[color].add(node);
}
node++;
}
}
int neighborhood4[][] = new int[V][4];
for(int j =0 ; j< V; ++j) {
int c = j % C;
int r = (j - c)/C;
int grad = 0;
if(neighbors(r,c,r,c+1, R, C))
neighborhood4[j][grad++] = (r) * C + (c+1);
if(neighbors(r,c,r,c-1, R, C))
neighborhood4[j][grad++] = (r) * C + (c-1);
if(neighbors(r,c,r+1,c, R, C))
neighborhood4[j][grad++] = (r+1) * C + (c);
if(neighbors(r,c,r-1,c, R, C))
neighborhood4[j][grad++] = (r-1) * C + (c);
if(grad < 4)
neighborhood4[j][grad] = -1;
}
//bfs
int qbegin, qend;
int[] Q = new int[V];
int[] dist = new int[V];
for(int j=0; j<V; j++) dist[j] = INF;
dist[start] = 0;
qbegin = qend = 0;
Q[qend++] = start;
int complexity = 0;
while(qbegin != qend) {
int currNode = Q[qbegin++];
for(int j=0; j<4; j++) {
int neighbor = neighborhood4[currNode][j];
if(neighbor == -1) break;
int color = nodeColor[neighbor];
if(dist[neighbor] == INF && color != -1 ) {
Q[qend++] = neighbor;
dist[neighbor] = dist[currNode] + 1;
}
complexity++;
}
int color = nodeColor[currNode];
if (color == 0)
continue;
Iterator<Integer> iter = colorMap[color].iterator();
while(iter.hasNext()) {
int viz = iter.next();
if(dist[viz] == INF) {
Q[qend++] = viz;
dist[viz] = dist[currNode] + 1;
}
iter.remove();
complexity++;
}
}
System.out.printf("%d\n",dist[end]);
//System.out.printf("(compl. %d V=%d constant: %.2f)\n", complexity, V, ((float)complexity) / V);
}
}
private static boolean neighbors(int ar, int ac, int br, int bc, int R, int C) {
if(ar < 0 || ac < 0 || br < 0 || bc < 0) return false;
if(ar >= R || ac >= C || br >= R || bc >= C) return false;
return (Math.abs(ar - br) <= 1 && Math.abs(ac - bc) ==0) || (Math.abs(ar - br) == 0 && Math.abs(ac - bc) <=1);
}
}
答案 3 :(得分:1)
你可以将迷宫表示为图形并使用BFS解决它(它适用于这种情况,并且比Dijkstra和A *更简单。)
为了填补边缘,你可以这样做:
for each row
for each line
if char == 'S' mark this point as start
if char == 'E' mark this point as end
if char != 'W' then
create edges between this point and its adjacents (only if they exist and aren't a wall)
if char >= '1' and char <= '9'
create edges between this point and everyone with the same color
然后将start
的BFS(Breadth-first start)应用到end
,您就完成了。
PS:为了节省内存,您应该使用邻接列表来表示图形,因为大多数节点将有4个邻居。
答案 4 :(得分:0)
构造一个所有正方形作为顶点的图形,边缘标记可能的移动,然后让Dijkstra算法解决它。
答案 5 :(得分:0)
可以使用Dynamic programming完成此操作。在这里,您可以定义连接,并从第一个方块开始。你记录了到达某一点的最小距离。当您到达终点时,通过回溯最快下降路线找到路径。
在您的情况下,您应该定义相邻单元格之间以及相同颜色的单元格之间的连接。