本文参考文献::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的初始化

有几种基本初始化方法:

  1. vector<T> v1 ; //构造函数
  2. vector<T> v2(v1) ; //拷贝构造函数
  3. vector<T> v2 =v1 ; //拷贝赋值
  4. vector<T> v1{ , , , , , , , , , }; //初始化

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

3、Vector基本操作

  1. vector<int> vec;
  2. vector<int> vec2;
  3. vec.push_back(t); //向v的尾端添加元素t
  4. vec.empty();
  5. vec.size();
  6. vec == vec2; //相等类似的操作都有

四、迭代器

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

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

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

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

类似的函数有equal_to

  1. template<class _Ty = void>
  2. struct not_equal_to
  3. : public binary_function<_Ty, _Ty, bool>
  4. { // functor for operator!=
  5. bool operator()(const _Ty& _Left, const _Ty& _Right) const
  6. { // apply operator!= to operands
  7. return (_Left != _Right);
  8. }
  9. };

2、bind1st 和 bind2nd

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

简单的两个例子:

  1. #include "stdafx.h"
  2. #include <vector>
  3. #include <functional>
  4. #include <algorithm>
  5. #include <iostream>
  6. #include <iterator>
  7. #include<windows.h>
  8. #include <Mmsystem.h>
  9.  
  10. using namespace std;
  11.  
  12. void ShowArray(vector<int> &Vec)
  13. {
  14. vector<int>::iterator it = Vec.begin();
  15. //it 是一个地址
  16. while (it < Vec.end())
  17. {
  18. cout << *it << endl;
  19. it++;
  20. }
  21.  
  22. };
  23. int _tmain(int argc, _TCHAR* argv[])
  24. {
  25. int a[] = { , , , };
  26. std::vector<int> arr(a, a + );
  27.  
  28. // 移除所有小于100的元素
  29. arr.erase(std::remove_if(arr.begin(), arr.end(),
  30. std::bind2nd(std::less< int>(), )), arr.end());
  31. ShowArray(arr);
  32. /**************************************/
  33. printf("*******************************\n");
  34. int b[] = { , , , };
  35. std::vector<int> arr2(b, b + );
  36. // 移除所有大于100的元素
  37. arr2.erase(std::remove_if(arr2.begin(), arr2.end(),
  38. std::bind1st(std::less< int>(), )), arr2.end());
  39. ShowArray(arr2);
  40. }

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

3、remove_copy_if

remove_copy_if() 函数原型

  1. template<class _InIt,class _OutIt,class _Pr>
  2. inline _OutIt remove_copy_if(_InIt _First, _InIt _Last,
  3. _OutIt _Dest, _Pr _Pred)
  4. {
  5. // copy omitting each element satisfying _Pred
  6. _DEBUG_RANGE(_First, _Last);
  7. _DEBUG_POINTER(_Dest);
  8. _DEBUG_POINTER(_Pred);
  9. return (_Remove_copy_if(_Unchecked(_First), _Unchecked(_Last),
  10. _Dest, _Pred,
  11. _Is_checked(_Dest)));
  12. }

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

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

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

  1. bool IsNotZero(int unexpectedInt)
  2. {
  3. return (unexpectedInt == );
  4. }

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

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

  1. // Vector.cpp : 定义控制台应用程序的入口点。
  2. //
  3.  
  4. /*
  5. 问题:
  6. 给定一个 vector:v1 = [0, 0, 30, 20, 0, 0, 0, 0, 10, 0],
  7. 希望通过 not_equal_to 算法找到到不为零的元素,并复制到另一个 vector: v2
  8. */
  9.  
  10. /*
  11. 要点一:
  12. 第一步、利用not_equal_to函数进行数值比较,区分vector某一元素是否是非0数据
  13. 第二步、查找所有的非0元素
  14. 第三步、将所有非0元素拷贝到v2中来
  15. 要点二:效率问题
  16. 测试结果:
  17. 利用下标耗费时间最少,运行速度比较快,但不通用(vector可以利用下标)。
  18. 利用迭代器耗费时间较多,但更为通用。
  19. 利用C++ 11 remove_copy_if() algorithm 进行分析
  20.  
  21. 总结:
  22. C++ 11 remove_copy_if() algorithm 代码最少,效率最高。
  23.  
  24. */
  25.  
  26. #include "stdafx.h"
  27. #include <vector>
  28. #include <functional>
  29. #include <algorithm>
  30. #include <iostream>
  31. #include <iterator>
  32. #include<windows.h>
  33. #include <Mmsystem.h>
  34. using namespace std;
  35. #pragma comment( lib,"winmm.lib" )
  36.  
  37. //利用下标的方法
  38.  
  39. void FiltArray0(vector<int> &Vec1, vector<int> &Vec2, const int unexpectedInt)
  40. {
  41. //测试时间
  42. LARGE_INTEGER litmp;
  43. LONGLONG qt1, qt2;
  44. double dft, dff, dfm;
  45. QueryPerformanceFrequency(&litmp);//获得时钟频率
  46. dff = (double)litmp.QuadPart;
  47. QueryPerformanceCounter(&litmp);//获得初始值
  48. //测试时间开始
  49. qt1 = litmp.QuadPart;
  50.  
  51. int size = Vec1.size();
  52. for (int i = ; i<size; i++)
  53. {
  54. if (not_equal_to<int>()(Vec1[i], unexpectedInt))
  55. {
  56. Vec2.push_back(Vec1[i]);
  57. }
  58. else
  59. continue;
  60. }
  61.  
  62. QueryPerformanceCounter(&litmp);//获得终止值
  63. qt2 = litmp.QuadPart;
  64. dfm = (double)(qt2 - qt1);
  65. dft = dfm / dff;//获得对应的时间值
  66. cout<<"下标方法测试时间为:" << dft << endl;
  67.  
  68. }
  69.  
  70. //使用迭代器
  71. void FiltArray1(vector<int> &Vec1, vector<int> &Vec2, const int unexpectedInt)
  72. {
  73. //测试时间
  74. LARGE_INTEGER litmp;
  75. LONGLONG qt1, qt2;
  76. double dft, dff, dfm;
  77. QueryPerformanceFrequency(&litmp);//获得时钟频率
  78. dff = (double)litmp.QuadPart;
  79. QueryPerformanceCounter(&litmp);//获得初始值
  80. //测试时间开始
  81. qt1 = litmp.QuadPart;
  82.  
  83. //查找第一个不为0的数值
  84. vector<int>::iterator result = find_if(Vec1.begin(), Vec1.end(), bind2nd(not_equal_to<int>(), unexpectedInt));
  85. while (result != Vec1.end())
  86. {
  87. Vec2.push_back(*result);
  88. //result结果的下一位开始查找不为0的数
  89. result = find_if(result + , Vec1.end(), bind2nd(not_equal_to<int>(), unexpectedInt));
  90. }
  91.  
  92. QueryPerformanceCounter(&litmp);//获得终止值
  93. qt2 = litmp.QuadPart;
  94. dfm = (double)(qt2 - qt1);
  95. dft = dfm / dff;//获得对应的时间值
  96. cout << "迭代器方法测试时间为:" << dft << endl;
  97.  
  98. }
  99.  
  100. bool IsNotZero(int unexpectedInt)
  101. {
  102. return (unexpectedInt == );
  103. }
  104.  
  105. void FiltArray2(vector<int> &Vec1, vector<int> &Vec2, const int unexpectedInt)
  106. {
  107.  
  108. //测试时间
  109. LARGE_INTEGER litmp;
  110. LONGLONG qt1, qt2;
  111. double dft, dff, dfm;
  112. QueryPerformanceFrequency(&litmp);//获得时钟频率
  113. dff = (double)litmp.QuadPart;
  114. QueryPerformanceCounter(&litmp);//获得初始值
  115. //测试时间开始
  116. qt1 = litmp.QuadPart;
  117.  
  118. // C++ 11 里的函数
  119. //《effective STL》 :尽量用算法替代手写循环;查找少不了循环遍历
  120. remove_copy_if(Vec1.begin(), Vec1.end(), back_inserter(Vec2), IsNotZero);
  121.  
  122. QueryPerformanceCounter(&litmp);//获得终止值
  123. qt2 = litmp.QuadPart;
  124. dfm = (double)(qt2 - qt1);
  125. dft = dfm / dff;//获得对应的时间值
  126. cout << "利用拷贝算法测试时间为:" << dft << endl;
  127.  
  128. }
  129.  
  130. void ShowArray(vector<int> &Vec)
  131. {
  132. vector<int>::iterator it = Vec.begin();
  133. //it 是一个地址
  134. while (it < Vec.end())
  135. {
  136. cout << *it << endl;
  137. it++;
  138. }
  139.  
  140. }
  141.  
  142. void ClearArray(vector<int> &Vec)
  143. {
  144. vector<int>::iterator it = Vec.begin();
  145. //清空数据
  146. while (it < Vec.end())
  147. {
  148. it = Vec.erase(it);
  149. }
  150.  
  151. }
  152.  
  153. int main()
  154. {
  155. vector<int> v1{ , , , , , , , , , };
  156. vector<int> v2;
  157. const int unexpectedInt = ;
  158. /*
  159. 方案一: 利用数组下标sanzho
  160. */
  161. FiltArray0(v1, v2, unexpectedInt);
  162. cout << "利用数组下标方案,V2中数据为:" << endl;
  163. ShowArray(v2);
  164. /*
  165. 方案二: 利用迭代器
  166. */
  167. ClearArray(v2);
  168. FiltArray1(v1, v2, unexpectedInt);
  169. cout << "利用迭代器方案,V2中数据为:" << endl;
  170. ShowArray(v2);
  171. /*
  172. 方案三: 利用拷贝算法
  173. */
  174. ClearArray(v2);
  175. FiltArray2(v1, v2, unexpectedInt);
  176. cout << "利用拷贝算法,V2中数据为:" << endl;
  177. ShowArray(v2);
  178. return ;
  179. }

三、效率对比

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

等等。综合发现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()的原型
  1. iterator end() _NOEXCEPT
  2. { // return iterator for end of mutable sequence
  3. return (iterator(this->_Mylast, this));
  4. }
  5.  
  6. const_iterator end() const _NOEXCEPT
  7. { // return iterator for end of nonmutable sequence
  8. return (const_iterator(this->_Mylast, this));
  9. }
在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

[GeekBand] STL vector 查找拷贝操作效率分析的更多相关文章

  1. STL vector用法介绍

    STL vector用法介绍 介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和f ...

  2. STL vector 用法介绍

    介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和for_each()中的使用.通 ...

  3. STL vector使用方法介绍

    介绍 这篇文章的目的是为了介绍std::vector,怎样恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和for_each()中的使用.通 ...

  4. C++STL vector详解(杂谈)

    介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和for_each()中的使用.通 ...

  5. C++ stl vector介绍

    转自: STL vector用法介绍 介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if ...

  6. 【C++】朝花夕拾——STL vector

    STL之vector篇 N久之前是拿C的数组实现过vector中的一些简单功能,什么深拷贝.增删查找之类的,以为vector的实现也就是这样了,现在想想真是...too young too naive ...

  7. STL vector

    STL vector vector是线性容器,它的元素严格的按照线性序列排序,和动态数组很相似,和数组一样,它的元素存储在一块连续的存储空间中,这也意味着我们不仅可以使用迭代器(iterator)访问 ...

  8. STL vector+sort排序和multiset/multimap排序比较

    由 www.169it.com 搜集整理 在C++的STL库中,要实现排序可以通过将所有元素保存到vector中,然后通过sort算法来排序,也可以通过multimap实现在插入元素的时候进行排序.在 ...

  9. STL 二分查找三兄弟(lower_bound(),upper_bound(),binary_search())

    一:起因 (1)STL中关于二分查找的函数有三个:lower_bound .upper_bound .binary_search  -- 这三个函数都运用于有序区间(当然这也是运用二分查找的前提),以 ...

随机推荐

  1. C# 映射

    public class Myclass1 { private int m_Count = 100; public string love{get;set;} public int Count { g ...

  2. 洛谷 P1551 亲戚

                      洛谷 P1551 亲戚 题目背景 若某个家族人员过于庞大,要判断两个是否是亲戚,确实还很不容易,现在给出某个亲戚关系图,求任意给出的两个人是否具有亲戚关系. 题目描 ...

  3. MyCat:对MySQL数据库进行分库分表

    本篇前提: mycat配置正确,且能正常启动. 1.schema.xml <table>标签: dataNode -- 分片节点指定(取值:dataNode中的name属性值) rule ...

  4. OpenWrt配置绿联的usb转Ethernet网口驱动

    这个选择kernel modules中的kmod-usb-net-asix 须要加入网络设备接口.相似建立一个vlan,配置下防火墙之类的.

  5. Java基础学习总结(51)——JAVA分层理解

    service是业务层  action层即作为控制器 DAO (Data Access Object) 数据访问   1.JAVA中Action层, Service层 ,modle层 和 Dao层的功 ...

  6. EularProject 41:最长的n位Pandigital素数问题

    Pandigital prime Problem 41 We shall say that an n-digit number is pandigital if it makes use of all ...

  7. 表单提交数据格式form data

    前言: 最近遇到的最多的问题就是表单提交数据格式问题了. 常见的三种表单提交数据格式,分别举例说明:(项目是vue的框架) 1.application/x-www-form-urlencoded 提交 ...

  8. Android(Lollipop/5.0) Material Design(四) 创建列表和卡片

    Material Design系列 Android(Lollipop/5.0)Material Design(一) 简单介绍 Android(Lollipop/5.0)Material Design( ...

  9. [Javascript Natural] Break up language strings into parts using Natural

    A part of Natural Language Processing (NLP) is processing text by “tokenizing” language strings. Thi ...

  10. iOS Universal Static Framework 手动转 XCode Cocoa Framework

    不须要又一次创建Project,手动改动project设置. 第一步:在Project文件里,改动type,去掉static 1. 搜索wrapper.framework.static,去掉stati ...