现在开发的项目对性能比较敏感,有一个列表结构频繁地会通过索引获取对象,通过下标或对象本身删除列表中的一个或多个对象,在列表中间某个位置、尾部插入一个或多个对象,请问能不能实现这种数据结构让每种方法都以 O(1)的方式进行。
1
THESDZ 2022-06-30 16:44:03 +08:00
通过下标获取的,是数组吧?(这个问题应该是这么简单吧?)
|
2
wdc63 OP @THESDZ 数组下标获取是 O1 ,但 InsertAt 、RemoveAt 、Remove(obj)都有 O(n)的时间开销,希望能后面几种方法也能以 O1 实现。
|
3
MoYi123 2022-06-30 16:47:19 +08:00
#include <ext/pb_ds/assoc_container.hpp>
只能 O(logn),而且常数不小. |
4
xtreme1 2022-06-30 16:48:25 +08:00
at() 和 removeAt() 应该没法同时 O(1)吧..
|
5
cogear 2022-06-30 16:50:00 +08:00
以我的见识来说,查询和插入都做到 O1 是不可能的。
线性表(数组)查询可以 O1 。 链表插入删除可以 O1 ,但是查询是 O(n) 跳跃表?能在插入和查询之间做个平衡,O(logN),但是不能 O1 。 |
8
dcsuibian 2022-06-30 16:54:12 +08:00
哈希表,平均 O(1),最差 O(n)。。。
|
10
Jooooooooo 2022-06-30 17:10:26 +08:00
一个简单的思路出发点是用空间去换时间
比如你维护多份相同的数据 |
11
wdc63 OP @Jooooooooo 具体怎样能实现呢?
|
12
Jooooooooo 2022-06-30 17:18:13 +08:00
@wdc63 简单想了一下, 先用 linkedList, 这样范围的插入和删除就是 O(1) 的
接下来问题是怎么找到 list 里对应的节点, 再用一个 map, 维护所有数据在上面 list 里的 index, 这样来了一个元素要查找能立马得知它在 list 哪个位置 (object -> index) 接下来再有一个问题是, 怎么快速遍历到 list 对应的位置上, 再用一个 map, 记录所有 index 指向的节点, 比如第 0 个元素的指针, 第 1 个元素的指针 这样有可能可以?? 不过细节得再想想. (在不考虑并发的前提下 |
13
Jooooooooo 2022-06-30 17:19:19 +08:00
@Jooooooooo 噢不行, index 没法维护...得再想想别的方案了.
|
14
thinkershare 2022-06-30 17:19:23 +08:00
@wdc63 没有这种数据结构, 不用找了
|
17
jhdxr 2022-06-30 17:25:15 +08:00
『通过索引获取对象,通过下标』
如果你觉得索引和下标是不一样的。那你不是默认就只剩数组了,链表之类的哪来下标? |
18
seers 2022-06-30 17:28:05 +08:00 via Android
哈西链表
|
19
MoYi123 2022-06-30 17:29:56 +08:00
@wdc63
看下这个库吧, from sortedcontainers import SortedList 把注释删掉大约 600 行 但是中间插入不好搞. 必须定期重新构建整个数据结构. from sortedcontainers import SortedList a = [0] def incr(): a[0] += 1 return a[0] s = SortedList() invert = {} # 向尾部插入单个 idx = incr() s.add([idx, 'A']) invert['A'] = idx idx = incr() s.add([idx, 'B']) invert['B'] = idx # 用下标获取 print(s[0]) # [1, 'A'] print(s[1]) # [2, 'B'] # 用下标删除 s.pop(0) print(s) # 用对象本身删除 idx = invert['B'] idx = s.bisect_left([idx, 'B']) s.pop(idx) print(s) # 中间插入(先插入 2 个值) idx = incr() s.add([idx, 'A']) invert['A'] = idx idx = incr() s.add([idx, 'B']) invert['B'] = idx # 中间插入,在 A,B 间加一个 C # 这里比较尴尬, float 精度有限, 需要定期用 O(n)重新构建整个表 left = s[0][0] right = s[1][0] s.add([(left + right) / 2, 'C']) print(s) |
20
sunny352787 2022-06-30 18:23:20 +08:00
不是,等一下,不会又碰到了 AB 问题了吧?为啥一定要用下标呢?一般来说 hash 表就可以了啊,你这是个什么场景一定要用数组下标这种形式呢?
|
21
aguesuka 2022-06-30 19:24:00 +08:00
这段代码能 O(1) 地 [添加元素返回下标] , [通过下标获得元素], [通过下标删除元素].
但是列表是无序的, 不能保证删除的幂等性. https://github.com/aguesuka/torrent-finder/blob/dev-nio2/src/main/java/cc/aguesuka/maguet/util/ArrayHeapSpace.java |
22
RicardoY 2022-06-30 20:53:05 +08:00
块状链表这个场景应该蛮快的,如果数据量不是太大的话,复杂度比你要求的高一点
|
23
hsfzxjy 2022-06-30 21:17:59 +08:00 via Android
了解下跳表 skip list
|
24
joetse 2022-06-30 23:15:19 +08:00 via Android
只有完美哈希可以做到,前提是无限内存
|
25
dqzcwxb 2022-06-30 23:32:55 +08:00
世间安得双全法
|
26
AllenHua 2022-07-01 09:43:18 +08:00 via iPhone
优先保证插入和删除是 o(1) 应该就是链表为主的结构了,但又要保证查询是 o(1) 应该是做不到的,在查询上妥协一些应该是比较理想的方案
|
27
qbqbqbqb 2022-07-01 11:51:07 +08:00
所有方法都 O(1)是不行的
可以做到所有方法都是 O(log n) 主要有两大类: * 跳跃表( skip list ),单次操作时间复杂度为期望 O(log n),实现较为简单 * 带有 order statistics 的平衡二叉搜索树(包括 AVL, 红黑树, Splay 等多种实现),根据实现的不同,单次操作时间复杂度为期望、均摊或严格 O(log n),实现较为复杂 |
28
wdc63 OP |