[GeekBand] STL vector 查找拷贝操作效率分析
本文参考文献::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<T> v1 ; //构造函数
vector<T> v2(v1) ; //拷贝构造函数
vector<T> v2 =v1 ; //拷贝赋值
vector<T> v1{ , , , , , , , , , }; //初始化
本文中采用最基本的方法进行初始化。
3、Vector基本操作
vector<int> vec;
vector<int> vec2;
vec.push_back(t); //向v的尾端添加元素t
vec.empty();
vec.size();
vec == vec2; //相等类似的操作都有
四、迭代器
简单的理解:类似于指针。如下面的一句话,迭代器有一个优点,那就是所有的标准库容器都可以使用迭代器,具有极强的通用性。
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
template<class _Ty = void>
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 <vector>
#include <functional>
#include <algorithm>
#include <iostream>
#include <iterator>
#include<windows.h>
#include <Mmsystem.h> using namespace std; void ShowArray(vector<int> &Vec)
{
vector<int>::iterator it = Vec.begin();
//it 是一个地址
while (it < Vec.end())
{
cout << *it << endl;
it++;
} };
int _tmain(int argc, _TCHAR* argv[])
{
int a[] = { , , , };
std::vector<int> arr(a, a + ); // 移除所有小于100的元素
arr.erase(std::remove_if(arr.begin(), arr.end(),
std::bind2nd(std::less< int>(), )), arr.end());
ShowArray(arr);
/**************************************/
printf("*******************************\n");
int b[] = { , , , };
std::vector<int> arr2(b, b + );
// 移除所有大于100的元素
arr2.erase(std::remove_if(arr2.begin(), arr2.end(),
std::bind1st(std::less< int>(), )), arr2.end());
ShowArray(arr2);
}
本例中因为仅判断是否为0 ,所有采用bind1st 和 bind2nd都一样。
3、remove_copy_if
remove_copy_if() 函数原型
template<class _InIt,class _OutIt,class _Pr>
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 == );
}
二、三种不同方法来实现将查找拷贝操作
完成代码如下: 开发环境 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 <vector>
#include <functional>
#include <algorithm>
#include <iostream>
#include <iterator>
#include<windows.h>
#include <Mmsystem.h>
using namespace std;
#pragma comment( lib,"winmm.lib" ) //利用下标的方法 void FiltArray0(vector<int> &Vec1, vector<int> &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 = ; i<size; i++)
{
if (not_equal_to<int>()(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<int> &Vec1, vector<int> &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<int>::iterator result = find_if(Vec1.begin(), Vec1.end(), bind2nd(not_equal_to<int>(), unexpectedInt));
while (result != Vec1.end())
{
Vec2.push_back(*result);
//result结果的下一位开始查找不为0的数
result = find_if(result + , Vec1.end(), bind2nd(not_equal_to<int>(), unexpectedInt));
} QueryPerformanceCounter(&litmp);//获得终止值
qt2 = litmp.QuadPart;
dfm = (double)(qt2 - qt1);
dft = dfm / dff;//获得对应的时间值
cout << "迭代器方法测试时间为:" << dft << endl; } bool IsNotZero(int unexpectedInt)
{
return (unexpectedInt == );
} void FiltArray2(vector<int> &Vec1, vector<int> &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<int> &Vec)
{
vector<int>::iterator it = Vec.begin();
//it 是一个地址
while (it < Vec.end())
{
cout << *it << endl;
it++;
} } void ClearArray(vector<int> &Vec)
{
vector<int>::iterator it = Vec.begin();
//清空数据
while (it < Vec.end())
{
it = Vec.erase(it);
} } int main()
{
vector<int> v1{ , , , , , , , , , };
vector<int> v2;
const int unexpectedInt = ;
/*
方案一: 利用数组下标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 ;
}
三、效率对比
运行了几次,来观察实际运行时间。



等等。综合发现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进行遍历效率最高。而且 下标索引的方式总是会效率高于迭代器方式。
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));
}
vector是STL中的一种序列式容器,采用的数据结构为线性连续空间,它以两个迭代器 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)会增大到原来的 m 倍。现在我们来均摊分析方法来计算 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 操作的时间复杂度为常量时间.

[GeekBand] STL vector 查找拷贝操作效率分析的更多相关文章
- STL vector用法介绍
STL vector用法介绍 介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和f ...
- STL vector 用法介绍
介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和for_each()中的使用.通 ...
- STL vector使用方法介绍
介绍 这篇文章的目的是为了介绍std::vector,怎样恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和for_each()中的使用.通 ...
- C++STL vector详解(杂谈)
介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if()和for_each()中的使用.通 ...
- C++ stl vector介绍
转自: STL vector用法介绍 介绍 这篇文章的目的是为了介绍std::vector,如何恰当地使用它们的成员函数等操作.本文中还讨论了条件函数和函数指针在迭代算法中使用,如在remove_if ...
- 【C++】朝花夕拾——STL vector
STL之vector篇 N久之前是拿C的数组实现过vector中的一些简单功能,什么深拷贝.增删查找之类的,以为vector的实现也就是这样了,现在想想真是...too young too naive ...
- STL vector
STL vector vector是线性容器,它的元素严格的按照线性序列排序,和动态数组很相似,和数组一样,它的元素存储在一块连续的存储空间中,这也意味着我们不仅可以使用迭代器(iterator)访问 ...
- STL vector+sort排序和multiset/multimap排序比较
由 www.169it.com 搜集整理 在C++的STL库中,要实现排序可以通过将所有元素保存到vector中,然后通过sort算法来排序,也可以通过multimap实现在插入元素的时候进行排序.在 ...
- STL 二分查找三兄弟(lower_bound(),upper_bound(),binary_search())
一:起因 (1)STL中关于二分查找的函数有三个:lower_bound .upper_bound .binary_search -- 这三个函数都运用于有序区间(当然这也是运用二分查找的前提),以 ...
随机推荐
- jtag引脚
如果不能下载,可能原因也许是电量不足了... 在电力不足的时候,仿真也不能进行... ///////////////////////////////////////////////////////// ...
- 量化交易中VWAP/TWAP算法的基本原理和简单源码实现(C++和python)(转)
量化交易中VWAP/TWAP算法的基本原理和简单源码实现(C++和python) 原文地址:http://blog.csdn.net/u012234115/article/details/728300 ...
- 线程TLAB区域的深入剖析
(1) 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的 (2) Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程 ...
- [Nuxt] Add Arrays of Data to the Vuex Store and Display Them in Vue.js Templates
You add array of todos to the store simply by adding them to the state defined in your store/index.j ...
- kafka集群原理介绍
目录 kafka集群原理介绍 (一)基础理论 二.配置文件 三.错误处理 kafka集群原理介绍 @(博客文章)[kafka|大数据] 本系统文章共三篇,分别为 1.kafka集群原理介绍了以下几个方 ...
- 【u237】分数化小数
Time Limit: 1 second Memory Limit: 128 MB [问题描述] 写一个程序,输入一个形如N/D的分数(N是分子,D是分母),输出它的小数形式.如果小数有循环节的话,把 ...
- jQuery获取多种input值的方法(转)
获取input的checked值是否为true: 第一种: if($("input[name=item][value='val']").attr('checked')==true) ...
- Canvas基础知识总结之中的一个
canvas的HTML语法: <canvas> Canvas not supported </canvas> 上面这句代码中内容部分所含的文本,这种文本的叫法"后备内 ...
- LIGO找到首个超越广义相对论的证据?
转自 麻省理工科技评论 原文 LIGO找到首个超越广义相对论的证据? 1915年,爱因斯坦根据简单的原理提出广义相对论,极大地改变了人们的时空观.广义相对论不仅解释了牛顿理论无法解释的现象,还做出许多 ...
- iOS 【UIKit-UIPageControl利用delegate定位圆点位置 之 四舍五入小技巧】
在UIScrollView中会加入UIPageControl作为页码标识,能够让用户清楚的知道当前的页数.我们须要优化的一点是让pageControl的小圆点精确的跟着scrollView而定位.我们 ...