【问题标题】:How to get dot product of two sparsevectors in O(m+n) , where m and n are the number of elements in both vectors如何在 O(m+n) 中获得两个稀疏向量的点积,其中 m 和 n 是两个向量中的元素数
【发布时间】:2015-12-21 13:58:09
【问题描述】:

我有两个稀疏向量 X 和 Y,想要得到 O(m+n) 中的点积,其中 m 和 n 是 X 和 Y 中非零元素的数量。我能想到的唯一方法是选取向量 X 中的每个元素并遍历向量 Y 以查找是否存在具有相同索引的元素。但这需要 O(m * n)。我将向量实现为链表,每个节点都有一个元素。

【问题讨论】:

  • 列表是否已经按索引排序?
  • @A.S.H m 和 n 不是向量的大小,而是每个向量中非零元素的数量
  • 是的,我在编辑后看到了。但是,答案仍然假设列表按索引排序。我认为这需要明确说明,
  • 如果您担心性能,我建议从链表切换到数组。
  • @stgatilov 好的,我明白你的意思了。我理解您的评论是指数组中的幼稚存储,这将导致存储大量零。

标签: algorithm big-o linear-algebra


【解决方案1】:

如果你的向量被存储为一个元组的链表,每个元组包含索引和非零元素的值并按索引排序,你就可以做到这一点。

您可以通过从向量中选择您位于较低索引处的下一个元素来遍历这两个向量。如果索引相同,则将元素相乘并存储结果。

重复直到一个列表到达末尾。

由于每个列表中的每个非零元素都有一个步骤,因此根据需要复杂度为 O(m+n)。

脚注:数据结构不必是链表,但必须提供 O(1) 的方式来访问下一个非 0 元素及其索引。

【讨论】:

  • @A.S.H 我试图在我的编辑中解释它,但对我来说似乎很明显它是 O(m+n)
  • 但是如果我的向量比另一个向量的元素多怎么办?例如 X = [(15,3.0), (1500,3.14) , (15000, 3.141), (150000, 3.1415)] 和 Y = [(15,1.0), (150000,1.0)] 我会用完遍历 X 时 Y 中的元素
  • @Jens,我同意,但您假设向量列表按索引排序。 OP没有明确说明这一点:)
  • 我看不出这是 O(m+N)。如何在具有不同数量的非零元素的向量上完成?您不能同时遍历两者
  • @Altaïr 您只会在当前位于较低索引处的列表中前进,直到到达其中一个列表的末尾。
【解决方案2】:

排序列表

鉴于您的非零元素按两个向量中的坐标索引排序,它是通过 merge 算法实现的。那是计算机科学中的一种标准算法,它将两个排序后的序列合并为一个排序后的序列,它的工作时间O(M + N)

有两种方法可以做到这一点。第一个是检查合并中的相等元素。而且确实是最好的办法。

第二种方式是先合并,然后检查equals(那么它们必须是连续的):

std::pair<int, double> vecA[n], vecB[m], vecBoth[n+m];
std::merge(vecA, vecA+n, vecB, vecB+m, vecBoth);
double dotP = 0.0;
for (int i = 0; i+1 < n+m; i++)
  if (vecBoth[i].first == vecBoth[i+1].first)
    dotP += vecBoth[i].second * vecBoth[i+1].second;

std::merge 的复杂度为 O(M + N)

上面的示例假设数据存储在数组中(这是稀疏向量和矩阵的最佳选择)。如果你想使用链表,你也可以在O(M + N)时间内进行merge,见this question

未排序的列表

即使您的列表未排序,您仍然可以在 O(M + N) 时间内执行点积。思路是先将A的所有元素放入hash表,然后遍历B的元素,看看hash中是否有相同索引的元素。 p>

如果索引非常大(例如超过百万),那么也许您真的应该使用非平凡的散列函数。但是,如果您的索引相当小,那么您可以避免使用散列函数。只需使用大小大于向量维度的数组即可。为了快速清除这个数组,你可以使用“generations”的技巧。

//global data! must be threadlocal in case of concurrent access
double elemsTable[1<<20];
int whenUsed[1<<20] = {0};
int usedGeneration = 0;

double CalcDotProduct(std::pair<int, double> vecA[n], vecB[m]) {
  usedGeneration++;   //clear used array in O(1)
  for (int i = 0; i < n; i++) {
    elemsTable[vecA[i].first] = vecA[i].second;
    whenUsed[vecA[i].first] = usedGeneration;
  }
  double dotP = 0.0;
  for (int i = 0; i < m; i++)
    if (whenUsed[vecB[i].first] == usedGeneration)
      dotP += elemsTable[vecB[i].first] * vecB[i].second;
  return dotP;
}

请注意,您可能需要每十亿点积清除一次whenUsed

【讨论】:

  • 我们还没有在课堂上介绍合并算法。您之前的一个人建议我同时遍历两个向量并检查索引是否相等。如果它们将值相乘并存储结果。但我不知道这将如何工作。如果我有两个元素数量不同的向量怎么办。例如 X = [(15,3.0), (1500,3.14) , (15000, 3.141), (150000, 3.1415)] 和 Y = [(15,1.0), (150000,1.0)] 我会用完遍历 X 时 Y 中的元素,我将出现空指针异常。对不对?
  • @Altaïr:由于merge是众所周知的算法,你可以自己找解释。这里是similar question,这里是algolist article,这里是some question,等等。你可以在合并排序的上下文中看到一些代码here,它显示了当一个序列首先结束时要做什么。附言为未排序的情况添加了解决方案。
【解决方案3】:
 Use Map to store each vector.
 Each entry of map has index as key and value as the vector value at the particular index. Insert only the non zero values 
 Iterate on one map and for each entry check whether the particular key is present in the other map.If yes update the product else ignore the current key
 Time Complexity :  n -> vector size
                   O(n) - for map construction 
                   O(n) - for iteration 
 Space Complexity :  O(n) - for maps

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多