用于存储帕累托最优解的有效结构

Efficient structure for storing pareto optimal solutions

本文关键字:有效 结构 存储 用于      更新时间:2023-10-16

我正试图解决在计算过程中需要存储帕累托最优解的问题。我将把帕累托最优解集合称为一个袋子。

到目前为止,我只有两个标准,这允许基于数组的非常有效的解决方案,其中元素根据第一个标准按降序排序,根据第二个标准按升序排序。这种阵列的一个例子是:

[(100, 0), (50, 1), (-10, 3)]

(关于帕累托最优-维基)

然而,最近我发现我需要添加第三个标准,对于这样的扩展,上述方法似乎不适用。我试着在谷歌上搜索是否有人已经解决了这个问题,但没有发现令人满意的结果。也许我问错了谷歌的问题。

更确切地说,我需要什么:能够存储相互不占主导地位的帕累托最优元素的结构。我需要在结构中插入元素,并且需要对元素进行迭代,但没有特定的顺序。在我的情况下,通常不会有超过4-5个元素,但有时会更多,达到10-20个。在我的算法中,插入包的情况经常发生,所以我需要它们尽可能快。

该应用程序是用C++编写的,但它可能并不真正相关。

任何帮助都将不胜感激。

编辑:我已经有了自己的一些想法——将元素排列成某种三角形结构,但我无法将这个想法形式化。

第2版:请注意,我要求在每次插入后,结构中只保留相互不占主导地位的元素。例如,如果我有一组非支配三元组{(1,2,3), (3, 1, 1)},加上三元组(3, 3, 3),我将得到集合{(3,3,3)}

Edit3:为了更清楚地了解元素的显性-我们说,在这个特殊的情况下,三重(a,b,c)显性(e,f,g)当且仅当a >= e && b >= f && c >= g和至少一个不等式是严格的->

首先是三元方法,这样我们就可以看到我们试图改进和验证的内容,这个答案实际上与问题有关。

// Taken from the question and translated.
// Is the dominance condition.
let x_doms_y x y =
    let (a,b,c) = x
    let (e,f,g) = y
    a >= e && b >= f && c >= g &&
    (a > e || b > f || c > g)

Naive方法需要O(n)测试,以便过滤掉数据集中的现有元素,这些元素由要添加的新项目主导。下面显示了天真的O(n)解,随后我们尝试对其进行改进。

type Triplet = int * int * int
type TripletSet = Triplet list
module Naive =
    let insert (t : Triplet) (tset : TripletSet) : TripletSet =
        t :: (tset|> List.filter (fun u -> not (x_doms_y t u)))

从一个空列表开始,然后在下一个列表后添加一个三元组:

let foo =
[] |> show
|> Naive.insert (1,2,3) |> show
|> Naive.insert (3,1,1) |> show
|> Naive.insert (3,3,3) |> show
> []
  [(1, 2, 3)]
  [(3, 1, 1); (1, 2, 3)]
  [(3, 3, 3)]

这似乎符合预期。

为了提高速度,除了所选数据结构的插入成本外这里不考虑,但这可能是相关的,我们试图将优势比较的数量减少到<n.至少平均而言。

这个问题可以从几何意义上解释。三元组,例如1,2,3,可以看作是从位于0,0,0的立方体的一端到其对角的向量。

一个体积较小的立方体能支配一个体积较大的立方体吗?答案是否定的。我们可以在一维等价物上进行类比来证明这一点。如果x<y、 x不能支配y,因为要支配,它应该持有x >= y && X > y

对于二维,可以设想类似的等价性。它对我们的三胞胎也有同样的意义。

现在,我们缩小了搜索空间。现有集合中体积小于新三元组的那些项目可以是,但不必由新三元组支配。那些体积比新的三重态更大的不能被支配。

因此,一种改进的方法是:

  • 设qset是已插入的四元组的排序序列
  • 设vt是新三元组t:(a,b,c)的体积。vt=a*b*c
  • 设qt=(vt,a,b,c)
  • 使用二进制搜索在qset中按音量查找索引位置
  • pos(带有v<vt)左边的所有四边形都是筛选的候选者
  • pos右侧的所有四边形都不能被支配,因为它们更大
  • 因此,现在,我们只需要将朴素的方法应用于子集qset[0..pos-1]。如果插入的值对于这个关系是随机的,那么平均而言,我们只需过滤n/2,其中n是qset的大小
  • 将位置处的qt插入qset并返回qset

例如,您可以根据它们的范数对它们进行排序(如a*a + b*b + c*c),然后如果它们主导新元素,则只需要检查具有较大范数的元素;如果它们被新元素主导,则只需检查具有较小范数的元素。

然而,我不确定元素的排序是否有特别的价值,如果你一开始只有这么少的元素。维持这种秩序(无论是什么)都有其自身的开销,而且在算法复杂性方面可能远远超过你所获得的任何好处。特别是,我不会做任何涉及动态每个元素分配和释放的事情,比如使用标准分配器的std::liststd::map。即使是数组上的堆也可能不会带来明显的优势。

我可能只会使用一个简单的、未排序的std::vector:

std::vector<Element> frontier;
void insert(const Element& newElement) {
    if (std::none_of(frontier.begin(), frontier.end(),  [&](const auto& e) {return dominates(e, newElement); })) {
        frontier.erase(std::remove_if(frontier.begin(), frontier.end(), [&](const auto& e) {return dominates(newElement, e); }),frontier.end());
        frontier.push_back(newElement);     
    }
}

第一个想法可能是以某种方式使用带有比较函数的std::set,该函数返回一个元素是否由另一个元素Pareto支配。但因为(至少在C++中)元素等价性是通过反射性地比较元素来确定的,这意味着没有支配关系的元素对将被视为等价的,所以你无法将它们添加到集合中。一种方法可能是使用multiset,然后在添加新元素之前先删除所有"较小"的元素。但我怀疑复杂性不会比vector更好,因为每次寻找主导元素时,所有元素都是"等效的"。

无论如何,我只是想指出,使用帕累托优势作为直接进入set的比较函数是行不通的。真正最好的解决方案可能需要更专业的数据结构。在你的情况下,有一种快速检查每个维度主导地位的方法似乎真的很合适,就像你最初只在两个维度上做的那样。每个维度都有一个索引,以加快检查速度。然后,对于元素,你可以有一个反向索引来快速删除它。但如果大多数时候你只是进行检查而不是修改集合,那么这就没有必要了。

如果我正确理解了这个问题,那么您正在为整数的三元组寻找合适的字典顺序。然而,目前尚不清楚您为什么希望以排序的方式存储Pareto边界,因为您表示希望不按特定顺序迭代。也许set(在标准库中实现)已经足够了。

天真的解决方案(C++’ish伪代码):

我们将元素存储在向量CCD_ 20中。然后插入可能看起来像这样:

void insert(const auto& e) {
    for (size_t i = 0; i < vec.size(); ++i) {
        if (e.dominates(vec.at(i))) {
            remove(vec.at(i));
        } else if (vec.at(i).dominates(e)) {
            return;
        }
    }
    vec.append(e);
}

这个代码必须经过更精细的处理才能有效地删除元素,但我怀疑这不是我们能得到的最好的,因为我们必须总是为每个元素调用dominates(如果插入的元素没有被支配),而在我的解决方案中,我有点不得不检查集合中已经被支配的第一个元素,其余的都被很好地消除了。