如何解决有没有办法将基于矩阵的图形表示转换为 OCaml 中的邻接表之类的东西?
通常,在编程面试中,您会遇到一个问题,要求您使用诸如 problem on LeetCode 之类的 DFS 或 BFS 遍历图形的二维矩阵表示。不幸的是,当您遇到节点补丁时,循环遍历元素并运行 dfs 的典型算法很难在功能上实现。我想知道是否有一种简单的方法可以将 2D 矩阵转换为 OCaml 中的邻接列表表示,以便函数算法可以获得更有效的解决方案。
解决方法
不幸的是,在遇到节点补丁时循环元素和运行 dfs 的典型算法很难在功能上实现。
相反,它们实施起来非常容易和自然。让我们展示一下。 LeetCode 的输入表示为,
grid = [
["1","1","0"],["1","0",["0","0"]
]
直接映射到 OCaml 为
let grid = [
["1";"1";"1";"1";"0"];
["1";"1";"0";"1";"0"];
["1";"1";"0";"0";"0"];
["0";"0";"0";"0";"0"]
]
唯一的语法区别是您必须使用 ;
而不是 ,
作为分隔符。
OCaml 中的迭代,以及一般的函数式语言,都是使用迭代器来表达的。迭代器是一个高阶函数,它接受一个容器和另一个函数,并将这个函数应用于该容器的每个元素。因此,迭代不需要特殊的语言结构,例如 for
或 while
1。
迭代器大致可以分为两组:文件夹和映射器。文件夹将函数应用于每个元素并累积结果。映射器创建一个新的数据结构,其中每个元素都是原始元素的映射(转换)。不映射每个元素的映射器,即产生可能小于输入的输出数据结构,称为过滤器。最后,每个映射器都可以(通常是)使用文件夹实现,因此文件夹是最通用的迭代形式。
现在,当我们掌握了这些知识后,我们可以查看 List 以了解 iterators 提供了什么。由于我们需要找到孤立岛的数量,因此输入表示不是最优的,我们需要将其转换为更合适的东西。从图论的角度来看,一个岛是 a connected component,我们需要将我们的土地划分为一组岛屿。显然,我们的输入数据结构不是很适合,因为它不允许我们有效地查询一个元素是否连接到其他元素。单链表中的所有查询都是线性的,因为它们必须从第一个元素到最后一个元素。因此,我们需要找到一种更好的数据结构来表示我们的世界地理信息。
由于我们只对 is_land
感兴趣,因此有效的表示是一组位置,其中每个位置都表示为 x 和 y 坐标,
type pos = {x : int; y : int}
让我们为仓位类型定义一个模块,并在其中添加一些有用的功能,
module Pos = struct
type t = pos
let compare = compare (* use structural compare *)
let zero = {x=0; y=0}
let north p = {p with y = p.y - 1}
let south p = {p with y = p.y + 1}
let east p = {p with x = p.x + 1}
let west p = {p with x = p.x - 1}
end
最后,我们准备将世界定义为一组位置,
module World = Set.Make(Pos)
现在我们可以迭代我们的矩阵并创建一个世界,
let is_land = function "1" -> true | _ -> false
let make_world input : World.t =
snd @@
List.fold_left (fun (pos,map) ->
List.fold_left (fun ({x;y},map) piece ->
if is_land piece
then ({x=x+1; y},World.add {x;y} map)
else ({x=x+1; y},map))
({x=1; y = pos.y + 1},map))
({Pos.zero with x = 1},World.empty)
input
了解我们如何使用 List 迭代器对矩阵执行迭代。
接下来,我们将实现 union-find 算法将世界划分为一组岛屿。让我们自上而下地开发它。 union-find 算法将一组元素划分为一组不相交的集合(通俗地称为 quotient set)。我们的初始集合是 World.t
,它是所有土地位置的集合。对于每个位置,我们需要找到该位置 is_connected
所在的岛屿列表。我们现在需要精确定义 is_connected
的含义。就我们世界的几何形状而言,位于 pos
的一块土地与一个岛屿 island
相连,如果它属于 island
或者它的任何邻居属于 island
{1}},其中 neighbors
的 pos
是,
let neighbors pos = [
Pos.north pos;
Pos.south pos;
Pos.east pos;
Pos.west pos;
]
所以 is_connected
是使用 List.exists
迭代器定义的,
let is_connected pos island =
List.exists (fun x -> World.mem x island)
(pos :: neighbors pos)
现在,我们可以编写一个函数,将岛的商集划分为一块土地所属的岛集和不与该块连接的岛集。使用 List.partition
迭代器很容易实现,
let find_islands pos =
List.partition (is_connected pos)
如果一个元素属于几个岛,那么就意味着它是一个连接元素,一个链接,连接了在我们发现这个元素之前认为是断开的几个岛。我们需要一个将一个岛的几个部分连接成一个岛的函数。同样,我们可以使用 List.fold_left
,
let union = List.fold_left World.union World.empty
现在,我们拥有了所有必要的构建元素来找到我们的主要算法,
let islands world =
World.fold (fun pos islands ->
let found,islands = find_islands pos islands in
World.add pos (union found) :: islands)
world []
让我们重申它的实现。对于我们世界的每一部分,我们将我们最初的一组岛屿(以空集开始)划分为这部分所属和不属于的岛屿。然后我们union
找到找到的岛屿,并将当前碎片添加到新形成的岛屿,并将该岛屿添加到岛屿集合中。
注意,在函数式编程语言中实现 union-find 是多么简单!
岛屿的数量显然是我们分区的基数,例如,
let number_of_islands world = List.length (islands world)
最后,solve
函数接受指定形式的输入并返回岛屿的数量,定义为,
let solve input = number_of_islands (make_world input)
我们来玩玩吧,
# solve [
["1";"1";"1";"0";"0"];
["1";"1";"0";"1";"0"];
["1";"1";"0";"0";"0"];
["0";"0";"0";"0";"1"]
];;
- : int = 3
# solve [
["1";"1";"1";"1";"0"];
["1";"1";"0";"1";"0"];
["1";"1";"0";"0";"0"];
["0";"0";"0";"0";"1"]
];;
- : int = 2
# solve [
["1";"1";"1";"1";"0"];
["1";"1";"0";"1";"1"];
["1";"1";"0";"0";"1"];
["0";"0";"0";"0";"1"]
];;
- : int = 1
#
看起来不错!但是如果它从一开始就不起作用怎么办?我们需要调试它。在函数式编程中,调试很容易,因为您可以独立调试每个小函数。但为此,您需要能够打印您的数据,而我们的 World.t
是一种抽象数据类型,打印为 <abstr>
。为了能够打印它,我们需要定义一些打印机,例如,
let pp_pos ppf {x; y} = Format.fprintf ppf "(%d,%d)" x y
let pp_comma ppf () = Format.fprintf ppf ","
let pp_positions ppf world =
Format.pp_print_list ~pp_sep:pp_comma pp_pos ppf
(World.elements world)
let pp_world ppf world =
Format.fprintf ppf "{%a}" pp_positions world
现在我们可以安装它(我假设您正在使用 ocaml
或 utop
解释器运行这个程序),现在我们可以看到我们的算法如何将我们的世界划分为岛屿,>
# #install_printer pp_world;;
# islands @@ make_world [
["1";"1";"1";"0";"0"];
["1";"1";"0";"1";"0"];
["1";"1";"0";"0";"0"];
["0";"0";"0";"0";"1"]
];;
- : World.t list =
[{(5,4)}; {(4,2)}; {(1,1),(1,2),3),(2,(3,1)}]
1) 我们仍然在 OCaml 中使用它们,但很少使用。
,我猜你会喜欢这样的:
let to_adjacency_list m =
(* Create an array of the size of the matrix with empty lists as values *)
let ml = Array.init (Array.length m) (fun _ -> []) in
(* For each cell (i,j) that contains true,append j to the list at index i *)
Array.iteri
(fun i a -> Array.iteri (fun j v -> if v then ml.(i) <- j :: ml.(i)) a)
m;
(* Reverse all the created lists for readability and convert the array to a list *)
Array.to_list (Array.map List.rev ml)
如果我将其应用于矩阵:
# let m =
[|
[| true; true; true; true; false |];
[| true; true; false; true; false |];
[| true; true; false; false; false |];
[| false; false; false; false; false |];
|] in to_adjacency_list m;;
- : int list list = [[0; 1; 2; 3]; [0; 1; 3]; [0; 1]; []]
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。