【问题标题】:How do I make my implementation of greedy set cover faster?如何更快地实现贪婪集覆盖?
【发布时间】:2011-12-18 01:03:19
【问题描述】:

在对我的原始问题here 进行了多次讨论后,我想出了以下贪婪套装封面的实现。根据我收到的帮助,我将问题编码为“Greedy Set Cover”,在收到更多帮助here 后,我提出了以下实现。我感谢大家帮助我解决这个问题。以下实现工作正常,但我想让它可扩展/更快。

通过可扩展/更快,我的意思是说:

  • 我的数据集在 S 中包含大约 50K-100K 集
  • U本身的元素数量非常少,在100-500的数量级
  • S 中每个集合的大小可以是 0 到 40 之间的任意值

这是我的尝试:

U = set([1,2,3,4])
R = U
S = [set([1,2]), 
     set([1]), 
     set([1,2,3]), 
     set([1]), 
     set([3,4]), 
     set([4]), 
     set([1,2]), 
     set([3,4]), 
     set([1,2,3,4])]
w = [1, 1, 2, 2, 2, 3, 3, 4, 4]

C = []
costs = []

def findMin(S, R):
    minCost = 99999.0
    minElement = -1
    for i, s in enumerate(S):
        try:
            cost = w[i]/(len(s.intersection(R)))
            if cost < minCost:
                minCost = cost
                minElement = i
        except:
            # Division by zero, ignore
            pass
    return S[minElement], w[minElement]

while len(R) != 0:
    S_i, cost = findMin(S, R)
    C.append(S_i)
    R = R.difference(S_i)
    costs.append(cost)

print "Cover: ", C
print "Total Cost: ", sum(costs), costs

我不是 Python 方面的专家,但对这段代码进行任何特定于 Python 的优化都会非常好。

【问题讨论】:

    标签: python performance algorithm optimization scalability


    【解决方案1】:

    当我implemented Matlab 中著名的用于设置覆盖(无权重)的贪心算法时,我使用了一个技巧。您可以以某种方式将此技巧扩展到加权情况,使用 set cardinality / set weight 而不是 set cardinality。此外,如果您使用 NumPy 库,将 Matlab 代码导出到 Python 应该非常容易。

    这是诀窍:

    1. (可选)我根据基数(即它们包含的元素数量)按降序对集合进行了排序。我还存储了它们的基数。
    2. 我选择了一个集合 S,在我的实现中它是最大的(即列表的第一个集合),我计算它包含多少未覆盖的元素。假设它包含 n 个未覆盖的元素。
    3. 因为现在我知道有一个集合 S 带有 n 个未覆盖的元素,所以我不需要处理所有基数低于 n 元素,因为它们不能比 S 更好。所以我只需要在基数至少n的集合中寻找最优集合;通过我的排序,我们可以轻松地专注于它们。

    【讨论】:

      【解决方案2】:

      你得到的时间和你需要的时间是什么时候?当然大部分执行时间都花在了 C 级代码查找集合交集上,所以您可以做的优化不多吗?使用 100000 个集合的一些随机数据(结果可能会因您的数据而异,不确定这些是否是好的值),每组 40 个元素,500 个唯一元素,权重从 1 到 10 随机,

      print 'generating test data'    
      num_sets = 100000
      set_size = 40
      elements = range(500)
      U = set(elements)
      R = U
      S = []
      for i in range(num_sets):
          random.shuffle(elements)
          S.append(set(elements[:set_size]))
      w = [random.randint(1,100) for i in xrange(100)]
      
      C = []
      costs = []
      

      我使用 cProfile 获得了这样的性能:

               8200209 function calls in 14.391 CPU seconds
      
         Ordered by: standard name
      
         ncalls  tottime  percall  cumtime  percall filename:lineno(function)
              1    0.000    0.000   14.391   14.391 <string>:1(<module>)
             41    4.802    0.117   14.389    0.351 test.py:23(findMin)
              1    0.001    0.001   14.391   14.391 test.py:40(func)
        4100042    0.428    0.000    0.428    0.000 {len}
             82    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
             41    0.001    0.000    0.001    0.000 {method 'difference' of 'set' objects}
              1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        4100000    9.160    0.000    9.160    0.000 {method 'intersection' of 'set' objects}
      

      嗯,所以很明显 1/3 的时间不在固定的十字路口。但我个人不会再优化了,尤其是以清晰度为代价。剩下的 2/3 将无能为力,何必呢?

      【讨论】:

      • +1 谢谢。你说的对。我一直在寻找不必要的优化。就我而言,它在大约 15 秒内运行,这对我有好处。再次感谢您。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多