左派堆两个版本创建实现

时间:2015-10-14 21:05:05

标签: data-structures functional-programming ocaml leftist-tree

最近,我正在读“Purely-functional-data-structures”一书 当我参加“练习3.2直接定义插入而不是通过调用合并”为Leftist_tree.I实现我的版本插入。

 let rec insert x t =
try
  match t with
| E -> T (1, x, E, E)
| T (_, y, left, right ) ->
  match (Elem.compare x y) with
  | n when n < 0 -> makeT x left (insert y right)
  | 0 -> raise Same_elem
  | _ -> makeT y left (insert x right)
with
     Same_elem -> t

为了验证它是否有效,我测试它和本书提供的合并功能。

 let rec merge m n = match (m, n) with
| (h, E) -> h
| (E, h) -> h 
| (T (_, x, a1, b1) as h1, (T (_, y, a2, b2) as h2)) ->
  if (Elem.compare x y) < 0
  then makeT x a1 (merge b1 h2)
  else makeT y a2 (merge b2 h1)

然后我发现了一件有趣的事情。 我使用列表["a";"b";"d";"g";"z";"e";"c"]作为输入来创建此树。而且这两个结果是不同的。 对于合并方法,我得到了这样一棵树:

leftist-tree-by-merge

和我实现的插入方法给我一个这样的树:

leftist-tree-by-insert

我认为这两种方法之间存在一些细节,即使我遵循'merge'的实现来设计'insert'版本。但后来我尝试了一个列表逆[“c”;“e”;“z”;“g”;“d”;“b”;“a”]给了我两个左派树插入树即可。这让我非常困惑,以至于我不知道我的插入方法是错误还是正确。所以现在我有两个问题:

  1. 如果我的插入方法错了?
  2. leftist-tree-by-merge leftist-tree-by-insert 相同的结构?我的意思是这个结果给了我一种幻觉,就像它们在某种意义上是平等的一样。
  3. 整个代码

    module type Comparable = sig
        type t
        val compare : t -> t -> int
    end
    
    module LeftistHeap(Elem:Comparable) = struct
        exception Empty
        exception Same_elem
    
        type heap = E | T of int * Elem.t * heap * heap  
    
        let rank = function 
           | E -> 0
           | T (r ,_ ,_ ,_ ) -> r
    
        let makeT x a b =
           if rank a >= rank b
           then T(rank b + 1, x, a, b)
           else T(rank a + 1, x, b, a)
    
        let rec merge m n = match (m, n) with
           | (h, E) -> h
           | (E, h) -> h 
           | (T (_, x, a1, b1) as h1, (T (_, y, a2, b2) as h2)) ->
           if (Elem.compare x y) < 0
           then makeT x a1 (merge b1 h2)
           else makeT y a2 (merge b2 h1)
    
        let insert_merge x h = merge (T (1, x, E, E)) h
    
        let rec insert x t =
        try
            match t with
            | E -> T (1, x, E, E)
            | T (_, y, left, right ) ->
            match (Elem.compare x y) with
            | n when n < 0 -> makeT x left (insert y right)
            | 0 -> raise Same_elem
            | _ -> makeT y left (insert x right)
        with
            Same_elem -> t
    
        let rec creat_l_heap f = function 
            | [] -> E
            | h::t -> (f h (creat_l_heap f t))
    
        let create_merge l = creat_l_heap insert_merge l 
        let create_insert l = creat_l_heap insert l
    end;;
    
    module IntLeftTree = LeftistHeap(String);;
    
    open IntLeftTree;;
    
    let l = ["a";"b";"d";"g";"z";"e";"c"];;
    let lh = create_merge `enter code here`l;;
    let li = create_insert l;;
    
    let h = ["c";"e";"z";"g";"d";"b";"a"];;
    let hh = create_merge h;;
    let hi = create_insert h;;
    

    16。 2015年10月更新

    通过更精确地观察两个实现,很容易发现差异包括合并基础树T (1, x, E, E)或插入元素x我使用的图表可以更清晰地表达。

    enter image description here

    所以我发现我的插入版本将总是使用更多的复杂性来完成他的工作并且不利用左派树的优势,或者它总是在更糟糕的情况下工作,即使这个树结构完全是“左派”。

    如果我改变了一小部分,这两个代码将获得相同的结果。

    let rec insert x t =
      try
      match t with
      | E -> T (1, x, E, E)
      | T (_, y, left, right ) ->
        match (Elem.compare x y) with
        | n when n < 0 -> makeT x E t
        | 0 -> raise Same_elem
        | _ -> makeT y left (insert x right)
    with
      Same_elem -> t
    

    因此,对于我的第一个问题:我认为答案并不准确。它可以真正构建一个左派树,但总是在糟糕的情况下工作。

    第二个问题有点无意义(我不确定)。但这种情况仍然很有趣。例如,即使合并版本工作效率更高,但是从列表构造树而不需要像我提到的那样插入顺序([“a”;“b”;“d”;“g”;“z”; “e”;“c”],[“c”;“e”;“z”;“g”;“d”;“b”;“a”],如果订单不重要,对我来说我认为它们是相同的集合。)合并功能无法选择更好的解决方案。 (我认为树的结构为[“a”;“b”;“d”;“g”;“z”;“e”;“c”]优于[“c”;“e”;“ z“;”g“;”d“;”b“;”a“] s)

    所以现在我的问题是:

    1. 是每个右下方脊柱为空的树结构是一个很好的结构吗?
    2. 如果是,我们是否可以按任何输入顺序构建它?

1 个答案:

答案 0 :(得分:0)

每个子右脊柱为空的树只是一个列表。因此,这样一个简单的列表是一个更好的列表结构。运行时属性将与列表相同,这意味着插入将花费O(n)时间而不是所需的O(log n)时间。

对于树,您通常需要一个平衡的树,一个节点的所有子节点理想地具有相同的大小。在您的代码中,每个节点都有rank,目标是在每个节点的左侧和右侧具有相同的排名。如果树中没有完全2^n - 1条目,则这是不可能的,并且您必须允许树中的某些不平衡。通常允许等级为1或2的差异。插入应该在较小的一侧插入元素,并且删除必须重新平衡超过允许的等级差异的任何节点。这使树保持合理平衡,确保保留所需的运行时属性。

检查您的教科书在您的案例中允许的等级差异。