map,vector哪个效率更高,help!

yst_killed 2005-07-18 04:56:22
我目前有百完级的数据,都想保存到内存里面
不知道哪个用起来快写.
主要涉及到的操作.查找\和插入

...全文
5722 31 打赏 收藏 举报
写回复
用AI写文章
31 条回复
切换为时间正序
请发表友善的回复…
发表回复
xlsue 2005-07-26
  • 打赏
  • 举报
回复
概要:在有序vector中存储数据很有可能比在标准关联容器中保存相同的数据消耗更少的内存;当页面错误值得重视的时候,在有序vector中通过二分法查找可能比在一个标准关联容器中查找更快。

当然,有序vector的大缺点是它必须保持有序!当一个新元素插入时,大于这个新元素的所有东西都必须向上移一位。它和听起来一样昂贵,如果vector必须重新分配它的内在内存(参见条款14),则会更昂贵,因为vector中所有的元素都必须拷贝。同样的,如果一个元素从vector中被删除,所有大于它的元素都要向下移动。vector的插入和删除都很昂贵,但是关联容器的插入和删除则很轻量。这就是为什么只有当你知道你的数据结构使用的时候查找几乎不和插入和删除混合时,使用有序vector代替关联容器才有意义。

本条款有很多文字,但不幸的是只有很少的例子,所以我们来看看一个使用有序vector代替set的代码骨架:

vector<Widget> vw; // 代替set<Widget>
... // 建立阶段:很多插入,
// 几乎没有查找
sort(vw.begin(), vw.end()); // 结束建立阶段。(当
// 模拟一个multiset时,你
// 可能更喜欢用stable_sort
// 来代替;参见条款31。)
Widget w; // 用于查找的值的对象
... // 开始查找阶段
if (binary_search(vw.begin(), vw.end(), w))... // 通过binary_search查找
vector<Widget>::iterator i =
lower_bound(vw.begin(), vw.end(), w); // 通过lower_bound查找
if (i != vw.end() && !(w < *i))... // 条款19解释了
// “!(w < *i)”测试
pair<vector<Widget>::iterator,
vector<Widget>::iterator> range =
equal_range(vw.begin(), vw.end(), w); // 通过equal_range查找
if (range.first != range.second)...
... // 结束查找阶段,开始
// 重组阶段
sort(vw.begin(), vw.end()); // 开始新的查找阶段...

就像你可以看到的,这非常直截了当。里面最难的东西就是怎么在搜索算法中做出选择(比如,binary_search、lower_bound等),条款45可以帮你做出选择。

当你决定用vector代替map或multimap时,事情会变得更有趣,因为vector必须容纳pair对象。毕竟,那是map和multimap所容纳的。但是要注意,如果你声明一个map<K, V>的对象(或者等价的multimap),保存在map中的元素类型是pair<const K, V>。如果要用vector模拟map或者multimap,你必须去掉const,因为当你对vector排序时,它的元素的值将会通过赋值移动,那意味着pair的两个组件都必须是可赋值的。当使用vector来模拟map<K, V>时,保存在vector中数据的类型将是pair<K, V>,而不是pair<const K, V>。

map和multimap以顺序的方式保存他们的元素,但用于排序目的时它们只作用于元素的key部分(pair的第一个组件),所以当排序vector时你必须做一样的事情。你需要为你的pair写一个自定义比较函数,因为pair的operator<作用于pair的两个组件。

有趣的是,你会需要第二个比较函数来进行查找。用来排序的比较函数将作用于两个pair对象,但是查找只用到key值。必须传给用于查找的比较函数一个key类型的对象(要查找的值)和一个pair(存储在vector中的一个pair)——两个不同的类型。还有一个附加的麻烦,你不会知道key还是pair是作为第一个实参传递的,所以你真的需要两个用于查找的比较函数:一个key值先传递,一个pair先传递。

这个例子演示了怎么把这些东西合在一起:

typedef pair<string, int> Data; // 在这个例子里
// "map"容纳的类型
class DataCompare { // 用于比较的类
public:
bool operator()(const Data& lhs, // 用于排序的比较函数
const Data& rhs) const
{
return keyLess(lhs.first, rhs.first); // keyLess在下面
}

bool operator()(const Data& Ihs, // 用于查找的比较函数
const Data::first_type& k) const // (形式1)
{
return keyLess(lhs.first, k);
}

bool operator()(const Data::first_type& k, // 用于查找的比较函数
const Data& rhs) const // (形式2)
{
return keyLessfk, rhs.first);
}

private:
bool keyLess(const Data::first_type& k1, // “真的”
const Data::first_type& k2) const // 比较函数
{
return k1 < k2;
}
};

在这个例子中,我们假设有序vector将模拟map<string, int>。这段代码几乎是上面讨论的字面转换,除了存在成员函数keyLess。那个函数的存在是用来保证几个不同的operator()函数之间的一致性。每个这样的函数只是简单地比较两个key的值,所以我们把这个测试放在keyLess中并让operator()函数返回keyLess所做的事情,这比复制那个逻辑要好。这个软件工程中绝妙的动作增强了DataCompare的可维护性,但有一个小缺点,它提供了有不同参数类型的operator()函数,这将导致函数对象无法适配(看见条款40)。噢,好了。

把有序vector用作map本质上和用作set一样。唯一大的区别是必须把DataCompare对象用作比较函数:

vector<Data> vd; // 代替map<string, int>
... // 建立阶段:很多插入,
// 几乎没有查找
sort(vd.begin(), vd.end(), DataCompare()); // 结束建立阶段。(当
// 模拟multimap时,你
// 可能更喜欢用stable_sort
// 来代替;参见条款31。)
string s; // 用于查找的值的对象
... // 开始查找阶段
if (binary_search(vd.begin(), vd.end(), s,
DataCompare()))... // 通过binary_search查找
vector<Data>::iterator i =
lower_bound(vd.begin(), vd.end(), s,
DataCompare()); // 在次通过lower_bound查找,
if (i != vd.end() && !DataCompare()(s, *i))... // 条款45解释了
// “!DataCompare()(s, *i)”测试
pair<vector<Data>::iterator,
vector<Data>::iterator> range =
equal_range(vd.begin(), vd.end(), s,
DataCompare()); // 通过equal_range查找
if (range.first != range.second)...
... // 结束查找阶段,开始
// 重组阶段
sort(vd.begin(), vd.end(), DataCompare()); // 开始新的查找阶段...

正如你所见,一旦你写了DataCompare,东西都很好的依序排列了。而一旦位置合适了,只要你的程序按照101页描述的阶段方式使用数据结构,它们往往比相应的使用真的map的设计运行得更快而且使用更少内存。如果你的程序不是按照阶段的方式操作数据结构,那么使用有序vector代替标准关联容器几乎可以确定是在浪费时间。
xlsue 2005-07-26
  • 打赏
  • 举报
回复
>>> 考虑用有序vector代替关联容器 <<<<
当需要一个提供快速查找的数据结构时,很多STL程序员立刻会想到标准关联容器:set、multiset、map和multimap。直到现在这很好,但不是永远都好。如果查找速度真得很重要,的确也值得考虑使用非标准的散列容器(参见条款25)。如果使用了合适的散列函数,则可以认为散列容器提供了常数时间的查找。(如果选择了不好的散列函数或表的太小,散列表的查找性能可能明显下降,但在实际中这相对少见。)对于多数应用,被认为是常数时间查找的散列容器要好于保证了对数时间查找的set、map和它们的multi同事。

即使你需要的就只是对数时间查找的保证,标准关联容器仍然可能不是你的最佳选择。和直觉相反,对于标准关联容器,所提供的性能也经常劣于本该比较次的vector。如果你要有效使用STL,你需要明白什么时候和怎么让一个vector可以提供比标准关联容器更快的查找。

标准关联容器的典型实现是平衡二叉查找树。一个平衡二叉查找树是一个对插入、删除和查找的混合操作优化的数据结构。换句话说,它被设计为应用于进行一些插入,然后一些查找,然后可能再进行一些插入,然后也许一些删除,然后再来一些查找,然后更多的插入或删除,然后更多的查找等。这个事件序列的关键特征是插入、删除和查找都是混合在一起的。一般来说,没有办法预测对树的下一个操作是什么。

在很多应用中,使用数据结构并没有那么混乱。它们对数据结构的使用可以总结为这样的三个截然不同的阶段:

建立。通过插入很多元素建立一个新的数据结构。在这个阶段,几乎所有的操作都是插入和删除。几乎没有或根本没有查找。
查找。在数据结构中查找指定的信息片。在这个阶段,几乎所有的操作都是查找。几乎没有或根本没有插入和删除。
重组。修改数据结构的内容,也许通过删除所有现有数据和在原地插入新数据。从动作上说,这个阶段等价于阶段1。一旦这个阶段完成,应用程序返回阶段2。
对于这么使用它们的数据结构的应用来说,一个vector可能比一个关联容器能提供更高的性能(时间和空间上都是)。但不是任意的vector都会,只有有序vector。因为只有有序容器才能正确地使用查找算法——binary_search、lower_bound、equal_range等(参见条款34)。但为什么一个(有序的)vector的二分法查找比一个二叉树的二分法查找提供了更好的性能?因为有些东西是过时的但却是真的,其中的一个是大小问题。其他东西不那么过时也不那么真,其中的一个是引用局部性问题。

考虑第一个大小问题。假设我们需要一个容器来容纳Widget对象,而且,因为查找速度对我们很重要,我们考虑一个Widget的关联容器和一个有序vector<Widget>。如果我们选择一个关联容器,我们几乎确定了要使用平衡二叉树。这样的树是由树节点组成,每个都不仅容纳了一个Widget,而且保存了一个该节点到左孩子的指针,一个到它右孩子的指针,和(典型的)一个到它父节点的指针。这意味着在关联容器中用于存储一个Widget的空间开销至少会是三个指针。

与之相对的是,当在vector中存储Widget并没有开销:我们简单地存储一个Widget。当然,vector本身有开销,在vector结尾也可能有空的(保留)空间(参见条款14),但是每个vector开销是可以忽略的(通常是三个机器字,比如,三个指针或两个指针和一个int),而且如果必要的话,末尾空的空间可以通过“交换技巧”去掉(看见条款17)。即使这个附加的空间没有去掉,也并不影响下面的分析,因为当查找时不会引用那段内存。

假设我们的数据结构足够大,它们可以分成多个内存页面,但是vector比关联容器需要的页面要少。那是因为vector不需要每个Widget的开销,而关联容器给每个Widget上附加了三个指针。要知道为什么这很重要,假设在你使用的系统上一个Widget的大小是12个字节,指针是4个字节,一个内存页面是4096(4K)字节。忽略每个容器的开销,当用vector保存时,你可以在一页面上放置341个Widget,但使用关联容器时你最多只能放170个。因此关联容器和vector比起来,你将会使用大约两倍的内存。如果你使用的环境可以用虚拟内存,就很可以容易地看出那会造成大量的页面错误,因此一个系统会因为大数据量而明显慢下来。

实际上我在这里还是对关联容器很乐观的,因为我们假设在二叉树中的节点都群集在一个相关的小内存页面集中。大部分STL实现使用自定义内存管理器(实现在容器的配置器上——参见条款10和11)来达到这样的群集,但是如果你的STL实现没有改进树节点中的引用局部性,这些节点会分散在所有你的内存空间。那会导致更多的页面错误。即使使用了自定义群集内存管理器,关联容器也会导致很多页面错误,因为,不像连续内存容器,比如vector,基于节点的容器更难保证在容器的遍历顺序中一个挨着一个的元素在物理内存上也是一个挨着一个。但当进行二分查找时那种内存组织方式(译注:遍历顺序中一个挨着一个的元素在物理内存上也是一个挨着一个)正好是页面错误最少的。

sandrowjw 2005-07-23
  • 打赏
  • 举报
回复
5555,楼主当我啥都没说过就行了。
sandrowjw 2005-07-22
  • 打赏
  • 举报
回复
百万级?Page作为Node,分级访问,上级用索引树(map、B+树都可以),下级用hash,留一块空间作为冲突溢出区。
这样插入的时候如果Node满了会非常慢,因为要rehashing,但是查找的效率会比较稳定。
问题是自己写代码难保证效率啊,还是搞个内存数据库什么的吧。
whatsouta 2005-07-21
  • 打赏
  • 举报
回复
MAP
熊主任 2005-07-21
  • 打赏
  • 举报
回复
楼上理解错我的意思了,我是说用数据库不符合搂主提出的要求(用内存数据库的话另说)。还有如果用数据库的话,b+树的效率,嘿嘿~~~自己嘿咻出来的代码就不要比了。
Wolf0403 2005-07-21
  • 打赏
  • 举报
回复
数据库没什么过分的。B-tree 的 Berkeley DB 也是数据库,绝对比用 STL 实现这种东西要好吧。
STL 设计目标是通用性。对于这种大数据量的情况是否合用?
熊主任 2005-07-20
  • 打赏
  • 举报
回复
用数据库太过分了!用map的话插入和查找的复杂度都是log(n)。用list和vector插入的效率是o(1),但是查找是o(n),而且频繁插入查找的话那真是噩梦。内存映射文件是治标不治本,如果操作效率低的话根本不解决问题。
xlsue 2005-07-20
  • 打赏
  • 举报
回复
《C++标准程序库》P228: 关联式容器拥有自动排序能力,并不意味它们在排序方面的执行效率更高。事实上由于关联式容器每安插一个新元素,都要进行一次排序,所以速度反而不及系列容器经常采用的手法:先安插所有元素,然后调用排序算法进行一次完全排序。
Jagen在路上 2005-07-20
  • 打赏
  • 举报
回复
这种情况就使用map把,map的插入和查找时间复杂度都是O(logN)
Jagen在路上 2005-07-20
  • 打赏
  • 举报
回复
楼上的哥们,你的肾值多少钱:)
bm1408 2005-07-20
  • 打赏
  • 举报
回复
百万级的数据?

你可以使用内存映射文件进行操作!
OpenHero 2005-07-20
  • 打赏
  • 举报
回复
还是用数据库吧
Wolf0403 2005-07-20
  • 打赏
  • 举报
回复
百万级的数据?什么概念?数据量大的话还是用数据库吧……看看那些 in-memory db
xlsue 2005-07-19
  • 打赏
  • 举报
回复
如果内存不是考虑的问题。用vector比map好。map每插入一个数据,都要排序一次。所以速度反不及先安插所有元素,再进行排序。用binary_search对已序区间搜索,如果是随机存取iterator,则是对数复杂度。可见,在不考虑内存问题的情况下,vector比map好。
xlsue 2005-07-19
  • 打赏
  • 举报
回复
不考虑内存问题,vector是你的选择!当然你只能在后面插入!如果你从百万个数据文件中读取数据,你可以考虑先把数据全部读到vector中,然后排序,再用binary_search算法查找,这个算法如果搭配的是随机iterator,复杂度是对数的。这个复杂度和map的查找复杂度一样的快。因此,不考虑内存问题,按你的意图,vector是更好的选择。
垲垲 2005-07-19
  • 打赏
  • 举报
回复
哈哈~~~
HASH类型的查找肯定快,是映射关系嘛,但是插入和删除却慢,要做移动操作

LIST类型的使链式关系,插入非常快,但是查找却费时,需要遍历~~

还是用LIST类型的吧,虽然查找慢点,先快速排序,然后二分查找,效率也不低
熊主任 2005-07-19
  • 打赏
  • 举报
回复
涉及到查找的话用map比较好,因为map的内部数据结构用rb-tree实现,而用vector你只能用线性查找,效率很低。sgi的stl还提供了hash容器,理论上查找是飞快~~~。做有序插入的话vector是噩梦,map则保证肯定是按key排序的,list要自己做些事情。
yst_killed 2005-07-19
  • 打赏
  • 举报
回复
而且我不是为了比较这两个的区别.我是想知道哪个查询更快,而且插入后更快..
yst_killed 2005-07-19
  • 打赏
  • 举报
回复
百万级的数据.已经成功的转化为 unsigned long 型了.而且可以做为key来存储..
也就是对大量的unsigned long型数据进行查询.如果存在则忽略,不存在则填加..

具体操作就这些.
加载更多回复(11)
大气污染是影响公众健康与生态环境的重要问题,精准的空气质量时空预测与污染源贡献度量化是精准治污的关键支撑。针对现有研究多源融合不充分、时空关联刻画不足、预测与源解析割裂三方面缺陷,本文设计实现了城市空气质量时空预测与污染源贡献度分析系统,融合监测、气象、工业排放与交通四类数据,构建基于时空注意力的LSTM(STAM-LSTM)预测模型与基于正定矩阵因子分解(PMF)的源解析模型,形成数据融合-特征工程-预测-源解析-可视化闭环。 系统实现四类数据时空对齐与融合,构建时序与空间邻域特征,以普通克里金插值生成1km网格浓度场;STAM-LSTM引入时空注意力自适应学习站点间污染传输时变权重,以72小时输入预测未来24小时逐小时PM2.5浓度;PMF识别交通、工业、燃煤、扬尘与二次生成五个源因子,量化各源全年贡献度并分析时空演变。 实验表明:STAM-LSTM预测RMSE 24.6、MAE 17.8、R² 0.88,相对LSTM基线(30.2)提升18.5%;普通克里金插值误差8.9,优于反距离加权(11.4);源解析显示交通源28.4%、工业源23.1%、燃煤源19.6%为主要贡献源,冬季燃煤源升至27.3%、早高峰交通源达34.8%,下风向工业源贡献高出上风向8~12个百分点;减排情景显示交通源减排20%可使年均PM2.5下降5.7%,与源贡献度排序一致。 系统按五模块14组件实现,功能测试16项用例全部通过,为大气污染预警、源管控与减排政策制定提供了决策依据。 【课程报告内容】 摘要 第1章 绪论 第2章 相关技术与理论 第3章 系统需求分析 第4章 系统总体设计 第5章 系统详细设计与实现 第6章 系统测试与分析 第7章 总结与展望 参考文献 附件-实现指南
基于iTransformer-BiGRU-KAN多模型融合的滚动轴承剩余寿命预测研究(Python代码实现)内容概要:本文提出了一种基于iTransformer-BiGRU-KAN多模型融合的滚动轴承剩余寿命预测方法,旨在通过结合多种先进深度学习模型的优势,提升在复杂工况下的预测精度与鲁棒性。该方法利用iTransformer捕捉长期时间序列中的全局依赖关系,通过BiGRU模型提取双向时序特征,最后引入KAN(Kernel Attention Network)增强非线性映射与关键特征的自适应加权能力,实现对轴承退化过程的精准建模。文中详细介绍了模型架构设计、训练流程及在公开数据集上的实验验证,结果表明该融合模型相比单一模型在预测精度和稳定性方面均有显著提升。; 适合人群:具备一定机器学习与深度学习基础,从事设备故障诊断、工业大数据分析或智能运维相关领域的研究人员及工程技术人员,尤其适合研究生及以上学历或有相关项目经验的专业人员。; 使用场景及目标:①应用于工业设备状态监测与预测性维护系统中,实现对滚动轴承等关键部件剩余寿命的精准预测;②为复杂时间序列回归任务提供多模型融合的设计思路与技术参考;③推动深度学习在智能制造与工业物联网领域的落地应用。; 阅读建议:建议读者结合Python代码实现部分,深入理解各子模型的接口设计与融合逻辑,重点关注特征融合机制与注意力权重的可视化分析,以便在实际项目中灵活调整与优化模型结构。
几何旋转和天线校准模式对GNSS相位缠绕的组合效应(Matlab代码实现)内容概要:本文研究了几何旋转和天线校准模式对全球导航卫星系统(GNSS)相位缠绕的组合效应,并提供了基于Matlab的代码实现方案。相位缠绕是GNSS高精度定位中的重要误差源,受卫星与接收机相对几何关系及天线相位中心变化的共同影响。文章通过建模分析几何旋转与天线校准参数对相位缠绕的影响机制,探讨二者耦合作用下的修正方法,旨在提升GNSS数据处理的精度与可靠性。研究涵盖了理论建模、算法实现与仿真实验,结合Matlab工具进行数值模拟与结果可视化,验证了所提方法的有效性。; 适合人群:具备一定GNSS基础知识和Matlab编程能力的科研人员、研究生及从事高精度定位相关工作的技术人员。; 使用场景及目标:①用于GNSS高精度数据处理中相位缠绕误差的精确建模与修正;②支持地壳形变监测、精密授时、卫星定轨等对定位精度要求较高的应用场景;③为相关算法开发与教学研究提供可复现的代码实例。; 阅读建议:建议读者结合GNSS误差处理的相关理论,边运行代码边理解算法细节,重点关注几何旋转模型与天线校准参数的集成方式,并可通过修改参数进行敏感性分析以加深理解。

24,850

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 工具平台和程序库
社区管理员
  • 工具平台和程序库社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧