所以我试图了解BST和Hashing的某些函数的数据类型和Big O表示法。
首先,BST和Hashing是如何存储的? BST通常是数组,还是链表,因为它们必须指向左右叶? Hashing怎么样?在基于计算的搜索方面,我最难找到有关哈希的明确信息。我知道Hashing最好用一系列链来实现。这是为了更快地搜索还是减少创建分配数据类型的开销?
以下问题可能只是我的错误解释,但是什么使得遍历功能与BST,Hashing和STL容器中的搜索功能不同? 是否正在遍历BSTS的Big O(N),因为您实际上是在访问每个节点/数据成员,而search()可以通过消除一半的搜索字段来减少其时间?
有些相关,为什么在STL中,list.insert()和list.erase()有一个Big O(1)而vector和deque对应的是O(N)?
最后,为什么vector.push_back()是O(N)?我认为函数可以按照O(1)这样的方式完成,但我遇到的文字说它是O(N):
vector<int> vic(2,3);
vector<int>::const iterator IT = vic.end();
//wanna insert 4 to the end using push_back
IT++;
(*IT) = 4;
希望这有效。我有点累,但我会喜欢任何解释为什么类似的东西不会有效或合理。感谢
答案 0 :(得分:1)
BST(有序二进制树)是一系列节点,其中父节点指向其两个子节点,而这两个子节点又指向其最多两个子节点等。由于遍历访问,它们在O(n)时间内遍历每个节点。查找需要O(log n)时间。插入需要O(1)时间,因为在内部它们不需要一堆现有节点;只需分配一些内存并重新指向指针。 :)
哈希(unordered_map)使用散列算法将元素分配给存储桶。通常,存储桶包含链接列表,因此散列冲突只会导致同一存储桶中的多个元素。如预期的那样,遍历将再次为O(n)。查找和插入将分摊O(1)。摊销意味着平均而言,O(1),尽管单个插入可能导致重新划分(重新分配桶以最小化碰撞)。但随着时间的推移,平均复杂度为O(1)。但请注意,big-O表示法并不真正处理“常量”方面;只有增长的顺序。散列算法中的常量开销可以足够高,对于某些数据集,O(log n)二叉树优于散列。然而,哈希的优势在于其操作是恒定的时间复杂度。
搜索功能利用了“订单”概念(在二叉树的情况下);通过BST搜索具有与有序数组上的基本二进制搜索相同的特征。 O(log n)增长。哈希不是真正的“搜索”。他们计算存储桶,然后快速浏览碰撞以找到目标。这就是为什么查找是恒定的时间。
插入和擦除;在基于数组的序列容器中,目标之后的所有元素都必须向右碰撞。在C ++ 11中移动语义可以提高性能,但操作仍然是O(n)。对于链接的序列容器(list,forward_list,trees),插入和擦除只是意味着在内部摆弄一些指针。这是一个固定的时间过程。
push_back()将为O(1),直到超过向量的现有分配容量。一旦超出容量,就会进行新的分配以生成足够大的容器以接受更多元素。然后需要将所有元素移动到更大的存储区域中,这是一个O(n)过程。我相信Move Semantics也可以在这里提供帮助,但它仍然是O(n)。实现向量和字符串,因为它们为不断增长的数据集分配空间,它们分配的内容超出了他们的需要,预计会有额外的增长。这是一种效率保障;这意味着典型的push_back()不会触发新的分配并将整个数据集移动到更大的容器中。但最终在足够的push_backs之后,将达到限制,并且向量的元素将被复制到一个更大的容器中,这也会留下一些额外的余量,以便更有效地推送回调。
答案 1 :(得分:0)
Traversal是指访问每个节点,而搜索只是为了找到一个特定的节点,所以你的直觉就在那里。 O(N)复杂性因为您需要访问N个节点。
std::vector::insert
用于插入中间,它涉及将所有后续元素复制一个插槽,以便为插入的元素腾出空间,因此为O(N)。链接列表没有这个问题,因此O(1)。 erase
的类似逻辑。 deque
属性类似于vector
std::vector::push_back is a O(1) operation,在大多数情况下,只有在超出容量且需要重新分配+复制时才会出现偏差。