ALL,
这个问题是this one的延续。 我认为STL错过了这个功能,但它只是我的恕我直言。
现在,问题。
请考虑以下代码:
class Foo
{
public:
Foo();
int paramA, paramB;
std::string name;
};
struct Sorter
{
bool operator()(const Foo &foo1, const Foo &foo2) const
{
switch( paramSorter )
{
case 1:
return foo1.paramA < foo2.paramA;
case 2:
return foo1.paramB < foo2.paramB;
default:
return foo1.name < foo2.name;
}
}
int paramSorter;
};
int main()
{
std::vector<Foo> foo;
Sorter sorter;
sorter.paramSorter = 0;
// fill the vector
std::sort( foo.begin(), foo.end(), sorter );
}
在任何给定的时刻,矢量都可以重新排序。 该类还具有在分拣机结构中使用的getter方法。
在向量中插入新元素的最有效方法是什么?
我的情况是:
我有一个网格(电子表格),它使用类的排序向量。在任何给定时间,矢量都可以重新排序,网格将相应地显示排序数据。
现在我需要在矢量/网格中插入一个新元素。 我可以插入,然后重新排序,然后重新显示整个网格,但这对于大网格来说非常低效。
任何帮助都将不胜感激。
答案 0 :(得分:52)
问题的简单答案:
template< typename T >
typename std::vector<T>::iterator
insert_sorted( std::vector<T> & vec, T const& item )
{
return vec.insert
(
std::upper_bound( vec.begin(), vec.end(), item ),
item
);
}
带谓词的版本。
template< typename T, typename Pred >
typename std::vector<T>::iterator
insert_sorted( std::vector<T> & vec, T const& item, Pred pred )
{
return vec.insert
(
std::upper_bound( vec.begin(), vec.end(), item, pred ),
item
);
}
其中Pred是类型T的严格排序谓词。
为此,输入向量必须已经在此谓词上排序。
执行此操作的复杂性为O(log N)
upper_bound
搜索(查找插入位置),但最多为O(N)
插件本身。
为了更好的复杂性,如果不存在任何重复项,则可以使用std::set<T>
;如果可能存在重复项,则可以std::multiset<T>
使用vector
。这些将自动为您保留排序顺序,您也可以在这些上指定自己的谓词。
您还可以做其他更复杂的事情,例如:管理新添加的项目的set
和multiset
/ sorted vector
/ vector
,然后在有足够数量时合并这些项目。任何类型的迭代都需要在两个集合中运行。
使用第二个向量具有保持数据紧凑的优势。在这里你的新增&#34;项O(M)
相对较小,因此插入时间为M
,其中O(N)
是此向量的大小,可能比插入大O(N+M)
更可行矢量每次。合并将为O(NM)
,这比O(N+M) + O(M²)
更好,它将一次插入一个,因此总共M
插入{{1}}元素然后合并。< / p>
您可能也会将插入向量保持在其容量状态,因此当您增长时,您将不会进行任何重新分配,只需移动元素。
答案 1 :(得分:24)
如果您需要始终对矢量进行排序,首先您可以考虑使用std::set
或std::multiset
是否不会简化您的代码。
如果您确实需要一个已排序的向量并希望快速插入一个元素,但又不想强制执行排序条件以便始终满足,那么您可以先使用std::lower_bound()
来查找位置在应该以对数时间插入元素的排序范围内,然后使用vector
的{{3}}成员函数在该位置插入元素。
如果性能问题,请考虑std::list
与std::vector
进行基准测试。对于小项目,由于缓存命中率较高,std::vector
已知更快,但insert()
操作本身在列表上的计算速度更快(无需移动元素)。
答案 2 :(得分:8)
只需注意,您也可以根据需要使用upper_bound
。 upper_bound
将确保与其他条目等效的新条目将显示在其序列的 end 中,lower_bound
将确保等同于其他条目的新条目将出现在开头他们的序列。对于某些实现(可能是可以共享“位置”但不是所有细节的类)可能很有用!)
两者都会向您保证,向量仍会根据元素的<
结果进行排序,但插入lower_bound
将意味着移动更多元素。
示例:
insert 7 @ lower_bound of { 5, 7, 7, 9 } => { 5, *7*, 7, 7, 9 }
insert 7 @ upper_bound of { 5, 7, 7, 9 } => { 5, 7, 7, *7*, 9 }
答案 3 :(得分:1)
而不是插入和排序。你应该找一个然后插入
保持矢量排序。 (排序一次)。当你必须插入
找到第一个与您要插入的元素相比较较大的元素。
在该位置之前插入一个插件。
这样矢量保持排序。
这是一个如何运作的例子。
start {} empty vector
insert 1 -> find first greater returns end() = 1 -> insert at 1 -> {1}
insert 5 -> find first greater returns end() = 2 -> insert at 2 -> {1,5}
insert 3 -> find first greater returns 2 -> insert at 2 -> {1,3,5}
insert 4 -> find first greater returns 3 -> insert at 3 -> {1,3,4,5}
答案 4 :(得分:0)
当你想在排序顺序之间切换时,你可以使用多个索引数据结构,每个数据结构都按排序顺序保存(可能是某种平衡树,比如std :: map,它将排序键映射到vector-indices或者std :: set来存储指向你的指针 - 但是使用不同的比较函数。)
这是一个执行此操作的库:http://www.boost.org/doc/libs/1_53_0/libs/multi_index/doc/index.html
对于每个更改(插入新元素或更新键),您必须更新所有索引数据结构,或将它们标记为无效。
如果没有“太多”排序顺序而不是“太多”数据结构更新,则此方法有效。否则 - 运气不好,每次想要改变订单时都必须重新排序。
换句话说:您需要的索引越多(加速查找操作),您需要的更新操作就越多。当然,每个指数都需要记忆。
为了使索引的数量保持较小,您可以使用一些查询引擎,它结合了多个字段的索引来支持多个字段的更复杂的排序顺序。像SQL查询优化器一样。但这可能有点过分......
示例:如果您有两个字段a和b,则可以支持4个排序顺序:
有2个指数(3和4)。 通过更多字段,排序顺序的可能组合变得更快,更快。但您仍然可以使用“几乎按照您的意愿”排序的索引,并在查询期间根据需要对您无法捕获的其余字段进行排序。对于整个数据的排序输出,这没有多大帮助。但是如果你只想查找一些元素,那么第一个“缩小范围”可能会有所帮助。
答案 5 :(得分:-1)
假设您确实想要使用向量,并且排序标准或键不会更改(因此已插入元素的顺序始终保持不变): 在末尾插入元素,然后一次将其移到前面一步,直到前面的元素不大。
它不能更快地完成(关于渐近复杂度,或“大O符号”),因为你必须移动所有更大的元素。这就是STL没有提供这个的原因 - 因为它对矢量效率低下,如果你需要它就不应该使用它们。
编辑:另一个假设:比较元素并不比移动它们贵得多。见评论。
编辑2:由于我的第一个假设不成立(您想要更改排序标准),请废弃此答案并查看我的另一个:https://stackoverflow.com/a/15843955/1413374