我有一个非常稀疏填充的表,其中巨大的维度。
,即我的表的索引可能非常大,但表中的元素数量非常少。
我一直在考虑这个数据结构。
我排除了一个rows x cols
表,因为它需要太多的内存和太多的时间来查找行/列中的所有元素。
相反,我考虑过使用两张地图:rows
和cols
。
让我们看看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)
方法吗?
答案 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)
让我们看看我是否理解你拥有的东西:
删除行时,只需将其关联的列列表设置为空。但在做之前 那么,为什么不使用这个列表来处理该列表中每列的行列表呢?
一个简单的例子。假设您有以下矩阵:
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} }而不是colCount
和rows
。
此解决方案比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。您可以在每行和每列中使用双向链表。删除行或列需要行或列中元素数量的线性时间。