我一直在研究一种使用Haskell的抽象国际象棋算法(试图扩展我对不同范式的理解),并且我遇到了一个我几周来一直在思考的挑战。
问题在于:
给定一个板(由整数列表表示;每个 整数表示后续点值),维度为n x n, 确定提供最多点的路径。如果有平局 为了获得最佳路径,请返回其中任何一个。
以下是具体细节:
A = [[5,4,3,1],[10,2,1,0],[0,1,2,0],[2,3,4,20]]
呈现为:
R1: 5 4 3 1, R2: 10 2 1 0, R3: 0 1 2 0, R4: 2 3 4 20.
规则是:
您可以从第一行的任何位置开始
您可以一次移动一个方格,可以是直下,左下(对角线),也可以是右下(对角线)。
输出必须是整数元组。
第一个元素是表示列与行的列表,第二个元素是总点数。例如。对于上面的板,最好的解决方案是从左上角(5)行进并沿对角线行进剩余的步骤(直到20点的正方形)。这将导致元组([1,2,3,4], 29)
。
请记住,这一切都在Haskell中,因此它是一个功能范式的递归问题。起初,我正在考虑使用贪婪算法,即选择r1中的最高值,并通过比较接下来的3种可能性进行递归;选择3中的最高值。然而,垮台是贪婪算法无法在下一行之前看到潜力。
我该怎么做?我不是在寻找代码本身,因为我喜欢自己解决问题。但是,非常感谢伪代码或一些算法指导!
答案 0 :(得分:3)
保留刚刚到达的行中每列的每个列的路径列表,其中包含该单元格的最高分数。
您将使用列表
开始(在您的示例中)[([1],5), ([2],4), ([3],3), ([4],1)]
然后,当检查下一行时,对于每一列,您选择上一行中可以到达该列的得分最高的路径,在这里,对于第二行,在第1列和第2列中,您选择在上面一行的第1列中结束的路径,在第3列中,您将选择在第4列中的第2列中结束的路径,在第4列中,路径以上一行中的第3列结尾,这样就可以你
[([1,1],15), ([1,2],7), ([2,3],5), ([3,4],3)]
对于第三行[0,1,2,0]
,您将再次选择前两列中以第1列结尾的路径,第三列中的路径以第2列结束,第3列中的路径以第3列结尾第四,
[([1,1,1],15), ([1,1,2],16), ([1,2,3],9), ([2,3,4],5)]
对于第四行[2,3,4,20]
,您将选择前三列中以第2列结尾的路径,以及最后一列中以第3列结尾的路径,
[([1,1,2,1],18), ([1,1,2,2],19), ([1,1,2,3],20), ([1,2,3,4],29)]
然后,当您到达最后一行时,选择总数最高的路径。
为什么会这样:
让得分最高的路径在专栏c
中结束。最后一列上方的部分必须是在倒数第二行的一列c-1, c, c+1
中结束的最高得分路径,因为最后一行中的列c
只能从那些列中到达。
答案 1 :(得分:3)
我在同一主题上看到了您之前的问题,我开始研究它 由于您不想直接解决方案,我可以向您提供我对您的问题的反思,我想它可以帮助您。
一些基本属性:
1.移动的数量总是与列表的长度相比 m =长度A
2.起始点的数量是列表头部长度的长度 n =长度(头部A)
3.目前的立场永远不会消极,那么:
- 如果当前位置为0,您可以向下或向右
- 否则你可以去左,右或右
这导致我们使用这个伪代码
generate_path :: [[Int]] -> [[Int]]
generate_path [] = [[]]
generate_path A = ... -- You have to put something here
where
m = length A
n = length (head A)
这件事看起来像这个
move pos0 count0
| count0 == 0 =
| pos0 == 0 = move (down count) ++ move (right count)
| otherwise = move (left count) ++ move (down count) ++ move (right count)
where
count = count0 - 1
down = position0
left = position0 - 1
right = position0 + 1
事实上,记住所有这些并添加(!!)运算符,我们不应该是解决方案的目标。说服你使用 A + list comprehension + !! ,作为
[A !! x !! y | x <- [1..2], y <- [0..2]] -- I take random range
或者使用其他版本:
[[A !! x !! y | x <- [1..2]] | y <- [0..2]]] -- I take random range
实际上你有两个递归主要工作在参数n = length(头A),你重复相同的动作从0到(n-1)at(n-1)检索结果,这个递归嵌入另一个在m上工作,从0到(m-1)重复相同的动作。
希望有所帮助。 祝你好运。
答案 2 :(得分:3)
最好的解决方案不是自上而下的贪婪算法,而是从最后一行开始并开始工作的方法:
import Data.Function
import Data.List
-- All elements of Board are lists of equal lengths
-- valid b = 1 == length (group (map length b))
type Value = Int
type Board = [[Value]]
type Index = Int
type Result = ([Index], Value)
p :: Board
p = [[5,4,3,1],[10,2,1,0],[0,1,2,0],[2,3,4,20]]
best_from :: Board -> Result
best_from [] = undefined
best_from xs | any null xs = undefined
best_from b = best_of . best_list $ b
best_list :: Board -> [Result]
best_list b = foldr1 layer (map label b)
where label = zipWith (\index value -> ([index],value)) [1..]
layer new rest = zipWith (\(i1,v1) (i2,v2) -> (i1++i2, v1+v2)) new best
where temp = head rest : map best_pair (zip rest (tail rest))
best = map best_pair (zip temp (tail rest)) ++ [last temp]
best_pair :: (Result,Result) -> Result
best_pair (a@(_,a1), b@(_,b1)) | a1 >=b1 = a
| otherwise = b
best_of :: [Result] -> Result
best_of = maximumBy (compare `on` snd)
main = do
print (best_from p)
如果有一行,很容易解决。因此,这会使用简单的[#]解决方案路径将每行转换为Result列表。
鉴于rest
行下面的puzzel new
,然后添加new
行就是从best
找到rest
解决方案(按检查,向左,向右,并与new
行结合。
这会使foldr
或foldr1
成为自然结构。
答案 3 :(得分:0)
我选择了不同的路径,没有双关语。我列出了允许的索引组合并将其映射到它们。也许有人可以找到一种方法将它推广到任何规模的电路板上。
import Data.List
import Data.Ord
import Data.Maybe
a = [[5,4,3,1],[10,2,1,0],[0,1,2,0],[2,3,4,20]]
r1 = a !! 0
r2 = a !! 1
r3 = a !! 2
r4 = a !! 3
i = [0,1,2,3]
index_combinations = [[a,b,c,d] | a <- i, b <- i, c <- i, d <- i,
abs (b-a) < 2, abs (c-b) < 2, abs (d-c) < 2]
mapR xs = [r1 !! (xs !! 0), r2 !! (xs !! 1),
r3 !! (xs !! 2), r4 !! (xs !! 3)]
r_combinations = map mapR index_combinations
r_combinations_summed = zip r_combinations $ map (foldr (+) 0) r_combinations
result = maximumBy (comparing snd) r_combinations_summed
path = index_combinations !! fromJust (elemIndex result r_combinations_summed)