如何在常量时间从稀疏表中删除行/列?

时间:2013-10-10 17:15:34

标签: algorithm data-structures

我有一个非常稀疏填充的表,其中巨大的维度

,即我的表的索引可能非常大,但表中的元素数量非常少。

我一直在考虑这个数据结构。

我排除了一个rows x cols表,因为它需要太多的内存和太多的时间来查找行/列中的所有元素。

相反,我考虑过使用两张地图:rowscols

让我们看看rows。键是行索引,键k的值是行k中所有元素的列号列表。

示例(1表示元素存在):

0 1 0
1 0 1

将是rows地图:

0: [1]
1: [0, 2]

我会保留一个类似的cols地图,其中键是列号,键k的值是列k中所有元素的行号列表。< / p>

当我想在表格中删除行k时,我会这样做: del rows[k]

但这不会从cols地图中删除顶点。 我将不得不遍历删除了一些元素的所有列,并从cols地图中删除每个元素。

O(1)方法吗?

5 个答案:

答案 0 :(得分:2)

非常不正统的方法是将矩阵实现为 k d树, k = 2。您可以通过访问与该行或列相交的所有单元格来删除行或列;如果矩阵是正方形并且它有n个非零项,我相信你需要检查的平均细胞数是sqrt(n)。 (我已经在Stackoverflow上的一些答案中写了一个这样的证明 - 如果你需要的话,我可以查一下。)

以非常相似的方式,你可以使用四叉树;我理解这些术语的方式不同之处在于,单元边界是预先定义的,总是将x和y范围切割成四边形中的一半(对于每个非叶子中的四个相同的子单元),而节点确定 k d-tree中的边界(对于每个非叶子中的两个不相同的子单元)。

我认为对于这两个版本,此解决方案的性能取决于复杂方式的稀疏性。首先,如果数据真的非常稀疏,例如,每行/每列的非零条目的平均数远小于1,则此解决方案将比您建议的更加节省内存,但可能更少时间效率。如果非零条目的数量是条目总数的一小部分c,并且您的矩阵为m * k,则此解决方案可能更有效:对于您的解决方案,删除您需要的一列平均更改c*m行列表。对于每个这样的行列表,您需要找到列所在的位置并移动其后的所有条目,一个向前移动。这是每行平均c*k/2 = O(k)条目,总共c^2*m*k/2 = O(m*k)次操作。我们会n = c*m*k,因此平均操作总数为c*n/2 = O(n),而此处提出的解决方案为O(sqrt(n))。类似地,如果您假设矩阵大致为方形,例如m*m,并且每个行/列平均具有f(m)个非零条目(因此n = f(m)*m),则操作数为{ {1}}为您的解决方案,O(f(m)^2)为此解决方案;这意味着,如果O(sqrt(m*f(m))),此解决方案会更好。 (请注意,这是一个lower case omega;它基本上意味着,f(m) = ω(m^(1/3))渐近增长的速度比f(m)快,例如m^(1/3)sqrt(m)。)

(我假设c*m地图的每个条目都是解决方案中的数组;链表会给出相同的复杂性,因为在列表中找到正确的列需要线性时间。你可以做假设每个行和列不是由数组表示,而是由自平衡树表示 - 那么你可以为每一行rows操作和O(log(k))总计,矩阵离正方形并不太远。仍然不比这个树业更好,但如果你真的需要最好的表现,那么在实践中看它是如何工作的可能是值得实现的。)

如果矩阵的密度确实是常数O(m * log(k)) = O(sqrt(n)*log(n)),那么密集矩阵表示也会进行c运算,因此渐近行为应该是相同的。常数因素将取决于O(sqrt(n)),所以再一次,你需要实现两者以确定哪个更快。

对于具有良好性能的四叉树解决方案,您还需要将非零值集中在一个小区域中;分布不需要特别均匀,只是不是非常集中。

如果您还希望经常添加和删除任意条目,那么 k d-tree非常难以做好 - 我认为没有简单的方案来实现树平衡本身,如红黑色或AVL或类似的一维树木。四叉树仍然可以工作。

答案 1 :(得分:1)

让我们看看我是否理解你拥有的东西:

  • 对于每一行,您都会维护一个已占用的列列表。
  • 对于每个列,您将维护一个已占用的行列表。
  • 这些结构中的每一个都足以描述matrx。

删除行时,只需将其关联的列列表设置为空。但在做之前 那么,为什么不使用这个列表来处理该列表中每列的行列表呢?

一个简单的例子。假设您有以下矩阵:

   1 0 0 0 1 1
   0 1 0 0 0 0
   0 1 1 0 0 0
   0 0 0 0 0 1

每行的列列表将是:

   0 [0, 4, 5]
   1 [1]
   2 [1, 2]
   3 [5]

每列的行列表为:

   0 [1]
   1 [1, 2]
   2 [2]
   3 []
   4 [0]
   5 [0, 3]

如果要删除第2行,那么您将处理与该行关联的列列表,在本例中为2 [1, 2]。这些是 行列表将包含“2”的列。没有其他的 需要查看行列表。

   Delete row 2: 
    -Column list for row 2: [1, 2]
    -Remove row '2' from the row list for columns 1 and 2
    -Set column list for row 2 to []
   done.

更新的列列表是:

   0 [0, 4, 5]
   1 [1]
   2 []   <== updated
   3 [5]

更新的行列表是:

   0 [1]
   1 [1]  <== updated
   2 []   <== updated
   3 []
   4 [0]
   5 [0, 3]

这两种结构都描述了以下矩阵:

   1 0 0 0 1 1
   0 1 0 0 0 0
   0 0 0 0 0 0
   0 0 0 0 0 1

这不是您正在寻找的O(1)算法,但对于非常稀疏的矩阵来说它应该是合理有效的。

答案 2 :(得分:0)

嗯,我一直在思考和思考,我认为这是可能的。

然而,解决方案并不理想,因为它在开始时可能是O(1),但O(n)依赖性仍然存在,但是对于某些类型的数据和用法,它应该接近恒定时间。所以它取决于它对你有用(与数组长度和/或操作数相比,变化的数量要少得多)。

对于每次删除,您都应该添加到更改的“更改列表”中。例如,您删除行号。 10,所以你加入你的清单:“10号以上,减1”。

在计算右边的行时,您必须通过“更改列表”减去/添加。

此外,您还需要一个阵列,其中包含最后使用的减去/添加的数字和“更改列表”的数量,因此您无需计算已为该行计算的更改。

在最坏的情况下,它仍然是“a * n”,其中a是常数。


示例:

rows, cols = 1000;
delete(row,573);
//=> list_of_changes[0] = {573, 'deleted'}
access_row(581)
//=> help_array[581] = {-1, 1}
//=> help_array.structure = {"how much add/subtract on this line", "number of changes used"}
access_row(581)
//=> look at the help_array[581] seeing having used 1 change,
//   the size of list_of_change is 1, so you don't have to count
//   anything, using the -1 value. - constant time

当然,如果我删除row [0]然后我访问所有0..998值,那么它将是O(n)因为它必须计算help_array的n倍。

答案 3 :(得分:0)

由于您主要使用cols地图是为了尽快计算列数,而不是从中获取数据,因此我会创建一个带有嵌套地图的table和一个{{1} }而不是colCountrows

此解决方案比O(1)更接近O(n),但您不会在cols结构中出现浪费的周期。

因此,对于您的示例,row x cols将如下所示:

table

{0: {1:"Value"}, {1: {0:"Value", 2:"Value"}} 看起来像这样:

colCount

然后,当您删除行#Each column in the example only had one value {0:1, 1:1, 2:1} 时,您只需递减行中找到的每个列的计数器。这是一些伪代码:

k

答案 4 :(得分:0)

总有唐纳德克努特的dancing links technique。您可以在每行和每列中使用双向链表。删除行或列需要行或列中元素数量的线性时间。