Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/340.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 树上的有效等式函数_Python_Algorithm_Tree_Time Complexity_Sml - Fatal编程技术网

Python 树上的有效等式函数

Python 树上的有效等式函数,python,algorithm,tree,time-complexity,sml,Python,Algorithm,Tree,Time Complexity,Sml,几天前,我被问到以下面试问题。它是用标准的ML代码描述的,但我可以自由地用我选择的语言回答(我选择了Python): 我有一种类型: datatype t = Leaf of int | Node of (t * t) 还有一个带有签名的函数,f val f: int -> t 您需要编写一个函数equals,用于检查两棵树 他们是平等的f是O(n),它做的是“最坏的可能” 您的函数的时间复杂性等于函数的时间复杂性。写 等于,这样它在n上就永远不会是指数型的 f 提供的f示例

几天前,我被问到以下面试问题。它是用标准的ML代码描述的,但我可以自由地用我选择的语言回答(我选择了Python):

我有一种类型:

datatype t 
  = Leaf of int
  | Node of (t * t)
还有一个带有签名的函数,
f

val f: int -> t
您需要编写一个函数
equals
,用于检查两棵树 他们是平等的
f
O(n)
,它做的是“最坏的可能” 您的
函数的时间复杂性等于
函数的时间复杂性。写
等于
,这样它在
n
上就永远不会是指数型的
f

提供的
f
示例如下:

fun f n = 
  if n = 0 then 
    Leaf(0)
  else 
    let 
      val subtree = f (n - 1) 
    in
      Node (subtree, subtree)
    end
它在
O(n)
时间内生成一个指数级大的树,因此
等于(f(n),f(n))
对于简单的
equals
实现,它在树的节点数上是线性的
O(2^n)

我制作了这样的东西:

class Node:
    def __init__(self, left, right):
        self.left = left
        self.right = right

class Leaf:
    def __init__(self, value):
        self.value = value

def equals(left, right):
    if left is right:
        return True
    try:
        return left.value == right.value 
    except ValueError:
        pass
    try:
        return equals(left.left, right.left) and equals(left.right, right.right)
    except ValueError:
        return False
cache = {}
def equals(left, right):
    try:
        return cache[(left, right)]
    except KeyError:
        pass

    result = False
    try:
        result = left.value == right.value 
    except ValueError:
        pass
    try:
        left_result = equals(left.left, right.left) 
        right_result = equals(left.right, right.right)
        cache[(left.left, right.left)] = left_result
        cache[(left.right, right.right)] = right_result
        result = left_result and right_result
    except ValueError:
        pass

    cache[(left, right)] = result
    return result
这在面试官提供的
f
的例子中起了作用,但在一般情况下失败了“
f
做了最坏的事情。”他提供了一个我不记得的例子,打破了我的第一次尝试。我犹豫了一会儿,最终做了一件像这样的东西:

class Node:
    def __init__(self, left, right):
        self.left = left
        self.right = right

class Leaf:
    def __init__(self, value):
        self.value = value

def equals(left, right):
    if left is right:
        return True
    try:
        return left.value == right.value 
    except ValueError:
        pass
    try:
        return equals(left.left, right.left) and equals(left.right, right.right)
    except ValueError:
        return False
cache = {}
def equals(left, right):
    try:
        return cache[(left, right)]
    except KeyError:
        pass

    result = False
    try:
        result = left.value == right.value 
    except ValueError:
        pass
    try:
        left_result = equals(left.left, right.left) 
        right_result = equals(left.right, right.right)
        cache[(left.left, right.left)] = left_result
        cache[(left.right, right.right)] = right_result
        result = left_result and right_result
    except ValueError:
        pass

    cache[(left, right)] = result
    return result
但我觉得这是一个尴尬的黑客,显然不是面试官想要的。我怀疑有一种优雅的方法可以避免重新计算子树——它是什么?

从外观上看,您的解决方案是O(n^2)。我们可以通过对一棵树(而不是一对树)的身份进行记忆,使其成为O(n):

memoByVal = {}
memoByRef = {id(None): 0}
nextId = 1

# produce an integer that represents the tree's content
def getTreeId(tree):
  if id(tree) in memoByRef:
    return memoByRef[id(tree)]
  # nodes are represented by the (left, right, value) combination
  # let's assume that leafs just have left == right == None
  l, r = getTreeId(tree.left), getTreeId(tree.right)
  if (l, r, tree.value) not in memoByVal:
    memoByVal[l, r, tree.value] = nextId
    nextId += 1
  res = memoByVal[l, r, tree.value]
  memoByRef[id(tree)] = res
  return res

# this is now trivial
def equals(a, b):
  return getTreeId(a) == getTreeId(b)
您可以使用在线性时间内创建两个树的副本,然后在恒定时间内比较它们是否相等

下面是一个在sml中考虑哈希的示例

更新:


见评论。我回答得太匆忙了。我认为不可能在线性时间内创建复制副本。你需要从hash consed类型开始,并且只在f中使用那些构造函数。

记忆化也是我想到的第一件事。等等,你应该得到等号的次线性时间?我认为这在一般情况下是不可能的。有没有限制
f(n)
最多使用O(n)个唯一节点,以便在最坏的情况下,记忆实际上可以帮助您?例如,如果
f
返回一个严格的、经过充分评估的树(由于时间限制)@NiklasB,则会出现这种情况。限制是
f(n)
O(n)
,我认为这意味着它最多只能产生
O(n)
唯一的节点。@PatrickCollins如果返回的树被完全计算了,顺便说一句,我认为在最坏的情况下,最终的解决方案是O(n^2),而不是O(n)。这在SML中是不可能的。没有id函数。您不能仅仅获取任意值的内存位置,这将破坏类型抽象。@seanmcl我被允许使用我选择的语言。但是考虑到面试官是一名标准的ML程序员,我假设他想要的解决方案可以在标准ML中实现。在引用透明的语言中@seanmcl,这项任务是不可能解决的。OP可以使用任何语言though@NiklasB:你忘了返回
res
@beroal谢谢,我修好了!采访从一些我认为与哈希表无关的问题开始。这听起来像是票。我将等待一段时间来接受答案,但我怀疑你已经得到了答案。这不是总节点数的线性关系吗,它可以是n的指数?我不太明白如何在给定的类型上有效地实现这一点,而不使用较低级别(引用透明性破坏)的黑客。SML不是引用透明的。它有状态,例如refs和hashtables。@Niklas:你说得对,但我不清楚如何将t类型的给定值转换为hash consed变量,而不使用模式匹配遍历节点,如果尝试相等(f(N),f(N)),这将产生指数时间。我相信你需要换t型。