【问题标题】:Greedy algorithm to find potential weighted activities in order?贪心算法按顺序找到潜在的加权活动?
【发布时间】:2014-01-02 05:04:33
【问题描述】:

例如,假设有一个活动列表 {a, b, c, d, e, f, g}

a & b 各得 9 分。 c 值 8 分。 d & e 各得 6 分。 f 值 5 分。 g 值 4 分。

活动列表已按点数降序排列。

我想找到满足某些要求(例如 F(X) = true)的三个活动的最高分组合(我们称之为组合 X)。

F(X) 只接受三个活动的组合,不能修改。

如何在不必先计算所有可能的组合的情况下生成 X?
如何迭代所有可能的组合以减少总分?

我希望能够找到最高点组合,测试一下。如果失败,则生成第二高点组合等。

示例列表只有几个项目。但是,实际列表可能会变得非常大,生成所有组合是不切实际的。

我应该怎么做?

【问题讨论】:

  • 什么是 F(X) ?没有它,恐怕你无法以任何方式做出非暴力解决方案
  • 我不是说找到非暴力解决方案,但我想按点的顺序输入组合。所以首先测试 {a, b, c} 然后尝试 {a, b, d} 等。
  • 您正在尝试迭代所有可能的组合以减少总分。我编辑了您的问题以反映这一点。
  • 谢谢,我想这两个回答不理解我的部分是我的错:(
  • F(X) 不能确定每个组合的值。它只返回真或假,然而,F(X) 的重要属性是它只接受一组三个活动。每个活动都已经有一个权重,如果不先计算每个组合,就没有办法按降序输出组合吗?获得 MAX 很容易。我们只选择列表中的前三个项目。

标签: algorithm optimization


【解决方案1】:

下面的想法只解决了问题的一个更简单的版本,但也许它可以扩展到更强大的解决方案。

令 [a(1)..a(N)] 为输入数组。 我在这里建议的是一种在完整枚举中从 C(N,3)~N^3 个中枚举前 N^(1/3) 个三元组的方法。我知道这是适度的,但它保证了 O(N) 时间和空间

  1. s = a(1) + a(2) + a(N^(1/3))
  2. T = [a(1),a(2), .. ,a(N^(1/3))] 中的所有三元组(占用 O(N) 时间和空间)
  3. 对 T 进行降序排列(时间复杂度:O(N^(1/3) * log N) = O(N) )
  4. 迭代 T 并返回每个三元组 r 而 sum(r) >= s

说明:

在 (1) 中,我们为不只涉及 T = [a(1)..a(N^(1/3))] 的项目的三元组计算可能的最高分。换句话说,T 已经包含了所有 score > s 的三元组。因此,我们在 T 中生成所有三元组,对它们进行排序,然后只返回我们确定的那些(即 score>=s 的那些)。将返回多少个这样的三胞胎?好吧,这取决于数组值,但我们可以保证至少 N^(1/3) - 2 个三元组,因为 2<i<=N^(1/3) 的所有三元组 [a(1)+a(2)+a(i)] 都有总和 >=s。在实践中,“好”三元组的数量可能要高得多,但这同样取决于数组编号分布。

【讨论】:

  • 这只是简单地创建和排序由活动列表的前三分之一组成的三元组吗?对不起,我想我很困惑。感谢您抽出宝贵时间并消除其他答案中的误解。
  • @EssamAl-Mansouri:不是第三个——而是列表中的一小部分——其中有 N^(1/3) 个项目(即长度的立方根)。仅对这小部分产生的三元组进行排序允许返回至少前 N^(1/3) 个三元组,并且它只使用 O(N) 空间,而完全排序则需要 O(N^3) 空间。
  • @EssamAl-Mansouri:例如,如果输入列表的长度为 10^9,则取前 1000 个项目,生成 166,167,000 个三元组,按降序对它们进行排序,然后开始返回三元组,直到您遇到的总和低于 a(1)+a(2)+a(1000)。
  • 哦,我明白了。是的,我知道那将如何工作。有没有一种方法可以解决这个问题而无需先存储 166m 三元组?这可能有点过分了。
  • @EssamAl-Mansouri:这取决于您需要多少结果。如果前 100 个结果足够,请使用前 100 个项目,它们仅产生 161,700 个三元组。一个额外的优化是仅当它的总和大于界限 s(在这种情况下为 a(1)+a(2)+a(100))时才创建一个三元组。这样三元组数组就更短了。
【解决方案2】:

这是生成三个有序组合的方法:-

    for(int i=0;i<Set.size();i++) {

       for(int j=i+1;j<Set.size();j++) {

           for(int k=j+1;k<Set.size();k++) {

                   if(F(Set[i],Set[j],Set[k])) {
                       // your code to find max
                   }  
           }
       }

}

【讨论】:

  • 这仍然会生成所有可能的三个组合。我需要按特定顺序生成它们。从多到少。
  • @EssamAl-Mansouri {a,b,c,d} 会怎样?
  • {a, b, c, d} 甚至不应该放在首位。那是4个活动。我正在寻找三个项目组合。如何迭代所有可能的三项组合以降低总分?
  • @EssamAl-Mansouri 由于三的所有组合都是唯一的,因此三的所有可能组合都将满足该约束
  • @VikramBhat:我认为解决方案不会因为重复值而失败。考虑以下示例:{10,9,6,2,1,0}。三元组 {10,1,0} 在您的迭代顺序中早于 {9,6,2},但总和较低。
【解决方案3】:

我认为没有很好的解决方案可以满足您的要求。这是我能想到的最好(也是最差)的解决方案:生成所有组合,将它们放在一个向量中然后排序!复杂度为O(n^3 log(n^3))。我认为通过假设在同一位置多次具有相同值没有意义,因为它会生成一个已经生成的组合,我认为只能实现一点点改进。

c++实现:

#include <stdio.h>
#include <algorithm>
#include <vector>
using namespace std;

struct Combin {
    int a, b, c, sum;
};

bool comp(Combin a, Combin b) {
    return a.sum>b.sum;
}

int main() {
    vector<int> act;
    int n;
    while(scanf("%d", &n)==1 && n) {
        act.push_back(n);
    }

    vector<Combin>combs;

    for (int i=0; i<act.size(); i++) {
        if (i>0 && act[i-1]==act[i]) continue;
        for (int j=i+1; j<act.size(); j++) {
            if (j>i+1 && act[j-1]==act[j]) continue;
            for (int k=j+1; k<act.size(); k++) {
                if (k>j+1 && act[k-1]==act[k]) continue;
                    Combin comb;
                    comb.a=i; comb.b=j; comb.c=k;
                    comb.sum = act[i]+act[j]+act[k];
                    combs.push_back(comb);
            }
        }
    }

    sort(combs.begin(), combs.end(), comp);

    for (int i=0; i<combs.size(); i++) {
        printf("(%d,%d,%d)=%d\n", act[combs[i].a], act[combs[i].b], act[combs[i].c], combs[i].sum);
    }

    return 0;
}

【讨论】:

  • 所有可能的三活动组合只有(n 选择 3)不是 n!。我了解您要对嵌套循环执行的操作,但它太慢了。那是我尝试的第一件事,除了j的初始值是j = i + 1,而k是j + 1。然而,只有1000个活动,n^3是10亿,n选择3仍然是166百万,虽然好很多,但还是不够快。
  • 实际上我回答这个问题是因为我不清楚你所说的所有组合是什么意思,无论如何,你似乎需要一个比 O(n^3) 更好的解决方案,但是可以做多少改进将是高度取决于您的函数 F(X),因此描述该函数如何验证组合将帮助您获得更好的答案。
  • 为了不太具体,这个问题对其他人没有帮助,您可以放心地假设 F(X) 基本上是用户输入。用户必须决定这是否适合他们。
  • 如果用户函数过于狂野,没有贪婪的余地,用户规则集可以是“'a'不能与'b'在同一个集合中”,“' c' 和 'd' 应该总是在一起”或“两个数字的总和必须大于第三个”等等。
  • 我不确定你的意思,我会尝试用假设的情况来解释。假设有 10,000 种不同的活动。这些活动并非都是平等的,有些很有价值,有些则很少。用户看不到活动的价值,他只能说他喜欢不喜欢。我们正在寻找最有价值且用户喜欢的组合(即 F(X) 为真)。另外,你能解释一下“太狂野”是什么意思吗?
【解决方案4】:

我记得 Python 的组合生成器倾向于保持其输入的 LHS-to-RHS 从最小到最大变化,并首先输出 LHS 项。

如果你使用 Pythons 生成组合的方法,它的docs 状态如下:

def combinations(iterable, r):
    # combinations('ABCD', 2) --> AB AC AD BC BD CD
    # combinations(range(4), 3) --> 012 013 023 123
    pool = tuple(iterable)
    n = len(pool)
    if r > n:
        return
    indices = range(r)
    yield tuple(pool[i] for i in indices)
    while True:
        for i in reversed(range(r)):
            if indices[i] != i + n - r:
                break
        else:
            return
        indices[i] += 1
        for j in range(i+1, r):
            indices[j] = indices[j-1] + 1
        yield tuple(pool[i] for i in indices)

然后你可以给你的物品按重量递减顺序的功能来生成按重量递减的物品:

for x in combinations('abcdef', 3): 
    print(x)

('a', 'b', 'c')
('a', 'b', 'd')
('a', 'b', 'e')
('a', 'b', 'f')
('a', 'c', 'd')
('a', 'c', 'e')
('a', 'c', 'f')
('a', 'd', 'e')
('a', 'd', 'f')
('a', 'e', 'f')
('b', 'c', 'd')
('b', 'c', 'e')
('b', 'c', 'f')
('b', 'd', 'e')
('b', 'd', 'f')
('b', 'e', 'f')
('c', 'd', 'e')
('c', 'd', 'f')
('c', 'e', 'f')
('d', 'e', 'f')

注意:正如 Essam 在 cmets 中指出的,combinations(data, 3) 等价于 comb3(data) 其中:

def comb3(data):
    lendata = len(data)
    for i in range(lendata):
        for j in range(i+1, lendata):
            for k in range(j+1, lendata):
                yield (data[i], data[j], data[k])

【讨论】:

  • 这不就相当于for (i = 0, i &lt; list.size; i++){ for (j = i+1; j &lt; list.size; j++){ for (k = j+1; k &lt; list.size; k++){ //do stuff } } }
  • @EssamAl-Mansouri:是的 - 在前面添加了一个注释来回答。
  • 这不允许我按总分的顺序迭代三项组合:(
  • 怎么样?它对我有用。您能否举一个具体的例子说明您失败的原因-也许是沟通不畅?如果 'abcdef' 是按重量降序排列的项目,则返回的三元组将按总重量降序排列。
  • 嗯,我明白你的意思了。您可以选择 1:接受部分排序。 2:使用系列,但丢弃比前一个更大的三元组。 3:使用我正在研究的下一个食谱。
【解决方案5】:

我的其他答案只是按特定顺序使用生成的组合并没有给出完全排序的答案。事实上,他们将是 35 次中的 6 次,其中下一个三倍的总数上升而不是下降。

如果我们使用这些组合但将它们放在一个固定最大大小的堆中,那么我们可以用最大堆大小来换取排序的准确性,如下所示:

from itertools import combinations
from heapq import heappush, heappop, heappushpop

BESTOFLAST = 10     # max heap size

item2points = dict(a=9, b=9, c=8, d=6, e=6, f=5, g=4)

def partially_ordered_triples(item2points, BESTOFLAST=BESTOFLAST):
    ordereditems = sorted(item2points.keys(), 
                        key=lambda item: item2points[item],
                        reverse=True)
    #print(ordereditems)    # ['a', 'b', 'c', 'e', 'd', 'f', 'g']

    triples = combinations(ordereditems, 3)

    heap = []   # Empty heap

    # Preload heap
    for i in range(BESTOFLAST):
        triple = triples.next()
        total = sum(item2points[item] for item in triple)
        heappush(heap, (-total, triple))    # minheap so -total

    # load/remove from heap in partially sorted order    
    for triple in triples:
        total = sum(item2points[item] for item in triple)
        thistotal, thistriple = heappushpop(heap, (-total, triple))
        yield thistriple, -thistotal

    # drain rest of heap
    while heap:
        thistotal, thistriple = heappop(heap)
        yield thistriple, -thistotal

if __name__ == '__main__':
    for heapsize in range(BESTOFLAST + 1):
        print('Using a heap of size: %i and skipping:' % heapsize)
        length = skipped = 0
        previoustotal = sum(item2points.values())   # Starting high value
        for triple, newtotal in partially_ordered_triples(item2points, heapsize):
            if newtotal > previoustotal: skipped += 1
            length += 1
            previoustotal = newtotal

        print(" of %i triples, %i were skipped to keep the total count decreasing" % (length, skipped))

如果堆的大小足够大,那么就不会偏离所需的顺序。如果太小,则偏差的数量会增加:

输出:

Using a heap of size: 0 and skipping:
 of 35 triples, 6 were skipped to keep the total count decreasing
Using a heap of size: 1 and skipping:
 of 35 triples, 4 were skipped to keep the total count decreasing
Using a heap of size: 2 and skipping:
 of 35 triples, 4 were skipped to keep the total count decreasing
Using a heap of size: 3 and skipping:
 of 35 triples, 3 were skipped to keep the total count decreasing
Using a heap of size: 4 and skipping:
 of 35 triples, 2 were skipped to keep the total count decreasing
Using a heap of size: 5 and skipping:
 of 35 triples, 2 were skipped to keep the total count decreasing
Using a heap of size: 6 and skipping:
 of 35 triples, 1 were skipped to keep the total count decreasing
Using a heap of size: 7 and skipping:
 of 35 triples, 1 were skipped to keep the total count decreasing
Using a heap of size: 8 and skipping:
 of 35 triples, 1 were skipped to keep the total count decreasing
Using a heap of size: 9 and skipping:
 of 35 triples, 0 were skipped to keep the total count decreasing
Using a heap of size: 10 and skipping:
 of 35 triples, 0 were skipped to keep the total count decreasing

【讨论】:

  • @EssamAl-Mansouri:反馈?附:你还记得接受你的问题的答案之一吗?谢谢。
  • 随着堆中“look-ahead”大小的增加,准确度也会增加——这很明显。但是,缺少的是一种设置合适堆大小的方法(作为输入数组长度 N 的函数),这样就不会发生迭代错误。如果你能证明堆大小是 O(N),那么这个选项很有趣。所需的堆大小还取决于插入逻辑。您如何扫描项目以创建三胞胎?
  • 将 heapsize 设置为 N-1 以保证总点数的降序。您将不得不对样本数据进行试验,看看您可以接受什么样的权衡。如果没有,那么它等同于生成和排序所有三元组,因此不同的排序例程可能会更好。
  • “将 heapsize 设置为 N-1 以保证总点数的降序” - 是什么让您这么认为?如果 N 是项目数,则三元组的数量与 N^3 成正比。如果你说的是真的(这取决于你在堆中插入东西的顺序),那么它需要一个证明。
  • @Paddy3118:对不起朋友,我才看到这个答案。您的建议的问题在于“组合(ordereditems,3)”单独成本太高。有了abcdefg之类的示例列表,就可以了。但是,例如,当您将该列表列出 150 个元素时,已经计算出超过 500,000 个可能的组合。正如主帖所说,在这些情况下首先计算所有可能的组合是不切实际的。
猜你喜欢
  • 1970-01-01
  • 2014-07-20
  • 2023-04-02
  • 2022-12-07
  • 2019-04-27
  • 1970-01-01
  • 1970-01-01
  • 2013-01-20
  • 1970-01-01
相关资源
最近更新 更多