在一个矩阵中找到最短路径和。Dijkstra算法对于这种情况不是最优的吗?

huangapple go评论91阅读模式
英文:

Find the shortest path sum in a matrix. Is Dijkstra not optimal for this case?

问题

我正在尝试解决Project Euler中的以下问题(请查看链接中的描述和示例,这里是简短的解释)。

在矩阵中,通过向左、向右、向上和向下移动,找到从左上角到右下角的最小路径和。

在我看到这个问题后,我想到的明显解决方案是从矩阵创建一个图,然后使用Dijkstra算法找到最短路径。

为了从一个N*M矩阵构建一个图,对于每个(i, j)元素,我创建一个顶点i * N + j,并将其连接到任何其他顶点(可以通过向上、向右、向下、向左连接),边的权值将是我连接到矩阵中的元素的值。然后,我创建另外两个顶点-1连接到顶点0-2连接到N*M - 1,它们将成为我的起点和终点顶点(两个连接的权值为0)。

之后,我使用Dijkstra算法从-1-2找到最短路径的代价。我的Dijkstra算法实现使用优先队列,代码如下:

func dijkstraCost(graph map[int]map[int]int, start, end int) int {
    if start == end {
        return 0
    }
    frontier := make(PriorityQueue, 1)
    frontier[0] = &Item{value: start, priority: 0, index: 0}
    visited := map[int]bool{}
    heap.Init(&frontier)

    for frontier.Len() > 0 {
        element := heap.Pop(&frontier).(*Item)
        vertex, cost := element.value, element.priority
        visited[vertex] = true
        neighbors := graph[vertex]
        for vertex_new, cost_new := range neighbors {
            if !visited[vertex_new] {
                if vertex_new == end {
                    return cost + cost_new
                }
                heap.Push(&frontier, &Item{
                    value:    vertex_new,
                    priority: cost + cost_new,
                })
            }
        }
    }
    return -1
}

其中,优先队列的实现来自heap容器(示例PriorityQueue),只有一个小的修改:

func (pq PriorityQueue) Less(i, j int) bool {
    return pq[i].priority < pq[j].priority // 改为 <
}

我提供给函数的图示如下:

map[13:map[8:965 18:121 12:746 14:111] 16:map[11:803 21:732 15:537 17:497] 3:map[8:965 2:234 4:18] 4:map[9:150 3:103] 22:map[17:497 21:732 23:37] -1:map[0:131] 17:map[16:699 18:121 12:746 22:524] 1:map[6:96 0:131 2:234] 9:map[4:18 14:111 8:965] 11:map[6:96 16:699 10:630 12:746] 19:map[14:111 24:331 18:121] 24:map[23:37 -2:0 19:956] 2:map[3:103 7:342 1:673] 15:map[10:630 20:805 16:699] 18:map[13:422 23:37 17:497 19:956] 10:map[5:201 15:537 11:803] 14:map[19:956 13:422 9:150] 0:map[5:201 1:673] 6:map[5:201 7:342 1:673 11:803] 8:map[9:150 3:103 13:422 7:342] -2:map[] 12:map[7:342 17:497 11:803 13:422] 20:map[15:537 21:732] 21:map[16:699 20:805 22:524] 5:map[0:131 10:630 6:96] 23:map[18:121 22:524 24:331] 7:map[2:234 12:746 6:96 8:965]]

这个方法可以正确工作,但问题在于它被认为效率低下(根据Hackerrank版本的问题)。它应该在不到4秒的时间内找到700x700矩阵的最佳解的值,而我的解决方案需要10秒。

我认为我在Go语言中做错了什么,所以我用Python重新实现了相同的解决方案(对于700x700矩阵,大约需要70秒)。


**我的问题是:**我是否使用了正确的方法来找到矩阵中的最佳解决方案?如果是的话,我在实现中做错了什么?

附注:我有完整的Go和Python解决方案,只是觉得即使没有它们,问题也太长了。

英文:

I am trying to solve the following problem from project euler (please take a look at description and the example in the link, but here is the short explanation).

> in the matrix, find the minimal path sum from the top left to the bottom right, by moving left, right, up, and down

Right after I looked at the problem, the obvious solution which came to mind is to create a graph from the matrix and then use Dijkstra to find the shortest path.

To construct a graph from a N*M matrix, for every (i, j) element I create a vertex i * N + j and connect it to any other vertex (to which it is possible to connect with UP, RIGHT, DOWN, LEFT) and the edge will be the value of the element I am connecting to in the matrix. After that I create 2 other vertices -1 connected to vertex 0 and -2 connected to N*M - 1 which will be my start and end vertices (both connection have 0 cost).

After this I am doing Dijkstra to find shortest path cost from -1 to -2. My Dijkstra implementation uses priority queue and looks this way:

func dijkstraCost(graph map[int]map[int]int, start, end int) int{
if start == end{
return 0
}
frontier := make(PriorityQueue, 1)
frontier[0] = &amp;Item{value: start, priority: 0, index: 0}
visited := map[int]bool{}
heap.Init(&amp;frontier)
for frontier.Len() &gt; 0 {
element := heap.Pop(&amp;frontier).(*Item)
vertex, cost := element.value, element.priority
visited[vertex] = true
neighbors := graph[vertex]
for vertex_new, cost_new := range(neighbors){
if !visited[vertex_new]{
if vertex_new == end{
return cost + cost_new
}
heap.Push(&amp;frontier, &amp;Item{
value: vertex_new,
priority: cost + cost_new,
})
}
}
}
return -1
}

where Priority Queue implementation is taken from heap container (example PriorityQueue) with one minor modification:

func (pq PriorityQueue) Less(i, j int) bool {
return pq[i].priority &gt; pq[j].priority // changed to &lt;
}

The graph that I am providing to the function looks like:

map[13:map[8:965 18:121 12:746 14:111] 16:map[11:803 21:732 15:537 17:497] 3:map[8:965 2:234 4:18] 4:map[9:150 3:103] 22:map[17:497 21:732 23:37] -1:map[0:131] 17:map[16:699 18:121 12:746 22:524] 1:map[6:96 0:131 2:234] 9:map[4:18 14:111 8:965] 11:map[6:96 16:699 10:630 12:746] 19:map[14:111 24:331 18:121] 24:map[23:37 -2:0 19:956] 2:map[3:103 7:342 1:673] 15:map[10:630 20:805 16:699] 18:map[13:422 23:37 17:497 19:956] 10:map[5:201 15:537 11:803] 14:map[19:956 13:422 9:150] 0:map[5:201 1:673] 6:map[5:201 7:342 1:673 11:803] 8:map[9:150 3:103 13:422 7:342] -2:map[] 12:map[7:342 17:497 11:803 13:422] 20:map[15:537 21:732] 21:map[16:699 20:805 22:524] 5:map[0:131 10:630 6:96] 23:map[18:121 22:524 24:331] 7:map[2:234 12:746 6:96 8:965]]

This works correctly but the problem is that it is considered inefficient (judging by Hackerrank version of the problem). It should run find the value of the best solution for 700x700 matrix in less than 4 seconds, whereas my solution takes 10 seconds.

I thought that I am doing something wrong in go, so I reimplemented the same solution in python (where it took approximately 70 seconds for 700x700 matrix)


My question is: Am I using the right approach to find the best solution in a matrix. If so what am I doing wrong with my implementation?

P.S. I have full go and python solution, just thought that even without them the question is too long.

答案1

得分: 4

Dijkstra应该通过,我刚刚使用JAVA提交了一个解决方案,每个任务完成的时间不到一秒。

正如我之前提到的,矩阵中的每个值都可以达到10^9,你的解决方案可能会遇到数值溢出的问题,这会影响运行时间。

我的代码:

<!-- language:java -->

static int[]X = {0,1,0,-1};
static int[]Y = {1,0,-1,0};
public static void main(String[] args) throws FileNotFoundException {
    // PrintWriter out = new PrintWriter(new FileOutputStream(new File(
    // "output.txt")));
    PrintWriter out = new PrintWriter(System.out);
    Scanner in = new Scanner();        
    int n = in.nextInt();
    long[][]map = new long[n][n];
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++){
            map[i][j] = in.nextLong();
        }
    }
    PriorityQueue<Pos> q= new PriorityQueue();
    long[][]dist = new long[n][n];
    for(long[]a : dist){
        Arrays.fill(a,Long.MAX_VALUE);
    }
    q.add(new Pos(0,0,map[0][0]));
    dist[0][0] = map[0][0];
    while(!q.isEmpty()){
        Pos p = q.poll();
        if(dist[p.x][p.y] == p.cost){
            for(int i = 0; i < 4; i++){
                int x = p.x + X[i];
                int y = p.y + Y[i];
                if(x >= 0 && y >= 0 && x < n && y < n && dist[x][y] > dist[p.x][p.y] + map[x][y] ){
                    dist[x][y] = dist[p.x][p.y] + map[x][y];
                    q.add(new Pos(x,y,dist[x][y]));
                }
            }
        }
    }
    out.println(dist[n - 1][n - 1]);
    out.close();
}

static class Pos implements Comparable<Pos>{
    int x, y;
    long cost;
    public Pos(int x, int y, long cost) {
        super();
        this.x = x;
        this.y = y;
        this.cost = cost;
    }
    @Override
    public int compareTo(Pos o) {
        // TODO Auto-generated method stub
        return Long.compare(cost, o.cost );
    }
}

更新

我认为你的Dijkstra实现是不正确的:

for frontier.Len() > 0 {
    element := heap.Pop(&frontier).(*Item)
    vertex, cost := element.value, element.priority
    //You didn't check for visited vertex here!
    visited[vertex] = true
    neighbors := graph[vertex]
    for vertex_new, cost_new := range(neighbors){
        if !visited[vertex_new]{//You can add same vertex multiple times here!
            if vertex_new == end{
                return cost + cost_new
            }
            heap.Push(&frontier, &Item{
                value: vertex_new,
                priority: cost + cost_new,
            })
        }
    }
}

在你的实现中,只有当顶点从堆中弹出时才更新visited,因此,一个顶点可以被多次添加和处理,这会显著增加时间复杂度。

修复方法:

for frontier.Len() > 0 {
    element := heap.Pop(&frontier).(*Item)
    vertex, cost := element.value, element.priority
    if !visited[vertex]{
        visited[vertex] = true
        neighbors := graph[vertex]
        for vertex_new, cost_new := range(neighbors){
            if !visited[vertex_new]{
                if vertex_new == end{
                    return cost + cost_new
                }
                heap.Push(&frontier, &Item{
                    value: vertex_new,
                    priority: cost + cost_new,
                })
            }
        }   
    }
}
英文:

Dijkstra should pass, I just make a submission using JAVA, and it took less than a second to complete each task.

As I have mentioned, each value in the matrix can go up to 10^9, your solution can encounter a number overflow problem, which can effect the running time.

My code:

&lt;!-- language:java --&gt;
static int[]X = {0,1,0,-1};
static int[]Y = {1,0,-1,0};
public static void main(String[] args) throws FileNotFoundException {
// PrintWriter out = new PrintWriter(new FileOutputStream(new File(
// &quot;output.txt&quot;)));
PrintWriter out = new PrintWriter(System.out);
Scanner in = new Scanner();        
int n = in.nextInt();
long[][]map = new long[n][n];
for(int i = 0; i &lt; n; i++){
for(int j = 0; j &lt; n; j++){
map[i][j] = in.nextLong();
}
}
PriorityQueue&lt;Pos&gt; q= new PriorityQueue();
long[][]dist = new long[n][n];
for(long[]a : dist){
Arrays.fill(a,Long.MAX_VALUE);
}
q.add(new Pos(0,0,map[0][0]));
dist[0][0] = map[0][0];
while(!q.isEmpty()){
Pos p = q.poll();
if(dist[p.x][p.y] == p.cost){
for(int i = 0; i &lt; 4; i++){
int x = p.x + X[i];
int y = p.y + Y[i];
if(x &gt;= 0 &amp;&amp; y &gt;= 0 &amp;&amp; x &lt; n &amp;&amp; y &lt; n &amp;&amp; dist[x][y] &gt; dist[p.x][p.y] + map[x][y] ){
dist[x][y] = dist[p.x][p.y] + map[x][y];
q.add(new Pos(x,y,dist[x][y]));
}
}
}
}
out.println(dist[n - 1][n - 1]);
out.close();
}
static class Pos implements Comparable&lt;Pos&gt;{
int x, y;
long cost;
public Pos(int x, int y, long cost) {
super();
this.x = x;
this.y = y;
this.cost = cost;
}
@Override
public int compareTo(Pos o) {
// TODO Auto-generated method stub
return Long.compare(cost, o.cost );
}
}

Update:

I think your Dijkstra implementation is not correct:

for frontier.Len() &gt; 0 {
element := heap.Pop(&amp;frontier).(*Item)
vertex, cost := element.value, element.priority
//You didn&#39;t check for visited vertex here!
visited[vertex] = true
neighbors := graph[vertex]
for vertex_new, cost_new := range(neighbors){
if !visited[vertex_new]{//You can add same vertex multiple times here!
if vertex_new == end{
return cost + cost_new
}
heap.Push(&amp;frontier, &amp;Item{
value: vertex_new,
priority: cost + cost_new,
})
}
}
}

In your implementation, you only update visited when the vertex pop out of the heap, thus, one vertex can be added and processed multiple time, so, it will significantly increase your time complexity.

To fix

for frontier.Len() &gt; 0 {
element := heap.Pop(&amp;frontier).(*Item)
vertex, cost := element.value, element.priority
if !visited[vertex]{
visited[vertex] = true
neighbors := graph[vertex]
for vertex_new, cost_new := range(neighbors){
if !visited[vertex_new]{
if vertex_new == end{
return cost + cost_new
}
heap.Push(&amp;frontier, &amp;Item{
value: vertex_new,
priority: cost + cost_new,
})
}
}   
}

huangapple
  • 本文由 发表于 2015年6月15日 17:00:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/30841148.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定