对于有插入(范围插入)、 删除(范围删除)、 下标获取要求性能最好的数据结构是什么?

2022-06-30 16:38:55 +08:00
 wdc63

现在开发的项目对性能比较敏感,有一个列表结构频繁地会通过索引获取对象,通过下标或对象本身删除列表中的一个或多个对象,在列表中间某个位置、尾部插入一个或多个对象,请问能不能实现这种数据结构让每种方法都以 O(1)的方式进行。

2325 次点击
所在节点    程序员
28 条回复
THESDZ
2022-06-30 16:44:03 +08:00
通过下标获取的,是数组吧?(这个问题应该是这么简单吧?)
wdc63
2022-06-30 16:46:13 +08:00
@THESDZ 数组下标获取是 O1 ,但 InsertAt 、RemoveAt 、Remove(obj)都有 O(n)的时间开销,希望能后面几种方法也能以 O1 实现。
MoYi123
2022-06-30 16:47:19 +08:00
#include <ext/pb_ds/assoc_container.hpp>

只能 O(logn),而且常数不小.
xtreme1
2022-06-30 16:48:25 +08:00
at() 和 removeAt() 应该没法同时 O(1)吧..
cogear
2022-06-30 16:50:00 +08:00
以我的见识来说,查询和插入都做到 O1 是不可能的。

线性表(数组)查询可以 O1 。
链表插入删除可以 O1 ,但是查询是 O(n)

跳跃表?能在插入和查询之间做个平衡,O(logN),但是不能 O1 。
wdc63
2022-06-30 16:50:30 +08:00
@MoYi123 能否解释一下这种算法,只会 C#、JAVA 和 Python ,C++看起来太费劲。
wdc63
2022-06-30 16:51:26 +08:00
@cogear 能否做到查询 O1 ,插入 Ologn 。
dcsuibian
2022-06-30 16:54:12 +08:00
哈希表,平均 O(1),最差 O(n)。。。
wdc63
2022-06-30 16:56:24 +08:00
@dcsuibian hash 表没法用下标查询。
Jooooooooo
2022-06-30 17:10:26 +08:00
一个简单的思路出发点是用空间去换时间

比如你维护多份相同的数据
wdc63
2022-06-30 17:11:22 +08:00
@Jooooooooo 具体怎样能实现呢?
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 个元素的指针

这样有可能可以?? 不过细节得再想想. (在不考虑并发的前提下
Jooooooooo
2022-06-30 17:19:19 +08:00
@Jooooooooo 噢不行, index 没法维护...得再想想别的方案了.
thinkershare
2022-06-30 17:19:23 +08:00
@wdc63 没有这种数据结构, 不用找了
cogear
2022-06-30 17:20:18 +08:00
@wdc63 不能,查询做不到 O1 ,查询大约是二分查找的性能
MoYi123
2022-06-30 17:21:58 +08:00
@wdc63 有点问题, 这个不是很合适.
jhdxr
2022-06-30 17:25:15 +08:00
『通过索引获取对象,通过下标』
如果你觉得索引和下标是不一样的。那你不是默认就只剩数组了,链表之类的哪来下标?
seers
2022-06-30 17:28:05 +08:00
哈西链表
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)
sunny352787
2022-06-30 18:23:20 +08:00
不是,等一下,不会又碰到了 AB 问题了吧?为啥一定要用下标呢?一般来说 hash 表就可以了啊,你这是个什么场景一定要用数组下标这种形式呢?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/863239

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX