通过矩阵的最佳路径


19

给定一个由正整数组成的矩阵,当从左上角元素到右下角遍历时,输出总和最小的路径。您可以垂直,水平和对角移动。请注意,可以上下左右移动,也可以左右移动。

例:

 1*   9    7    3   10    2    2
10    4*   1*   1*   1*   7    8
 3    6    3    8    9    5*   7
 8   10    2    5    2    1*   4
 5    1    1    3    6    7    9*

给出最低总和的路径标有星号,并得出以下总和:1 + 4 + 1 + 1 + 1 + 5 + 1 + 9 = 23

测试用例:

1   1   1
1   1   1
Output: 3

 7    9    6    6    4
 6    5    9    1    6
10    7   10    4    3
 4    2    2    3    7
 9    2    7    9    4
Output: 28

2  42   6   4   1
3  33   1   1   1
4  21   7  59   1
1   7   6  49   1
1   9   2  39   1
Output: 27 (2+3+4+7+7+1+1+1+1)

 5    6    7    4    4
12   12   25   25   25
 9    4   25    9    5
 7    4   25    1   12
 4    4    4    4    4
Output: 34 (5+12+4+4+4+1+4)

1   1   1   1
9   9   9   1
1   9   9   9
1   9   9   9
1   1   1   1
Output: 15

 2   55    5    3    1    1    4    1
 2   56    1   99   99   99   99    5
 3   57    5    2    2    2   99    1
 3   58    4    2    8    1   99    2
 4   65   66   67   68    3   99    3
 2    5    4    3    3    4   99    5
75   76   77   78   79   80   81    2
 5    4    5    1    1    3    3    2
Output: 67 (2+2+3+3+4+5+4+3+3+3+1+2+2+1+3+1+1+4+5+1+2+3+5+2+2)

这是因此每种语言中最短的代码将获胜。


非常相似,尽管它不允许对角线移动。
Mego

7
@WheatWizard我不同意。除了这个挑战允许对角线运动和所有位置都可以到达的大部分肤浅差异之外,另一个挑战要求返回路径本身,而不仅仅是返回路径成本。除非您使用返回两者的内置程序,否则代码不可互换。
烧杯

@beaker我看不出有什么区别。您必须找到路径才能知道其长度。在我看来,这里的差异是输出上的很小的差异,并且该挑战没有提供该挑战尚未涵盖的任何新的或有趣的东西。
小麦巫师

1
@WheatWizard我的解决方案在这里找不到路径。它可以找到路径,但并非没有单独的前任数组和逻辑,以避免使节点成为自己的前任。更不用说恢复路径了。
烧杯

我认为@beaker的修改是微不足道的。不管是否存在重复问题,不是一个挑战中的每个有效条目都可以以最小的努力移植过来,这与一般情况有关。我不仅认为这里的大部分努力都可以移植,而且我认为这一挑战没有提供其他新的或有趣的东西。
小麦巫师

Answers:


8

JavaScript中,442个412 408 358字节

这是我第一次提交PPCG。反馈将不胜感激。

(m,h=m.length,w=m[0].length)=>{for(i=0;i<h*w;i++)for(x=0;x<w;x++){for(y=0;y<h;y++){if(m[y][x]%1==0)m[y][x]={c:m[y][x],t:m[y][x]};for(X=-1;X<=1;X++)for(Y=-1;Y<=1;Y++){t=x+X;v=y+Y;if((X==0&&Y==0)||t<0||t>=w||v<0||v>=h)continue;if(m[v][t]%1==0)m[v][t]={c:m[v][t],t:null};c=m[y][x].t+m[v][t].c;if (c<m[v][t].t||m[v][t].t==null)m[v][t].t=c}}}return m[h-1][w-1].t}

这将多维数组作为输入。

说明

基本上,遍历所有单元反复调整最低已知成本以到达每个邻居。最终,网格将达到一种状态,其中到达右下角的总成本是到达那里的最低成本。

演示版

f=(m,h=m.length,w=m[0].length)=>{for(i=0;i<h*w;i++)for(x=0;x<w;x++){for(y=0;y<h;y++){if(m[y][x]%1==0)m[y][x]={c:m[y][x],t:m[y][x]};for(X=-1;X<=1;X++)for(Y=-1;Y<=1;Y++){t=x+X;v=y+Y;if((X==0&&Y==0)||t<0||t>=w||v<0||v>=h)continue;if(m[v][t]%1==0)m[v][t]={c:m[v][t],t:null};c=m[y][x].t+m[v][t].c;if (c<m[v][t].t||m[v][t].t==null)m[v][t].t=c}}}return m[h-1][w-1].t}

//Tests
console.log(f([[1,1,1],[1,1,1]])===3);
console.log(f([[7,9,6,6,4],[6,5,9,1,6],[10,7,10,4,3],[4,2,2,3,7],[9,2,7,9,4]])===28);
console.log(f([[2,42,6,4,1],[3,33,1,1,1],[4,21,7,59,1],[1,7,6,49,1],[1,9,2,39,1]])===27);
console.log(f([[5,6,7,4,4],[12,12,25,25,25],[9,4,25,9,5],[7,4,25,1,12],[4,4,4,4,4]])===34); 
console.log(f([[1,1,1,1],[9,9,9,1],[1,9,9,9],[1,9,9,9],[1,1,1,1]])===15)
console.log(f([[2,55,5,3,1,1,4,1],[2,56,1,99,99,99,99,5],[3,57,5,2,2,2,99,1],[3,58,4,2,8,1,99,2],[4,65,66,67,68,3,99,3],[2,5,4,3,3,4,99,5],[75,76,77,78,79,80,81,2],[5,4,5,1,1,3,3,2]])===67);

编辑:特别感谢@ETHproductions帮助我剃了许多美味的字节。

感谢@Stewie Griffin提供的技巧,这些技巧消除了50个字节。


3
欢迎来到PPCG!您可以在结尾处删除一些额外的空间(我总共算出5个),并且不需要直接在之前的任何分号,}它可以节省一些字节。您也不需要声明变量。我认为删除vars应该总共为您节省24个字节。
ETHproductions '17

2
欢迎使用PPCG =)很高兴您选择我的挑战之一作为起点。我唯一的评论:我喜欢解释。但是它是可选的,因此除非您愿意,否则不必添加它。:)
Stewie Griffin

我认为也许存储m[v][t]为变量:t=x+X;v=y+Y;k=m[v][t]会更短...吗?
Stewie Griffin

7

蟒3 + numpy的 + SciPy的239个 222 186字节

from numpy import*
from scipy.sparse.csgraph import*
def f(M):m,n=s=M.shape;x,y=indices(s);return dijkstra([(M*(abs(i//n-x)<2)*(abs(i%n-y)<2)).flatten()for i in range(m*n)])[0,-1]+M[0,0]

在线尝试!


6

八度+图像处理包,175个 162 157 151 142 139字节

保存的14字节由于@Luis Mendo和1字节由于@notjagan

function P(G)A=inf(z=size(G));A(1)=G(1);for k=G(:)'B=im2col(padarray(A,[1,1],inf),[3,3])+G(:)';B(5,:)-=G(:)';A=reshape(min(B),z);end,A(end)

使用图像处理包,因为为什么不呢?难道不是每个人都解决图形问题吗?

在线尝试!

爆炸了

function P(G)
   A=inf(z=size(G));         % Initialize distance array to all Inf
   A(1)=G(1);                % Make A(1) = cost of start cell
   for k=G(:)'               % For a really long time...
      B=im2col(padarray(A,[1,1],inf),[3,3])+G(:)';
       %  B=padarray(A,[1,1],inf);     % Add border of Inf around distance array
       %  B=im2col(B,[3,3]);           % Turn each 3x3 neighborhood into a column
       %  B=B+G(:)';                   % Add the weights to each row
      B(5,:)-=G(:)';         % Subtract the weights from center of neighborhood
      A=reshape(min(B),z);   % Take minimum columnwise and reshape to original
   end
   A(end)                    % Display cost of getting to last cell

说明

给定一系列权重:

7   12    6    2    4
5   13    3   11    1
4    7    2    9    3
4    2   12   13    4
9    2    7    9    4

初始化成本数组,以使到达每个元素的成本为无穷大,起始点(左上方的元素)的权重等于其权重。

  7   Inf   Inf   Inf   Inf
Inf   Inf   Inf   Inf   Inf
Inf   Inf   Inf   Inf   Inf
Inf   Inf   Inf   Inf   Inf
Inf   Inf   Inf   Inf   Inf

这是迭代0。对于每个后续迭代,到达单元的成本设置为以下最小值:

  • 达到该要素的当前成本,以及
  • 到达元素邻居的当前成本+元素的重量

第一次迭代后,元素(2,2)的路径成本(使用基于1的索引)将为

minimum([  7   Inf   Inf]   [13  13  13]) = 20
        [Inf   Inf   Inf] + [13   0  13]
        [Inf   Inf   Inf]   [13  13  13]

第一次迭代后的完整成本数组为:

  7    19   Inf   Inf   Inf
 12    20   Inf   Inf   Inf
Inf   Inf   Inf   Inf   Inf
Inf   Inf   Inf   Inf   Inf
Inf   Inf   Inf   Inf   Inf

迭代之后k,每个元素从一开始就最多k采取步骤便是达到该元素的最低成本。例如,可以分两步(迭代)到达(3,3)处的元素,费用为22:

  7    19    25   Inf   Inf
 12    20    22   Inf   Inf
 16    19    22   Inf   Inf
Inf   Inf   Inf   Inf   Inf
Inf   Inf   Inf   Inf   Inf

但是在第4次迭代中,找到了4个步骤的路径,成本为20:

 7   19   25   24   28
12   20   22   32   25
16   19   20   30   34
20   18   30   34   35
27   20   25   40   39

由于通过mxn矩阵的任何路径都不能长于矩阵中元素的数量(作为非常宽松的上限),因此在m*n迭代之后,每个元素都将包含从最开始到达该元素的最短路径的成本。


由-1之间去除空间字节while~
notjagan

@notjagan我从切换whilefor,仍然可以使用您的小费。谢谢!
烧杯

5

JavaScript,197个字节

a=>(v=a.map(x=>x.map(_=>1/0)),v[0][0]=a[0][0],q=[...(a+'')].map(_=>v=v.map((l,y)=>l.map((c,x)=>Math.min(c,...[...'012345678'].map(c=>a[y][x]+((v[y+(c/3|0)-1]||[])[x+c%3-1]||1/0)))))),v.pop().pop())

美化:

a=>(
  // v is a matrix holds minimal distance to the left top
  v=a.map(x=>x.map(_=>1/0)),
  v[0][0]=a[0][0],
  q=[
     // iterate more than width * height times to ensure the answer is correct
    ...(a+'')
  ].map(_=>
    v=v.map((l,y)=>
      l.map((c,x)=>
        // update each cell
        Math.min(c,...[...'012345678'].map(
          c=>a[y][x]+((v[y+(c/3|0)-1]||[])[x+c%3-1]||1/0)
        ))
      )
    )
  ),
  // get result at right bottom
  v.pop().pop()
)

4

Mathematica 279字节

基本的想法是创建与对应于矩阵条目顶点和任意两个顶点之间的有向边分离通过的曲线ChessboardDistance大于0但小于或等于1。顺便说一下,这种情况被称为一个景图形,因为它对应于国王在棋盘上的有效举动。

FindShortestPath然后用于获取最小路径。它适用于EdgeWeight,而不适用VertexWeight,因此有一些额外的代码将定义EdgeWeight为对应于每个有向边的目的地的矩阵条目。

码:

(m=Flatten[#];d=Dimensions@#;s=Range[Times@@d];e=Select[Tuples[s,2],0<ChessboardDistance@@(#/.Thread[s->({Ceiling[#/d[[1]]],Mod[#,d[[1]],1]}&/@s)])≤1&];Tr[FindShortestPath[Graph[s,#[[1]]->#[[2]]&/@e,EdgeWeight->(Last@#&/@Map[Extract[m,#]&,e,{2}])],1,Last@s]/.Thread[s->m]])&

请注意,该字符是转置符号。它将按原样粘贴到Mathematica中。

用法:

%@{{2, 55, 5, 3, 1, 1, 4, 1},
  {2, 56, 1, 99, 99, 99, 99, 5},
  {3, 57, 5, 2, 2, 2, 99, 1},
  {3, 58, 4, 2, 8, 1, 99, 2},
  {4, 65, 66, 67, 68, 3, 99, 3},
  {2, 5, 4, 3, 3, 4, 99, 5},
  {75, 76, 77, 78, 79, 80, 81, 2},
  {5, 4, 5, 1, 1, 3, 3, 2}}

输出:

67

如果进行设置g=Graph[...,GraphLayout->{"GridEmbedding","Dimension"->d},VertexLabels->Thread[s->m]p=FindShortestPath[...则以下图形将直观地显示解决方案(矩阵的顶部对应于图形的底部):

HighlightGraph[g,PathGraph[p,Thread[Most@p->Rest@p]]]

在此处输入图片说明


3

Haskell,228个字节

位置是两个元素的列表,因为它们很容易生成,sequence并且像2元组一样容易进行模式匹配。

h=g[[-1,-1]]
g t@(p:r)c|p==m=0|1<2=minimum$(sum$concat c):(\q@[a,b]->c!!a!!b+g(q:t)c)#(f(e$s$(\x->[0..x])#m)$f(not.e t)$zipWith(+)p#s[[-1..1],[-1..1]])where m=[l(c)-1,l(head c)-1]
(#)=map
f=filter
e=flip elem
s=sequence
l=length

开始-1,-1并计算每个步骤目标字段的成本。

可选的前两行:从0,0,开始,计算出发字段,在等于矩阵尺寸的坐标处终止(因此从目标向右下方,需要将其添加到合法目的地列表中)-长度完全相同,但速度较慢:

j=i[[0,0]]
i t@(p@[a,b]:r)c|p==m=0|1<2=c!!a!!b+(minimum$(sum$concat c):(\q->i(q:t)c)#(f(e$m:(s$(\x->[0..x-1])#m))$f(not.e t)$zipWith(+)p#s[[-1..1],[-1..1]]))where m=[l c,l$head c]

使用infix map不会在此处保存字节,但我会在不花一分钱的情况下立即替换它,因为它只能通过更多的使用(有时还要进行其他重组)来更好地使用,这会消除另一对括号。

有待改进:冗余filters。合并/在衬里他们filter(flip elem$(s$(\x->[0..x])#m)\\p)import Data.List\\费用3个字节。

另外,太糟糕的(fromEnumTo 0)是比延长2个字节(\x->[0..x])

sum$concat c是所有字段的成本的总和,因此,路径成本的明晰上限是minimum避免给空列表使用的(我的类型检查器已经确定了要在Integers 上处理的整个事情,因此无需硬编码最大值) , 呵呵)。不管我如何基于前一个步骤限制步骤(这将大大加快算法的运行速度,而且还增加了字节的开销),我也无法避免导致这种后退的死胡同。

  • 一个过滤器的想法是((not.e n).zipWith(-)(head r))与提取n=s[[-1..1],[-1..1]],这需要添加,[-1,-1]到初始路径。然后,该算法避免在上一步中将其移动到可能已经到达的位置,这使得在与该边缘正交的边缘场上步进成为死角。

  • 另一个是((>=0).sum.z(*)d)extracting z=zipWith,它d为递归函数引入了新的参数,如(z(-)p q)在递归和[1,1]初始情况下一样。该算法避免了标量积为负的连续步骤(d即上一步),这意味着没有45°的急转弯。这仍然大大缩小了选择范围,并避免了以前的琐碎死角,但是仍然有一些路径最终被封闭在已经访问过的字段中(并且可能会有“转义”,但可能会急转弯)。


3

Python 2,356320字节

s=input()
r=lambda x:[x-1,x,x+1][-x-2:]
w=lambda z:[z+[(x,y)]for x in r(z[-1][0])for y in r(z[-1][1])if x<len(s)>0==((x,y)in z)<len(s[0])>y]
l=len(s)-1,len(s[0])-1
f=lambda x:all(l in y for y in x)and x or f([a for b in[l in z and[z]or w(z)for z in x]for a in b])
print min(sum(s[a][b]for(a,b)in x)for x in f([[(0,0)]]))

在这里尝试!

-36字节,感谢notjagan

接收列表列表作为输入,并在从左上角到右下角导航矩阵时输出最低成本。

说明

从矩阵的左上角到右下角找到所有可能的路线,为每个路线创建一个x,y坐标列表。路线无法回溯,它们必须在处结束(len(s)-1,len(s[0])-1)

将每个坐标路径上的整数求和,然后返回最小成本。

print可以很容易地改变输出的最短路线坐标列表。


-36字节,具有一些其他更改。
notjagan

@notjagan很大的变化,特别是or有条件的使用。谢谢!
修复

1

APL(Dyalog Classic),33字节

{⊃⌽,(⊢⌊⍵+(⍉3⌊/⊣/,⊢,⊢/)⍣2)⍣≡+\+⍀⍵}

在线尝试!

{ } 带参数的功能

+\+⍀⍵ 按行和按列取部分和,以建立路径距离的悲观上限

( )⍣≡ 重复直到收敛:

  • (⍉3⌊/⊣/,⊢,⊢/)⍣2到邻居的距离的最小值,即执行两次(( )⍣2):在最左边的列(⊣/,)之前添加到self()并追加最右边的列(,⊢/),在水平三元组(3⌊/)中找到最小值并转置(

  • ⍵+ 将每个节点的值加上其到邻居的最小距离

  • ⊢⌊ 尝试击败目前最好的距离

⊃⌽, 最后,返回右下角的单元格

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.