找到矩阵中的最短路径总和。 Dijkstra在这种情况下不是最佳的吗?

时间:2015-06-15 09:00:27

标签: algorithm go

我正在尝试解决the following problem from project euler(请查看链接中的说明和示例,但这里是简短说明)。

  矩阵中的

,通过向左,向右,向上和向下移动找到从左上角到右下角的最小路径总和

在我查看问题之后,我想到的显而易见的解决方案是从矩阵创建图表,然后使用Dijkstra找到最短路径。

要从N*M矩阵构造图形,对于每个(i, j)元素,我创建一个顶点i * N + j并将其连接到任何其他顶点(可以用UP连接到它) ,RIGHT,DOWN,LEFT)和边缘将是我在矩阵中连接的元素的值。之后,我创建了另外两个顶点-1连接到顶点0-2连接到N*M - 1这将是我的起点和终点(两个连接都有0成本)。< / p>

在此之后,我正在做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
}

其中优先级队列实现是从堆容器(例如PriorityQueue)获取的,只有一个小的修改:

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

我向该函数提供的图表如下:

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 version of the problem判断)。它应该在不到4秒的时间内找到700x700矩阵的最佳解决方案的值,而我的解决方案需要10秒。

我认为我在go中做错了所以我在python中重新实现了相同的解决方案(700x700矩阵需要大约70秒)

我的问题是:我是否使用正确的方法在矩阵中找到最佳解决方案。如果是这样,我的实施有什么问题?

P.S。我有完整的go和python解决方案,只是认为即使没有它们,问题也太长了。

1 个答案:

答案 0 :(得分: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,
                })
            }
        }   
    }