使用基于邻接表的Dijkstra算法求解Project Euler问题

[color=black][b][size=medium]Project Euler中的几个问题[/size][/b][/color]

  首先,来看一看[url=http://projecteuler.net]Project Euler[/url]上的第81到83题。这几个题目的前提条件是一样的,已知一个[url=http://projecteuler.net/project/matrix.txt]80×80的矩阵[/url](由正整数组成)
  [url=http://projecteuler.net/index.php?section=problems&id=81]81题[/url]:[color=green]Find the minimal path sum, in the 80 by 80 matrix, from the top left to the bottom right by only moving right and down.[/color]
  其意思是:[color=orange]只允许从上往下或从左到右,找一条从这个矩阵左上角到右下角的路径,使得路径上的数字之和最小。[/color]

  [url=http://projecteuler.net/index.php?section=problems&id=82]82题[/url]:[color=green]Find the minimal path sum, in the 80 by 80 matrix, from the left column to the right column,by only moving up, down, and right.[/color]
  该题的意思是:[color=orange]允许从上到下或从下到上或从左到右移动,找一条从该矩阵最左列到最右列的路径,使得路径上的数字之和最小。[/color]

  [url=http://projecteuler.net/index.php?section=problems&id=83]83题[/url]:[color=green]Find the minimal path sum, in the 80 by 80 matrix, from the top left to the bottom right by moving left, right, up, and down.[/color]
  翻译过来就是:[color=orange]可以上下左右移动,找一条从该矩阵左上角到右下角的路径,使得路径上数字之和最小。[/color]

  这几个题非常类似,都是从一个给定矩阵找一条路径使得上面数字之和最小。只不过移动的方式越来越多,因此可以想象这几个题目的难度也是越来越大。如果对动态规划比较熟悉的话,很容易可以看出81与82题是可以使用动态规划来解决的。只不过81题属于典型的动态规划题,很容易写出代码,而82题稍稍麻烦了一点。
  不过这里我不谈怎么使用动态规划来解决这些问题,而是转换一下考察问题的角度,将这几个问题转换成图论中经典的“最短路”问题,然后可以使用同样的方法完美解决之。
  首先,[color=blue]将80×80的矩阵看成一个80×80个顶点的图,如果能够从矩阵(i,j)移动到(u,v),则用一条(i,j)到(u,v)的边将这两个顶点连接起来,并且该边的权即为矩阵在(u,v)处的取值。这样,就将一个矩阵转换为一个带权的有向图。[/color]并且容易看出,题目中要求的“数字之和最小的路径”对应该有向图的一个“最短路”。这三个题的区别只在于顶点与顶点之间连接的边不一样。


[color=black][b][size=medium]Dijkstra算法与实现[/size][/b][/color]

  图论中关于求“最短路”问题的算法有很多,著名的有Floyd-Warshall算法,Bellman-Ford 算法etc。不过,Floyd-Warshall算法虽然实现简单,但时间复杂度是O(n^3),而这里n = 80*80 = 6400,使用Floyd-Warshall算法时间上无法接受。另外,这三个题中涉及到的有向图都属于[color=orange]稀疏图[/color],所以我采用了基于图的邻接表结构的Dijkstra算法,这个实现的时间复杂度是O(e×log e),其中e表示边的条数。由于这三个题中e = O(v)(v指图的顶点个数),因此最后我们得到了时间复杂度为O(v log v)的解法(这些题中,v = 80*80)。下面是使用Scala实现的Dijkstra算法:
/**
&#Graph.scala
utils for graph algorithm
@author Eastsun
*/
package eastsun.math

object Graph {
/**
This is an implementation of Dijkstra's algorithm to find the shortest path for a directed
graph with non-negative edge weights.
*/
def dijkstra(size :Int,start :Int,lst :Int=> Iterable[(Int,Int)]):Array[Int] = {
import java.lang.Integer.{ MAX_VALUE => INF }
implicit def t2o(t :(Int,Int)) = new Ordered[(Int,Int)]{
def compare(that :(Int,Int)) = that._2 - t._2
}
val pq = new scala.collection.mutable.PriorityQueue[(Int,Int)]
val dist = Array.make(size,INF)
val mark = Array.make(size,false)
pq += start->0
while(!pq.isEmpty){
val (idx,dst) = pq.dequeue
if(!mark(idx)){
mark(idx) = true
dist(idx) = dst
for((i,d) <- lst(idx);if dist(i)>dst+d){
dist(i) = dst+d
pq += i->dist(i)
}
}
}
dist
}
}

  考虑到对Scala熟悉的不多,我简单解释一下上面这段代码。首先,函数
    def dijkstra(size :Int,start :Int,lst :Int=> Iterable[(Int,Int)]):Array[Int] 

有三个参数,其中:
    size    [color=orange]表示要考虑的有向图的阶[/color](也就是顶点个数)
    start    [color=orange]要求的最短路的起始点[/color]
    lst     [color=orange]一个参数为Int返回值为Iterable[(Int,Int)]的函数。lst(k)将返回图的第k个顶点对应的邻接表,邻接表的每个节点保存两个值(idx,dst)。其中idx表示顶点k到顶点idx有一条边,dst表示这条边的权值。[/color]
返回值为一个整型数组Array[Int],该数组保存了最终结果,其长度为size,第k个元素的值表示从顶点start到顶点k的最短距离,如果不能到达,则为Integer.MAX_VALUE。

  其次,看一下这段代码:
        implicit def t2o(t :(Int,Int)) = new Ordered[(Int,Int)]{
def compare(that :(Int,Int)) = that._2 - t._2
}
val pq = new scala.collection.mutable.PriorityQueue[(Int,Int)]

这段代码的功能是new了一个优先队列pq,该优先队列里面保存的数据类型为(idx,dst)。其中idx为顶点的序号,而dst为距离。并且规定了一个顺序Ordered[(Int,Int)],使得优先队列保持dst最小的在最上面。
  可以计算,上面的Dijkstra实现的时间复杂度为O(e log e)。


[color=black][b][size=medium]解题代码[/size][/b][/color]
  有了上面的介绍,下面直接给出Project Euler这几个问题的代码,可以看到,这几个问题的解题代码非常一致(82题略有不同)
81题的解题代码:
import eastsun.math.Graph._
import scala.io.Source._

object Euler081 extends Application {
val mtx = fromFile("matrix.txt").getLines.map{ line =>
line.split(",").map(_.trim.toInt)
}.toList.toArray

val LEN = mtx.size
val SIZE = LEN*LEN
def lst(n :Int):List[(Int,Int)] = {
val (x,y) = (n/LEN,n%LEN)
var ls = Nil:List[(Int,Int)]
if(x < LEN-1) ls = (n+LEN,mtx(y)(x+1))::ls
if(y < LEN-1) ls = (n+1,mtx(y+1)(x))::ls
ls
}
println(dijkstra(SIZE,0,lst)(SIZE-1)+mtx(0)(0))
}


82题的解题代码:
import scala.io.Source._
import eastsun.math.Graph._

object Euler082 extends Application {
val mtx = fromFile("matrix.txt").getLines.map{ line =>
line.split(",").map(_.trim.toInt)
}.toList.toArray
val LEN = mtx.size
val SIZE = LEN*LEN
def lst(n :Int):List[(Int,Int)] = {
val (x,y) = (n/LEN,n%LEN)
var ls = Nil:List[(Int,Int)]
if(y > 0) ls = (n-1,mtx(y-1)(x))::ls
if(y < LEN-1) ls = (n+1,mtx(y+1)(x))::ls
if(x < LEN-1) ls = (n+LEN,mtx(y)(x+1))::ls
ls
}
val res = 0.until(LEN).map{ n =>
dijkstra(SIZE,n,lst).slice(SIZE-LEN,SIZE).reduceLeft(_ min _)+mtx(n)(0)
}.reduceLeft(_ min _)
println(res)
}


83题的解题代码:
import scala.io.Source._
import eastsun.math.Graph._

object Euler083 extends Application {
val mtx = fromFile("matrix.txt").getLines.map{ line =>
line.split(",").map(_.trim.toInt)
}.toList.toArray
val LEN = mtx.size
val SIZE = LEN*LEN
def lst(n :Int):List[(Int,Int)] = {
val (x,y) = (n/LEN,n%LEN)
var ls = Nil:List[(Int,Int)]
if(y > 0) ls = (n-1,mtx(y-1)(x))::ls
if(y < LEN-1) ls = (n+1,mtx(y+1)(x))::ls
if(x > 0) ls = (n-LEN,mtx(y)(x-1))::ls
if(x < LEN-1) ls = (n+LEN,mtx(y)(x+1))::ls
ls
}

println(dijkstra(SIZE,0,lst)(SIZE-1)+mtx(0)(0))
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值