博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[GeekBand] STL vector 查找拷贝操作效率分析
阅读量:6254 次
发布时间:2019-06-22

本文共 8721 字,大约阅读时间需要 29 分钟。

本文参考文献::GeekBand课堂内容,授课老师:张文杰

            :C++ Primer 11 中文版(第五版)

                   :网络资料: 叶卡同学的部落格  http://www.leavesite.com/

                                                             http://blog.sina.com.cn/s/blog_a2a6dd380102w73e.html

 

一、关于Vector的基本概念及相关算法简介

1、什么是vector?

  简单的理解:数组!

     进一步的理解:变长一维的动态数组,连续存放的内存块,堆内分配内存。支持[]操作(一会就会用到),支持下标操作。

     再进一步的理解:vector表示对象的集合,集合中每个对象都有与之对应的索引(理解为下标,这里可以保存很多元素的对象,包括不限于,如int string、或者自己定义的类的对象等等。

 

2、Vector的初始化

有几种基本初始化方法:

vector
v1 ; //构造函数vector
v2(v1) ; //拷贝构造函数vector
v2 =v1 ; //拷贝赋值vector
v1{ 0, 0, 30, 20, 0, 0, 0, 0, 10, 0 }; //初始化

 本文中采用最基本的方法进行初始化。

3、Vector基本操作

vector
vec;vector
vec2;vec.push_back(t); //向v的尾端添加元素tvec.empty();vec.size();vec == vec2; //相等类似的操作都有

四、迭代器

简单的理解:类似于指针。如下面的一句话,迭代器有一个优点,那就是所有的标准库容器都可以使用迭代器,具有极强的通用性。

 

vector
::iterator result = find_if(Vec1.begin(), Vec1.end(), bind2nd(not_equal_to
(), unexpectedInt));

 

五、本文中可能会用到的一些(算法)操作:

1、函数 :not_equal_to,重载了操作符(),判断左右两个数是否相等,不等返回值 TRUE、即1,相等返回 FALSE、即0.

    类似的函数有equal_to 

template
struct not_equal_to : public binary_function<_Ty, _Ty, bool> { // functor for operator!= bool operator()(const _Ty& _Left, const _Ty& _Right) const { // apply operator!= to operands return (_Left != _Right); } };

 

2、bind1st 和 bind2nd

bind1st(const Fn2& Func,const Ty& left)  :1st指:传进来的参数应该放左边,也就是第一位bind2nd(const Fn2& Func,const Ty& right) :2nd指:传进来的参数应该放右边,也就是第二位

简单的两个例子:

#include "stdafx.h"#include 
#include
#include
#include
#include
#include
#include
using namespace std;void ShowArray(vector
&Vec){ vector
::iterator it = Vec.begin(); //it 是一个地址 while (it < Vec.end()) { cout << *it << endl; it++; }};int _tmain(int argc, _TCHAR* argv[]){ int a[] = { 1, 2, 100, 200 }; std::vector
arr(a, a + 4); // 移除所有小于100的元素 arr.erase(std::remove_if(arr.begin(), arr.end(), std::bind2nd(std::less< int>(), 100)), arr.end()); ShowArray(arr); /**************************************/ printf("*******************************\n"); int b[] = { 1, 2, 100, 200 }; std::vector
arr2(b, b + 4); // 移除所有大于100的元素 arr2.erase(std::remove_if(arr2.begin(), arr2.end(), std::bind1st(std::less< int>(), 99)), arr2.end()); ShowArray(arr2);}

本例中因为仅判断是否为0 ,所有采用bind1st 和 bind2nd都一样。

 

3、remove_copy_if

remove_copy_if() 函数原型

template
inline _OutIt remove_copy_if(_InIt _First, _InIt _Last, _OutIt _Dest, _Pr _Pred) { // copy omitting each element satisfying _Pred _DEBUG_RANGE(_First, _Last); _DEBUG_POINTER(_Dest); _DEBUG_POINTER(_Pred); return (_Remove_copy_if(_Unchecked(_First), _Unchecked(_Last), _Dest, _Pred, _Is_checked(_Dest))); }

 

remove_copy_if()的思考方式和copy_if()相反,若IsNotZero為true,則不copy,若為false,則copy。

 

remove_copy_if(Vec1.begin(), Vec1.end(), back_inserter(Vec2), IsNotZero);

此时要求: 当unexpectedInt 为0时,返回值为TRUE,不进行拷贝;当unexpectedInt 不为0时,返回值为FALSE,则进行copy。

bool IsNotZero(int unexpectedInt){    return (unexpectedInt == 0);}

 

 

二、三种不同方法来实现将查找拷贝操作

完成代码如下: 开发环境 VS2013 IDE

// Vector.cpp : 定义控制台应用程序的入口点。///*问题:给定一个 vector:v1 = [0, 0, 30, 20, 0, 0, 0, 0, 10, 0],希望通过 not_equal_to 算法找到到不为零的元素,并复制到另一个 vector: v2*//*要点一:    第一步、利用not_equal_to函数进行数值比较,区分vector某一元素是否是非0数据    第二步、查找所有的非0元素    第三步、将所有非0元素拷贝到v2中来要点二:效率问题    测试结果:    利用下标耗费时间最少,运行速度比较快,但不通用(vector可以利用下标)。    利用迭代器耗费时间较多,但更为通用。    利用C++ 11 remove_copy_if() algorithm 进行分析总结:    C++ 11 remove_copy_if() algorithm 代码最少,效率最高。*/#include "stdafx.h"#include 
#include
#include
#include
#include
#include
#include
using namespace std;#pragma comment( lib,"winmm.lib" )//利用下标的方法void FiltArray0(vector
&Vec1, vector
&Vec2, const int unexpectedInt){ //测试时间 LARGE_INTEGER litmp; LONGLONG qt1, qt2; double dft, dff, dfm; QueryPerformanceFrequency(&litmp);//获得时钟频率 dff = (double)litmp.QuadPart; QueryPerformanceCounter(&litmp);//获得初始值 //测试时间开始 qt1 = litmp.QuadPart; int size = Vec1.size(); for (int i = 0; i
()(Vec1[i], unexpectedInt)) { Vec2.push_back(Vec1[i]); } else continue; } QueryPerformanceCounter(&litmp);//获得终止值 qt2 = litmp.QuadPart; dfm = (double)(qt2 - qt1); dft = dfm / dff;//获得对应的时间值 cout<<"下标方法测试时间为:" << dft << endl;}//使用迭代器void FiltArray1(vector
&Vec1, vector
&Vec2, const int unexpectedInt){ //测试时间 LARGE_INTEGER litmp; LONGLONG qt1, qt2; double dft, dff, dfm; QueryPerformanceFrequency(&litmp);//获得时钟频率 dff = (double)litmp.QuadPart; QueryPerformanceCounter(&litmp);//获得初始值 //测试时间开始 qt1 = litmp.QuadPart; //查找第一个不为0的数值 vector
::iterator result = find_if(Vec1.begin(), Vec1.end(), bind2nd(not_equal_to
(), unexpectedInt)); while (result != Vec1.end()) { Vec2.push_back(*result); //result结果的下一位开始查找不为0的数 result = find_if(result + 1, Vec1.end(), bind2nd(not_equal_to
(), unexpectedInt)); } QueryPerformanceCounter(&litmp);//获得终止值 qt2 = litmp.QuadPart; dfm = (double)(qt2 - qt1); dft = dfm / dff;//获得对应的时间值 cout << "迭代器方法测试时间为:" << dft << endl;}bool IsNotZero(int unexpectedInt){ return (unexpectedInt == 0);}void FiltArray2(vector
&Vec1, vector
&Vec2, const int unexpectedInt){ //测试时间 LARGE_INTEGER litmp; LONGLONG qt1, qt2; double dft, dff, dfm; QueryPerformanceFrequency(&litmp);//获得时钟频率 dff = (double)litmp.QuadPart; QueryPerformanceCounter(&litmp);//获得初始值 //测试时间开始 qt1 = litmp.QuadPart; // C++ 11 里的函数 //《effective STL》 :尽量用算法替代手写循环;查找少不了循环遍历 remove_copy_if(Vec1.begin(), Vec1.end(), back_inserter(Vec2), IsNotZero); QueryPerformanceCounter(&litmp);//获得终止值 qt2 = litmp.QuadPart; dfm = (double)(qt2 - qt1); dft = dfm / dff;//获得对应的时间值 cout << "利用拷贝算法测试时间为:" << dft << endl;}void ShowArray(vector
&Vec){ vector
::iterator it = Vec.begin(); //it 是一个地址 while (it < Vec.end()) { cout << *it << endl; it++; }}void ClearArray(vector
&Vec){ vector
::iterator it = Vec.begin(); //清空数据 while (it < Vec.end()) { it = Vec.erase(it); }}int main(){ vector
v1{ 0, 0, 30, 20, 0, 0, 0, 0, 10, 0 }; vector
v2; const int unexpectedInt = 0; /* 方案一: 利用数组下标sanzho */ FiltArray0(v1, v2, unexpectedInt); cout << "利用数组下标方案,V2中数据为:" << endl; ShowArray(v2); /* 方案二: 利用迭代器 */ ClearArray(v2); FiltArray1(v1, v2, unexpectedInt); cout << "利用迭代器方案,V2中数据为:" << endl; ShowArray(v2); /* 方案三: 利用拷贝算法 */ ClearArray(v2); FiltArray2(v1, v2, unexpectedInt); cout << "利用拷贝算法,V2中数据为:" << endl; ShowArray(v2); return 0;}

 

 

三、效率对比

运行了几次,来观察实际运行时间。

 

 

等等。综合发现DEBUG模式下。

  第三种方案的运行时间最长,代码量最少。

  第二种方案的运行时间最长,更为通用。

  第一种方案的运行时间居中,但不通用。

 

Release模式下,数据如下图所示:

 

 

 

 

 

 

数据整理,对比如下表所示:

  下标操作 迭代器操作 remove_copy_if()算法操作
Debug统计数据一 0.000015762 0.0000395882 0.00000733115
Debug统计数据二 0.00000879738 0.000023093 0.00000806426
Debug统计数据三 0.00000879738 0.0000373889 0.00000733115
Release模式一 0.00000146623 0 0
Release模式二 0.00000146623 0.000000366557 0.000000366557
Release模式三 0.00000146623 0.00000109967 0.000000366557

 

对比发现,Release版本经过优化后,模式使用迭代器耗费的时间降低了不少。此时竟然比下标运行时间还要短!

 

四、进一步的思考与总结

1)效率相比自己手写更高;STL的代码都是C++专家写出来的,专家写出来的代码在效率上很难超越; 

2)千万注意要使用++iter 不能使用iter++,iter++ 是先拷贝一份值,再进行++,效率很低;

3)通过分析,用algorithm+functional进行遍历效率最高。而且 下标索引的方式总是会效率高于迭代器方式。

那么为什么迭代器速率比较慢呢?
其中的一位网友的解释:std::vector::end()的原型
iterator end() _NOEXCEPT        {    // return iterator for end of mutable sequence        return (iterator(this->_Mylast, this));        }    const_iterator end() const _NOEXCEPT        {    // return iterator for end of nonmutable sequence        return (const_iterator(this->_Mylast, this));        }

 

在Debug模式下,每次判断itr != Vec.end()的时候,都要进行重新构造一个迭代器并进行返回,这样当然降低的效率。
 
但同时,迭代器具有良好的通用性,在效率要求不是那么高的情况下,其实用哪个都无所谓!
 
 
4)push_back耗费时间复杂度分析,有一篇文章解释的清晰明了,估计看了就没明白为什么时间会差距这么大。
   (转帖) http://blog.sina.com.cn/s/blog_a2a6dd380102w73e.html
内容如下:

      vectorSTL中的一种序列式容器,采用的数据结构为线性连续空间,它以两个迭代器 start 和 finish 分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage 指向整块连续空间(含备用空间)的尾端,结构如下所示:

    template Alloc = alloc>

    class vector {

  ​  ...

    protected:

        iterator start;                     // 表示目前使用空间的头

        iterator finish;                   // 表示目前使用空间的尾

        iterator end_of_storage;  // 表示可用空间的尾​

     ...};​

     我们在使用 vector 时​,最常使用的操作恐怕就是插入操作了(push_back),那么当执行该操作时,该函数都做了哪些工作呢?

    该函数首先检查是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器 finish,使 vector 变大。如果没有备用空间了,就扩充空间,重新配置、移动数据,释放原空间。​

    其中​判断是否有备用空间,就是判断  finish 是否与 end_of_storage 相等.如果 

finish != end_of_storage,说明还有备用空间,否则已无备用空间。

    当执行 push_back 操作,该 vector 需要分配更多空间时,它的容量(capacity)会增大到原来的 倍。​现在我们来均摊分析方法来计算 push_back 操作的时间复杂度。

    假定有 n 个元素,倍增因子为 m。那么完成这 n 个元素往一个 vector 中的push_back​操作,需要重新分配内存的次数大约为 logm(n),第 i 次重新分配将会导致复制 m^i (也就是当前的vector.size() 大小)个旧空间中元素,因此 n 次 push_back 操作所花费的总时间约为 n*m/(m - 1):

 

时间复杂度计算
 

    很明显这是一个等比数列.那么 n 个元素,n 次操作,每一次操作需要花费时间为 m / (m - 1),这是一个常量.

    所以,我们采用均摊分析的方法可知,vector 中 push_back 操作的时间复杂度为常量时间.​

 
 
 
 
 
 
 
 
 
 
 
           
                                             2016.08.22

 

 

 

 

 

 

 

 

                                  

 

转载于:https://www.cnblogs.com/xuhe/p/5795193.html

你可能感兴趣的文章
基于MVC+EasyUI的Web开发框架经验总结(4)--使用图表控件Highcharts
查看>>
vs2015 xamarin 添加智能感知
查看>>
call to member function bind_param() on boolean...........
查看>>
刘启成_补充知识:awk:报告生成器
查看>>
Linux LVM逻辑卷配置过程详解
查看>>
【技术分享】VSAN如何处理磁盘或主机故障
查看>>
OS快捷键
查看>>
linux内核中Kconfig和Makefile 详解
查看>>
ASP.NET 使用List<T>.Remove 不生效
查看>>
Nginx的第三方模块ngx-fancyindex安装
查看>>
TCP有限状态机
查看>>
XenServer常用Debug问题的命令介绍
查看>>
算法分析-快速排序QUICK-SORT
查看>>
Web服务基础六之编译安装配置RHEL+Apache+MySQL+PHP+ZendOptimize
查看>>
log4net 使用
查看>>
通过bat文件运行jar包程序
查看>>
关于hive RegexSerDe的源码分析
查看>>
V$INSTANCE视图
查看>>
OpenCart之侧边浮动联系我们表单(Side Contact Us Form)
查看>>
PureWhite OpenCart 商城自适应主题模板 ABC-0009
查看>>