树与加权节点一起走

时间:2015-09-25 20:20:49

标签: algorithm tree

给定一个树,其中节点具有值(可以是负数),找到最大化路径中节点总和的路径。

我尝试使用动态编程技术来获得解决方案,但无法突破。

2 个答案:

答案 0 :(得分:0)

我们将在树上执行泛洪填充算法,从每个叶子开始,并在洪水填充期间在我们发现的路径上运行maximum subsequence sum算法。这为每对叶子产生候选最大值;我们选择其中最大的作为我们的真实答案。这个解决方案需要O(n ^ 2)时间:有O(n)个叶子,我们做O(n)工作来在每个叶子上做最大子序列和问题的泛洪填充版本,产生O(n) ^ 2)候选生产阶段的工作总计。然后我们做O(n ^ 2)工作来选择我们生成的O(n ^ 2)候选者的最大路径。

我将在Haskell中给出一个示例实现。您可以忽略一些导入:

import Data.List
import Data.Monoid

为简单起见,我们将表示我们的树作为函数,给定一个节点,它告诉权重和可用的邻居。命名类型和术语时,我会使用w表示权重,n表示节点。

type Tree w n = n -> (w, [n])

有一种方法可以引用路径及其权重:

type WeightedPath w n = (Sum w, [n])

在我们的行走过程中,我们将保留两个统计数据,即在当前节点结束的最大路径,以及到目前为止我们看到的最大路径。对于空树对应的“空摘要”,我们也有一个常量:没有路径,没有权重。

data Summary w n = Summary
    { endingHere     :: WeightedPath w n
    , endingAnywhere :: WeightedPath w n
    }

emptySummary :: Num w => Summary w n
emptySummary = Summary mempty mempty

我们需要一个泛洪填充算法。这很简单,虽然它是通过“初始摘要”参数化的,并且是通过一个额外加权节点扩展摘要的方法;这是因为我们将使用泛洪填充算法来查找所有叶子并找到候选路径。我们需要注意的一个技巧是确保我们不回溯;为此,我们在nPrevious中跟踪上一个节点(如果有)。

flood :: Eq n => (w -> n -> s -> s) -> s -> Tree w n -> n -> [s]
flood extend summary t = go Nothing summary where
    go nPrevious summary nCurrent = case maybe id delete nPrevious ns of
        [] -> [summary']
        ns -> ns >>= go (Just nCurrent) summary'
        where
        (w, ns)  = t nCurrent
        summary' = extend w nCurrent summary

例如,我们可以通过使“摘要”成为我们看到的最后一个节点来找到所有叶子。如果树是空的,我们会厌恶地举手:

findLeaves :: Eq n => Tree w n -> n -> [n]
findLeaves = flood (\w n s -> n) undefined

我们可以将路径候选摘要扩展为一个节点,如链接算法:

extend :: (Num w, Ord w, Ord n) => w -> n -> Summary w n -> Summary w n
extend w n s = answer where
    answer = s
        { endingHere = max (0, []) ((Sum w, [n]) <> endingHere s)
        , endingAnywhere = max (endingHere answer) (endingAnywhere s)
        }

这个函数很好地插入到我们的泛洪填充算法中,以便从特定节点开始寻找候选路径:

findCandidates :: (Num w, Ord w, Ord n) => Tree w n -> n -> [Summary w n]
findCandidates t n = findLeaves t n >>= flood extend emptySummary t

顶级功能只进行泛洪填充并选择最大候选者。 (关系是任意打破的,因为它们在extend中。)

maximalPath :: (Num w, Ord w, Ord n) => Tree w n -> n -> WeightedPath w n
maximalPath t n = maximum . map endingAnywhere $ findCandidates t n

让我们看看它的运行情况。我们将定义一个非常简单的样本树:它是一个星形树,其中心有节点0,节点15各自连接到中心集线器。该中心的权重为-1,根据它们所在的节点,叶子的权重为15

sampleTree :: Tree Int Int
sampleTree 0 = (-1, [1..5])
sampleTree n = (n, [0])

在ghci中,我们现在可以通过提供树和树中的任何节点来找到最大路径:

> maximalPath sampleTree 0
(Sum {getSum = 8},[5,0,4])

...表示最大的和为8,可以通过从外部节点5到中心节点0,然后向外到节点4的路径来实现。

答案 1 :(得分:0)

在树的每个节点上计算:

1)该节点的任何后代到该节点的总和最大的路径。

2)具有最大总和的路径,可以在以该节点为根的子树中找到。

这两个都可以使用在该节点的子节点计算的值来计算。

在情况(1)中,答案是在该节点处开始和停止的0长度路径,或者该节点处的值与任何子节点处的最大(1)值之和。

在情况(2)中,答案是该节点的两个最大(1)值的总和,减去该节点的值,或者来自任何子节点的最大值(2)。

您要查找的值是根目录下的(2)值,您可以添加额外的簿记来计算所涉及的路径。

这里的成本基本上是递归遍历树的成本,当你从深度优先搜索返回时完成大部分工作,所以我认为它是O(n)