[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 -- 这三个函数都运用于有序区间(当然这也是运用二分查找的前提),以 ...
随机推荐
- 原生js大总结八
071.如何组织事件冒泡 利用事件对象属性:stopPropagation 和 cancelBubble stopPropagetion是一个方法:e.stopPropagetion(); ...
- CODEVS——T 1993 草地排水 USACO
http://codevs.cn/problem/1993/ 时间限制: 2 s 空间限制: 256000 KB 题目等级 : 钻石 Diamond 题解 查看运行结果 题目描述 De ...
- C++ 多线程阻塞 (多线程同步)(MsgWaitForMultipleObjects)(连着消息一起控制,牛)
在主线程定要禁止使用waitforsingleobject(),原因是会阻塞主线程的消息循环,所以必须使用另一种 MsgWaitForMultipleObjects,即可以让消息通过,下面就是一个基于 ...
- RAC RMAN 备份 RMAN-03009 ORA-19504 ORA-27040 RMAN-06012 channel c3 not allocated 错误分析
备份Shell 脚本如下: ######################################################################## ## RAC_hot_da ...
- Swiper+ ejs模板引擎+ iScroll插件知识总结
一. Swiper swiper是一个应用于移动端的动画插件,原理类似于轮播图 官网 http://www.swiper.com.cn/# html结构 <div class="swi ...
- 函数的引用透明性(referential transparency)
1. 基础 初学程序设计时,比较容易混淆的两个概念是数学函数(math function)和程序中使用的函数. 在数学函数中 y=f(x),一个输入值有固定的输出值.例如,无论计算多少次,sinπ 的 ...
- Android Studio设置图片背景及主题设置
因为Android Studio是基于IDEA的,所以IDEA里面能用的插件Android Studio也能用,这次图片背景是依赖IDEA下的一个插件,名为BackgroundImage的插件,用户可 ...
- stm32的dma缓冲区长度,,存放数据数组会不会冲掉
- 《JavaScript高级程序设计》笔记——第一章到第三章
2019年,新年伊始,我打算好好重读一下<JavaScript高级程序设计>这本前端必备经典书.每天半小时. 以下内容摘自<JavaScript高级程序设计> 2019-2-1 ...
- Android layer-list的属性和使用具体解释
Android layer-list的属性和使用具体解释.layer-list是用来多个图层堆叠显示的,借这个特性能够做一些特别的效果(比方:阴影.以下的效果等),也能够投机取巧. 1.代码片 < ...