程序设计与算法(三)C++面向对象程序设计笔记 第九周 标准模板库STL(二)

笔记按照中国大学MOOC上北京大学郭炜老师主讲的程序设计与算法(三)C++面向对象程序设计所作,B站上也有资源。原课程链接如下:

程序设计与算法(三)C++面向对象程序设计

其他各章节链接如下:

程序设计与算法(三)C++面向对象程序设计笔记 第一周 从C到C++

程序设计与算法(三)C++面向对象程序设计笔记 第二周 类和对象基础

程序设计与算法(三)C++面向对象程序设计笔记 第三周 类和对象提高

程序设计与算法(三)C++面向对象程序设计笔记 第四周 运算符重载

程序设计与算法(三)C++面向对象程序设计笔记 第五周 继承

程序设计与算法(三)C++面向对象程序设计笔记 第六周 多态

程序设计与算法(三)C++面向对象程序设计笔记 第七周 输入输出和模板

程序设计与算法(三)C++面向对象程序设计笔记 第八周 标准模板库STL(一)

程序设计与算法(三)C++面向对象程序设计笔记 第九周 标准模板库STL(二)

程序设计与算法(三)C++面向对象程序设计笔记 第十周 C++11新特性和C++高级主题

其他各科笔记汇总

标准模板库 STL(二)

set 和 multiset

关联容器

set, multiset, map, multimap

内部元素从小到大有序排列,什么是小什么是大可以自己定义,新元素插入的位置取决于它的值,查找速度快

除了各容器都有的函数外,还支持以下成员函数:

  • find:查找等于某个值的元素( x 小于 y 和 y 小于 x 同时不成立即为相等,什么是小于可以自己定义)
  • lower_bound:查找某个下界
  • upper_bound:查找某个上界
  • equal_range:同时查找上界和下界
  • count:计算等于某个值的元素个数(x 小于 y 和 y 小于 x 同时不成立即为相等)
  • insert:用以插入一个元素或一个区间
预备知识:pair 模板

被写成 struct 形式,这样里面所有的成员不需要声明就都是公有的

template<class _T1, class _T2>
struct pair
{
    typedef _T1 first_type;
    typedef _T2 second_type;
    _T1 first;
    _T2 second;
    pair(): first(), second() { }
    pair(const _T1& __a, const _T2& __b)
    : first(__a), second(__b) { }
    template<class _U1, class _U2>
        pair(const pair<_U1, _U2>& __p)
             : first(__p.first), second(__p.second) { }
};

看一下 pair 模板的几个构造函数:

pair(): first(), second() { }

无参构造函数。如果 first 和 second 是成员对象,都用无参构造函数初始化,如果 first 和 second 是基本类型的变量,后面加一个圆括号从语法上讲也没有问题

 

pair(const _T1& __a, const _T2& __b) : first(__a), second(__b) { }

有两个参数 __a 和 __b,分别是 const _T1& 和 const _T2& 类型。在初始化列表里面分别用 __a 和 __b 去初始化 first 和 second

 

template<class _U1, class _U2>
pair(const pair<_U1, _U2>& __p) : first(__p.first), second(__p.second) { }

这个函数模板仅在要用到这种形式的构造函数时才会被实例化

这个函数模板有两个类型参数 _U1 和 _U2,构造函数的参数是另外一个 pair 模板类的对象 __p,__p 里面 first 和 second 成员变量分别为 _U1 和 _U2 类型,在初始化列表里分别用 __p.first 和 __p.second 初始化 first 和 second

 

map/multimap 容器里放着的都是 pair 模版类的对象,且按 first 从小到大排序

第三个构造函数用法示例:

pair<int, int> p(pair<double, double>(5.5, 4.6));
// p.first = 5, p.second = 4

定义 pair<int, int> 模版类的对象 p,p 后面的括号里给出了构造函数的参数。 由于参数只有一个,所以肯定调用了第三个构造函数

这个唯一的参数是另外一个模板类 pair<double, double> 的没有名字的临时对象,这个临时对象的 first 和 second 成员变量分别是double 类型的5.5和4.6。分别用5.5和4.6初始化 p.first 和 p.second,初始化的结果会把浮点数去尾取整

multiset
template<class Key, class Pred = less<Key>, class A = allocator<Key> >
class multiset { ...... };

类型参数 Key 代表 multiset 里面放的元素的类型,Pred 规定 multiset 里面的元素按什么规则比大小

 

 

Pred 类型的变量决定了 multiset 中的元素,“一个比另一个小”是怎么定义的。
multiset 运行过程中,比较两个元素 x,y 的大小的做法,就是生成一个 Pred 类型的变量,假定为 op,若表达式 op(x,y) 返回值为 true,则 x 比 y 小,也就是 x 要排在 y 的前面

op(x, y) 表达式要有定义,op 得是一个函数指针或者函数对象

Pred 的缺省类型是 less<Key>

 

less 函数对象类模板的定义:

template<class T>
struct less : public binary_function<T, T, bool>
{ bool operator()(const T& x, const T& y) { return x < y ; } const; };
//less模板是靠 < 来比较大小的

operator() 成员函数 x < y 表达式返回值为 true,则 x < y

 

第三个类型参数不重要,暂时可以不管

multiset 的成员函数

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rbA1Ju92-1666239138148)(C++ 面向对象程序设计.assets/image-20221017202858062.png)]

find、insert(插入一个元素)、lower_bound、upper_bound 时间复杂度都是 O ( l o g 2 n ) O(log_2n) O(log2n)。insert(插入一个区间)时间复杂度跟这个区间有多少个元素有关

使用 erase 时要注意如果一个迭代器指向的元素被删掉了,这个迭代器就会失效不能再使用

multiset 的用法示例
#include <set>
using namespace std;
class A { };
int main() {
    multiset<A> a;
    a.insert( A()); //error
}

multiset <A> a; 就等价于 multiset<A, less<A>> a;

插入元素时,multiset 会将被插入元素和已有元素进行比较。由于 less 模板是用 < 进行比较的,所以,这都要求 A 的对象能用 < 比较,即适当重载了 <

 

 

#include <iostream>
#include <set> //使用multiset须包含此文件
using namespace std;
template <class T>
void Print(T first, T last)
{ 
    for(;first != last ; ++first) cout << * first << " ";
    cout << endl;
}

class A {
    private:
        int n;
    public:
        A(int n_ ) { n = n_; }
        friend bool operator< (const A & a1, const A & a2) 
        { return a1.n < a2.n; }
        friend ostream & operator<< (ostream & o, const A & a2)
        { o << a2.n; return o; }
        friend class MyLess;
};

struct MyLess {
    bool operator()( const A & a1, const A & a2)
    //按个位数比大小
    { return ( a1.n % 10 ) < (a2.n % 10); }
};

typedef multiset<A> MSET1; //MSET1用 "<" 比较大小
typedef multiset<A,MyLess> MSET2; //MSET2用 MyLess::operator() 比较大小

int main()
{
    const int SIZE = 6;
    A a[SIZE] = { 4,22,19,8,33,40 };
    MSET1 m1;
    m1.insert(a,a+SIZE);
    m1.insert(22);
    cout << "1) " << m1.count(22) << endl; //输出 1) 2
    cout << "2) "; Print(m1.begin(),m1.end()); 
    //输出 2) 4 8 19 22 22 33 40
   
    //m1元素:4 8 19 22 22 33 40
    MSET1::iterator pp = m1.find(19);
    if( pp != m1.end() ) //条件为真说明找到
        cout << "found" << endl;
        //本行会被执行,输出 found
    cout << "3) "; cout << * m1.lower_bound(22) << ","
          <<* m1.upper_bound(22)<< endl;
    //输出 3) 22,33
    pp = m1.erase(m1.lower_bound(22),m1.upper_bound(22));
    //pp指向被删元素的下一个元素
    cout << "4) "; Print(m1.begin(),m1.end()); 
    //输出 4) 4 8 19 33 40
    cout << "5) "; cout << * pp << endl; //输出 5) 33
    MSET2 m2; //m2里的元素按n的个位数从小到大排
    m2.insert(a,a+SIZE);
    cout << "6) "; Print(m2.begin(),m2.end()); 
    //输出 6) 40 22 33 4 8 19
    return 0;
}

输出:
1) 2
2) 4 8 19 22 22 33 40
3) 22,33
4) 4 8 19 33 40
5) 33
6) 40 22 33 4 8 19

 

MyLess 是一个函数对象类

set
template<class Key, class Pred = less<Key>, class A = allocator<Key> >
class set { ... }

插入 set 中已有的元素时,忽略插入

set 里面不能够有重复元素,a < b 和 b < a 都不成立那么 a 和 b 就算重复

set 用法示例
#include <iostream>
#include <set>
using namespace std;
int main() {
    typedef set<int>::iterator IT;
    int a[5] = { 3,4,6,1,2 };
    set<int> st(a,a+5);    //st里是 1 2 3 4 6
    pair< IT,bool> result;
    result = st.insert(5); //st变成 1 2 3 4 5 6
    if( result.second )    //插入成功则输出被插入元素
        cout << * result.first << " inserted" << endl; 
        //输出: 5 inserted
    if( st.insert(5).second ) cout << * result.first << endl;
    else
        cout << * result.first << " already exists" << endl;
        //输出 5 already exists
    pair<IT,IT> bounds = st.equal_range(4);
    cout << * bounds.first << "," << * bounds.second ; 
    //输出:4,5
    return 0;
}

输出结果:
5 inserted
5 already exists
4,5

map 和 multimap

multimap
template<class Key, class T, class Pred = less<Key>, class A = allocator<T> >
class multimap {
    ....
    typedef pair<const Key, T> value_type;
    ....
}; //Key 代表关键字的类型

类型参数 Key 代表 multimap 里面元素的 first 成员变量类型,T 代表元素的 second 成员变量类型,Pred 规定比大小的规则,A 可以不管就用缺省值

 

multimap 中的元素由 <关键字,值>组成,每个元素是一个 pair 对象,关键字
就是 first 成员变量,其类型是 Key
multimap 中允许多个元素的关键字相同。元素按照 first 成员变量从小到大
排列,缺省情况下用 less<Key> 定义关键字的“小于”关系

multimap 示例
#include <iostream>
#include <map>
using namespace std;
int main() {
    typedef multimap<int,double,less<int> > mmid;
    mmid pairs;
    cout << "1) " << pairs.count(15) << endl;
    pairs.insert(mmid::value_type(15,2.7));
    //typedef pair<const Key, T> value_type;
    pairs.insert(mmid::value_type(15,99.3));
    cout <<2)<< pairs.count(15) << endl; 
    //求关键字等于某值的元素个数
    pairs.insert(mmid::value_type(30,111.11));
    pairs.insert(mmid::value_type(10,22.22));
    pairs.insert(mmid::value_type(25,33.333));
    pairs.insert(mmid::value_type(20,9.3));
    for( mmid::const_iterator i = pairs.begin(); i != pairs.end() ; i++ )
        cout << "(" << i->first << "," << i->second << ")" << ",";
}

输出:
1) 0
2) 2
(10,22.22),(15,2.7),(15,99.3),(20,9.3),(25,33.333),(30,111.11)

 

multimap<int, double, less<int>> 容器类的 first 和 second 成员变量类型分别是 int 和 double,关键字 first 比大小的规则是 less<int>,用 < 比大小

pairs.count(15) 计算有多少个元素的关键字的值等于15,x < y 和 y < x 同时不成立就算等于

mmid::value_type 是把 pair<const Key, T> 的 Key 替换成 int,T 替换成 double 得到的 pair 模板类,mmid::value_type(15, 2.7) 是一个 mmid::value_type 类的临时对象,插入 pairs 该对象的副本

multimap 例题

一个学生成绩录入和查询系统,接受以下两种输入:

Add name id score
Query score

name 是个字符串,中间没有空格,代表学生姓名。id是个整数,代表学号。score 是个整数,表示分数。学号不会重复,分数和姓名都可能重复

两种输入交替出现。第一种输入表示要添加一个学生的信息,碰到这种输入,就记下学生的姓名、id 和分数。第二种输入表示要查询,碰到这种输入,就输出已有记录中分数比 score 低的最高分获得者的姓名、学号和分数。如果有多个学生都满足条件,就输出学号最大的那个学生的信息。如果找不到满足条件的学生,则输出 “Nobody”

 

输入样例:
Add Jack 12 78
Query 78
Query 81
Add Percy 9 81
Add Marry 8 81
Query 82
Add Tom 11 79
Query 80
Query 81

 
输出样例:
Nobody
Jack 12 78
Percy 9 81
Tom 11 79
Tom 11 79

 

#include <iostream>
#include <map>  //使用multimap需要包含此头文件
#include <string>
using namespace std;

class CStudent
{
    public:
        struct CInfo //类的内部还可以定义类
       {
            int id;
            string name;
        };
        int score;
        CInfo info; //学生的其他信息
};

typedef multimap<int, CStudent::CInfo> MAP_STD;

int main() {
    MAP_STD mp;
    CStudent st;
    string cmd;
    while( cin >> cmd ) {
        if( cmd == "Add") {
            cin >> st.info.name >> st.info.id >> st.score ;
            mp.insert(MAP_STD::value_type(st.score,st.info ));
         }
        else if( cmd == "Query" ){
            int score;
            cin >> score;
            MAP_STD::iterator p = mp.lower_bound (score);
            if( p!= mp.begin()) {
                --p;
                score = p->first; //比要查询分数低的最高分
                MAP_STD::iterator maxp = p;
                int maxId = p->second.id;
                for( ; p != mp.begin() && p->first == 
                          score; --p) {
                //遍历所有成绩和score相等的学生
                    if( p->second.id > maxId ) {
                        maxp = p;
                        maxId = p->second.id ;
                    }
                }
                if( p->first == score) {
                    //如果上面循环是因为 p == mp.begin()
                    //而终止,则p指向的元素还要处理
                    if( p->second.id > maxId ) {
                        maxp = p;
                        maxId = p->second.id ;
                    }
                }
                cout << maxp->second.name << " “ 
                    << maxp->second.id << " "
                    << maxp->first << endl;
            }
            else
            //lower_bound的结果就是 begin,说明没人分数比查询分数低
                 cout << "Nobody" << endl;
            }
    }
    return 0;
}    

不停地更新和查询学生信息,添加一个学生和查询一个分数的时间复杂度是 O ( l o g 2 n ) O(log_2n) O(log2n)

用关联容器 multimap 存学生的信息,因为要根据分数查询,所以把分数对应 first 成员变量,姓名和 id 对应 second 成员变量

 

 

typedef multimap<int, CStudent::CInfo> MAP_STD 定义容器类 MAP_STD,first 和 second 成员变量的类型分别是 int 和 CStudent::CInfo,在使用内部类时前面要加上包含它的外部类的名字

 

mp.insert(MAP_STD::value_type(st.score,st.info ));
//mp.insert(make_pair(st.score,st.info )); 也可以

往 mp 里面插入一个学生的信息

MAP_STD::value_type(st.score, st.info) 生成一个 pair 模板类 MAP_STD::value_type 的对象,MAP_STD 这种 multimap 里面元素的类型就是 value_type

make_pair 是 STL 里的一个函数模板,返回值就是一个 pair 模板类的对象,对象 的 first 和 second 成员变量是 st.score 和 st.info

map
template<class Key, class T, class Pred = less<Key>, class A = allocator<T> >
class map {
    ....
    typedef pair<const Key, T> value_type;
    ......
};

map 中的元素都是 pair 模板类对象。关键字(first成员变量)各不相同。元素按照关键字从小到大排列,缺省情况下用 less<Key>,即“<” 定义“小于”

map 的[]成员函数

若 pairs 为 map 模版类的对象,

pairs[key]

返回对关键字等于 key 的元素的值(second 成员变量)的引用。若没有关键字为key 的元素,则会往 pairs 里插入一个关键字为 key 的元素,其值也就是 second 成员变量用无参构造函数初始化,并返回其值的引用

如:

map<int,double> pairs;

pairs[50] = 5;

会修改 pairs 中关键字为50的元素,使其值变成5。若不存在关键字等于50的元素,则插入此元素,并使其值变为5

map 示例
#include <iostream>
#include <map>
using namespace std;
template <class Key,class Value>
ostream & operator <<( ostream & o, const pair<Key,Value> & p)
{
    o << "(" << p.first << "," << p.second << ")";
    return o;
}

int main() {
    typedef map<int, double,less<int> > mmid;
    mmid pairs;
    cout << "1) " << pairs.count(15) << endl;
    pairs.insert(mmid::value_type(15,2.7));
    pairs.insert(make_pair(15,99.3)); //make_pair生成一个pair对象
    cout << "2) " << pairs.count(15) << endl;
    pairs.insert(mmid::value_type(20,9.3));
    mmid::iterator i;
    cout << "3) ";
    for( i = pairs.begin(); i != pairs.end();i ++ )
        cout << * i << ",";
    cout << endl;
    cout << "4) ";
    int n = pairs[40];   //如果没有关键字为40的元素,则插入一个
    for( i = pairs.begin(); i != pairs.end();i ++ )
        cout << * i << ",";
    cout << endl;
    cout << "5) ";
    pairs[15] = 6.28;    //把关键字为15的元素值改成6.28
    for( i = pairs.begin(); i != pairs.end();i ++ )
        cout << * i << ",";
}

输出:
1) 0
2) 1
3) (15,2.7),(20,9.3),
4) (15,2.7),(20,9.3),(40,0),
5) (15,6.28),(20,9.3),(40,0),

 

判断 insert 操作是否成功和 set 的情况很类似,可以参考 set 的例子

int n = pairs[40] 如果不存在关键字为40的元素,则插入 pairs 一个关键字为40的新元素,second 用无参构造函数初始化,这个例子里被初始化成0

容器适配器

容器适配器上面没有迭代器,STL 里面的各种算法都不适用于容器适配器,要使用容器适配器只能通过他自己的成员函数

stack

stack 是后进先出的数据结构,只能插入,删除,访问栈顶的元素

内部可用 vector,list,deque 来实现。缺省情况下,用 deque 实现

用 vector和 deque 实现,比用 list 实现性能好

template <class T, class Cont = deque<T> >
class stack {
    ......
};

第二个类型参数 class Cont 代表 stack 要用什么实现,缺省参数是 deque<T>,用 deque 实现,T 代表栈里面元素的类型

 

stack 上可以进行以下操作:

push:插入元素

pop:弹出元素

top:返回栈顶元素的引用

queue

和 stack 基本类似,可以用 list 和 deque 实现。缺省情况下用 deque 实现

template<class T, class Cont = deque<T> >
class queue {
    ......
};

 

同样也有 push,pop,top 函数。但是 push 发生在队尾;pop 和 top 发生在队头。先进先出

有 back 成员函数可以返回队尾元素的引用

priority_queue
template <class T, class Container = vector<T>, class Compare = less<T> >
class priority_queue;

类型参数 Compare 指明优先队列里面元素比较大小的方式,缺省值是 less<T>,比较元素大小的规则是用<比较

 

和 queue 类似,可以用 vector 和 deque 实现。缺省情况下用 vector 实现

priority_queue 通常用堆排序技术实现,保证最大的元素总是在最前面。即执行 pop 操作时,删除的是最大的元素;执行 top 操作时,返回的是最大元素的引用,不能通过这个引用修改队头元素内容。默认的元素比较器是 less<T>

push,pop 时间复杂度 O ( l o g 2 n ) O(log_2n) O(log2n)

top() 时间复杂度 O ( 1 ) O(1) O(1)

 

 

#include <queue>
#include <iostream>
using namespace std;
int main()
{
    priority_queue<double> pq1;
    pq1.push(3.2); pq1.push(9.8); pq1.push(9.8); pq1.push(5.4);
    while( !pq1.empty() ) {
           cout << pq1.top() << " ";
           pq1.pop();
    }//上面输出 9.8 9.8 5.4 3.2
    cout << endl;
    priority_queue<double,vector<double>,greater<double> > pq2;
    pq2.push(3.2); pq2.push(9.8); pq2.push(9.8); pq2.push(5.4);
    while( !pq2.empty() ) {
           cout << pq2.top() << " ";
           pq2.pop();
    }
    //上面输出 3.2 5.4 9.8 9.8
    return 0;
}

priority_queue<double,vector<double>,greater<double> > pq2 规定内部用 vector 实现,规定这个没有必要,但是由于想要给出第三个做比较的方法所以不得不把第二个类型参数也写上

做比较的方法是 greater<double>,在这个 priority_queue 里面比大小用 greater 模板。数学上小的反而优先级高,整个队列保持数学上最小的元素在队头

容器适配器的元素个数

stack,queue,priority_queue 都有

empty():成员函数用于判断适配器是否为空

size():成员函数返回适配器中元素个数

算法

STL 中的算法大致可以分为以下七类:

  1. 不变序列算法
  2. 变值算法
  3. 删除算法
  4. 变序算法
  5. 排序算法
  6. 有序区间算法
  7. 数值算法

 

大多重载的算法都是有两个版本

  • 用**“==”判断元素是否相等,或用“<”**来比较大小
  • 多出来一个类型参数“Pred”,以及函数形参“Pred op”,该版本通过表达式“op(x,y)”的返回值是 ture 还是 false 来判断 x 是否“等于” y,或者 x 是否“小于” y

如下面的有两个版本的 min_element:

iterate min_element(iterate first, iterate last);
iterate min_element(iterate first, iterate last, Pred op);

第一个版本用”<“比较大小,第二个版本用 op 定义比较大小的办法,真正调用 min_element 时 op 实参可以给一个函数名或者函数对象

不变序列算法
  • 该类算法不会修改算法所作用的容器或对象
  • 适用于顺序容器和关联容器
  • 时间复杂度都是 O ( n ) O(n) O(n)

 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TVAxvytt-1666239138149)(C++ 面向对象程序设计.assets/image-20221018160515232.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8I8f3isS-1666239138150)(C++ 面向对象程序设计.assets/image-20221018160614795.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HMU6BGpy-1666239138151)(C++ 面向对象程序设计.assets/image-20221018160643501.png)]

 

 

find:

template<class InIt, class T>
InIt find(InIt first, InIt last, const T& val);

返回区间 [first,last) 中的迭代器 i,使得 * i == val。如果没找到就返回 last

 

find_if:

template<class InIt, class Pred>
InIt find_if(InIt first, InIt last, Pred pr);

返回区间 [first,last) 中的迭代器 i,使得 pr(*i) == true

 

for_each:

template<class InIt, class Fun>
Fun for_each(InIt first, InIt last, Fun f);

对 [first,last) 中的每个元素 e,执行 f(e),要求 f(e) 不能改变e

 

count:

template<class InIt, class T>
size_t count(InIt first, InIt last, const T& val);

计算 [first,last) 中等于 val 的元素个数

 

count_if:

template<class InIt, class Pred>
size_t count_if(InIt first, InIt last, Pred pr);

计算 [first,last) 中符合 pr(e) == true 的元素 e 的个数

 

min_element:

template<class FwdIt>
FwdIt min_element(FwdIt first, FwdIt last);

返回 [first,last) 中最小元素的迭代器,以“< ”作比较器

最小指没有元素比它小,而不是它比别的不同元素都小

因为即便 a!= b。a<b 和 b<a 有可能都不成立

 

max_element:

template<class FwdIt>
FwdIt max_element(FwdIt first, FwdIt last);

返回 [first,last) 中**最大元素(它不小于任何其他元素,但不见得其他不同元素都小于它)**的迭代器

以“<”作比较器

#include <iostream>
#include <algorithm>
using namespace std;

class A {
    public: int n;
    A(int i):n(i) { }
};

bool operator<( const A & a1, const A & a2) {
    cout << "< called,a1=" << a1.n << " a2=" << a2.n << endl;
    if( a1.n == 3 && a2.n == 7)
        return true;
    return false;
}
int main() {
    A aa[] = { 3,5,7,2,1 };
    cout << min_element(aa,aa+5)->n << endl;
    cout << max_element(aa,aa+5)->n << endl;
    return 0;
}

输出:
< called,a1=5 a2=3
< called,a1=7 a2=3
< called,a1=2 a2=3
< called,a1=1 a2=3
3
< called,a1=3 a2=5
< called,a1=3 a2=7
< called,a1=7 a2=2
< called,a1=7 a2=1
7

 

顺序遍历整个数组,一开始要假设一个最小值,假设3最小。5跟3比较时,看5<3这个表达式是否为 true,为 true 则认为5比3更小,最小值就要更新为5

变值算法

此类算法会修改源区间或目标区间元素的值

值被修改的那个区间,不可以是属于关联容器的

 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OocsubYt-1666239138154)(C++ 面向对象程序设计.assets/image-20221018165421367.png)]

两个区间本身有重叠时,如果都是从前往后进行拷贝,可能后面的内容还没有被拷贝就被覆盖掉了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5t7OeYOT-1666239138158)(C++ 面向对象程序设计.assets/image-20221018165640779.png)]

 

 

transform:

template<class InIt, class OutIt, class Unop>
OutIt transform(InIt first, InIt last, OutIt x, Unop uop);

对 [first,last) 中的每个迭代器 I,

  • 执行 uop( * I ) ; 并将结果依次放入从 x 开始的地方
  • 要求 uop( * I ) 不得改变 * I 的值

本模板返回值是个迭代器,即 x + (last-first)

  • x 可以和 first相等

 

 

#include <vector>
#include <iostream>
#include <numeric>
#include <list>
#include <algorithm>
#include <iterator>
using namespace std;
class CLessThen9 {
public:
bool operator()( int n) { return n < 9; }
};
void outputSquare(int value ) { cout << value * value << " "; }
int calculateCube(int value) { return value * value * value; }

main() {
const int SIZE = 10;
int a1[] = { 1,2,3,4,5,6,7,8,9,10};
int a2[] = { 100,2,8,1,50,3,8,9,10,2 };
vector<int> v(a1,a1+SIZE);
ostream_iterator<int> output(cout," ");
random_shuffle(v.begin(),v.end());
cout << endl << "1) ";
copy( v.begin(),v.end(),output);
copy( a2,a2+SIZE,v.begin());
cout << endl << "2)";
cout << count(v.begin(),v.end(),8);
cout << endl << "3)";
cout << count_if(v.begin(),v.end(),CLessThen9());
    
cout << endl << "4)";
cout << * (min_element(v.begin(),v.end()));
cout << endl << "5)";
cout << * (max_element(v.begin(),v.end()));
cout << endl << "6) ";
cout << accumulate(v.begin(),v.end(),0);//求和
cout << endl << "7) ";
for_each(v.begin(),v.end(),outputSquare);
vector<int> cubes(SIZE);
transform(a1,a1+SIZE,cubes.begin(),calculateCube);
cout << endl << "8) ";
copy( cubes.begin(),cubes.end(),output);
}

输出:
1) 5 4 1 3 7 8 9 10 6 2
2)2
3)6
4)1
5)100
6)193
7)10000 4 64 1 2500 9 64 81 100 4
8)1 8 27 64 125 216 343 512 729 1000

 

ostream_iterator 是 STL 里面自己定义的一个类模板,ostream_iterator<int> 把这个类模板实例化,意思是以后要通过 ostream_iterator<int> 输出的东西都是 int 类型的。初始化该类型的对象 output 时给了两个参数 cout 和 " ",意味着以后把什么东西交给 output 时都会等价于交给 cout 输出到屏幕上,被输出的东西都是一个个 int,而且每输出完一个整数后面都要加一个空格

关于 ostream_iterator,istream_iterator 的例子:

int main() {
    istream_iterator<int> inputInt(cin);
    int n1, n2;
    n1 = * inputInt;  //读入 n1
    inputInt ++;
    n2 = * inputInt;  //读入 n2
    cout << n1 << "," << n2 << endl;
    ostream_iterator<int> outputInt(cout);
    * outputInt = n1 + n2;
    cout << endl;
    int a[5] = { 1, 2, 3, 4, 5 };
    copy(a, a+5, outputInt);   //输出整个数组
    return 0;
}

 

random_shuffle 变序算法用来随机地打乱 v 中间的值

之前说 copy 用来把一个区间的内容拷贝到另外一个区间,但 copy(v.begin(), v.end(), output) 里 output 不是一个容器,也不是一个区间的开始位置仍然可以这样用,导致 v 的内容在 cout 上输出

copy 操作要求目标区间的位置要有足够的空间,不然就会出错

CLessThen9() 是一个临时对象 op,这个对象没有名称,count_if 在计算元素是否符合条件时,就是看 op(e) 即 op.operator(e) 是否为 true

accumulate 数值计算算法累加 a2 数组当中的所有值到初始值0上面

copy 函数模板(算法)
template<class InIt, class OutIt>
OutIt copy(InIt first, InIt last, OutIt x);

本函数对每个在区间 [0, last-first) 中的 N 执行一次 *(x+N) = *(first + N),返回 x + N

对于 copy(v.begin(), v.end(), output);

first 和 last 的类型是 vector<int>::const_iterator,output 的类型是 ostream_iterator<int>

 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aDWsWHA3-1666239138161)(C++ 面向对象程序设计.assets/image-20221018181618673.png)]

不见得所有的 C++ 编译器都是这样实现的,但它的核心就是上面的循环

 

 

#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <iterator>
using namespace std;
int main(){
    int a[4] = { 1, 2, 3, 4 };
    My_ostream_iterator<int> oit(cout, "*");
    copy(a, a+4, oit);  //输出 1*2*3*4*
    ofstream oFile("test.txt", ios::out);
    My_ostream_iterator<int> oitf(oFile, "*");
    copy(a, a+4, oitf);  //向test.txt文件中写入 1*2*3*4*
    oFile.close();
    return 0;
} //如何编写 My_ostream_iterator?

上面程序中调用语句 “copy(a, a+4, oit)” 实例化后得到 copy 函数如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yvAWb7t1-1666239138163)(C++ 面向对象程序设计.assets/image-20221018183031910.png)]

要让这个 copy 函数能够编译通过,My_ostream_iterator 类应该重载“++”、“*”、“=”运算符,同时想办法输出 *_F

“*” 经过重载之后就是一个函数,“*_X” 就是一个函数调用的表达式,函数调用的表达式能够出现在等号的左边,返回值应该是引用,引用 _X

*_X = *_F 解释为 _X.operator=(*_F),在 operator= 函数里面输出 *_F

#include <iterator>
template<class T>
class My_ostream_iterator:public iterator<output_iterator_tag, T>{
    private:
        string sep; //分隔符
        ostream & os;
    public:
        My_ostream_iterator(ostream & o, string s):sep(s), os(o){}
        void operator ++(){}; //++只需要有定义即可,不需要做什么
        My_ostream_iterator & operator*(){ return * this; }
        My_ostream_iterator & operator=( const T & val ) 
        { os << val << sep; return * this; }
}

为了在 Dev C++ 或者其他编译器里面能编译通过还要加 :public iterator<output_iterator_tag, T>,因为这些编译器里面的 copy 实际不像上面写的那么简单

由于 ostream 的无参构造函数是私有的,所以在这里没有办法定义一个 ostream 类的对象,只能用 ostream &

删除算法

删除一个容器里的某些元素

“删除” —— 不会使容器里的元素减少

  • 将所有应该被删除的元素看做空位子
  • 用留下的元素从后往前移,依次去填空位子
  • 元素往前移后,它原来的位置也就算是空位子
  • 也应由后面的留下的元素来填上
  • 最后,没有被填上的空位子,维持其原来的值不变

删除算法不应作用于关联容器

 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XWufQjju-1666239138165)(C++ 面向对象程序设计.assets/image-20221018190913100.png)]

把一个数组排序然后把重复的元素都去掉就可以先 sort 再 unique

unique 返回被删完以后最后一个有效元素后面的那一个位置的迭代器,把该迭代器和整个区间的开头 begin 相减可知还剩下多少个元素

算法复杂度都是 O ( n ) O(n) O(n)

 

 

unique:

template<class FwdIt>
FwdIt unique(FwdIt first, FwdIt last);

用 == 比较是否等

 

template<class FwdIt, class Pred>
FwdIt unique(FwdIt first, FwdIt last, Pred pr);

用 pr(x, y) 为 true 说明 x 和 y 相等

对 [first,last) 这个序列中连续相等的元素,只留下第一个

返回值是迭代器,指向元素删除后的区间的最后一个元素的后面

 

 

int main()
{
    int a[5] = { 1,2,3,2,5};
    int b[6] = { 1,2,3,2,5,6};
    ostream_iterator<int> oit(cout,",");
    int * p = remove(a,a+5,2);
    cout << "1) "; copy(a,a+5,oit); cout << endl;
    //输出 1) 1,3,5,2,5,
    cout << "2) " << p - a << endl; //输出 2) 3
    vector<int> v(b,b+6);
    remove(v.begin(),v.end(),2);
    cout << "3) ";copy(v.begin(),v.end(),oit);cout << endl;
    //输出 3) 1,3,5,6,5,6,
    cout << "4) "; cout << v.size() << endl;
    //v中的元素没有减少,输出 4) 6
    return 0;
}

int * p = remove(a, a+5, 2) remove 返回一个迭代器,int * p 在整型数组上就是一个迭代器

变序算法

变序算法改变容器中元素的顺序

但是不改变元素的值

变序算法不适用于关联容器

算法复杂度都是 O ( n ) O(n) O(n)

 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lIbFVLlx-1666239138166)(C++ 面向对象程序设计.assets/image-20221018204318483.png)]

在这里插入图片描述

 

 

stable_patition:

把区间内满足某个条件的元素移到前面

不满足该条件的移到后面

而且对这两部分元素,分别保持它们原来的先后次序不变

 

random_shuffle:

template<class RanIt>
void random_shuffle(RanIt first, RanIt last);

随机打乱 [first,last) 中的元素,适用于能随机访问,上面是随机访问迭代器的容器

 

reverse:

template<class BidIt>
void reverse(BidIt first, BidIt last);

颠倒区间 [first,last) 顺序

 

next_permutation:

template<class InIt>
bool next_permutaion (Init first,Init last);

求下一个排列

排列是有大小关系的,比如1、2、3三个数排列一共有6个,123最小,321最大。这个大小也可以通过自定义的比较器来规定

 

 

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main()
{
    string str = "231";
    char szStr[] = "324";
    while (next_permutation(str.begin(), str.end()))
    {
        cout << str << endl;
    }
    cout << "****" << endl;
    while (next_permutation(szStr,szStr + 3))
    {
        cout << szStr << endl;
    }
    
    sort(str.begin(),str.end());
    cout << "****" << endl;
    while (next_permutation(str.begin(), str.end()))
    {
        cout << str << endl;
    }
    return 0;
}

输出
312
321
****
342
423
432
****
132
213
231
312
321

 

容器里面放的是整数或者别的一些对象都可以用 next_permutation 求它的排列

#include <iostream>
#include <algorithm>
#include <string>
#include <list>
#include <iterator>
using namespace std;
int main()
{
    int a[] = { 8,7,10 };
    list<int> ls(a , a + 3);
    while( next_permutation(ls.begin(),ls.end()))
    {
        list<int>::iterator i;
        for( i = ls.begin();i != ls.end(); ++i)
            cout << * i << " ";
        cout << endl;
    }
}

输出:
8 10 7
10 7 8
10 8 7

排序算法

比前面的变序算法复杂度更高,一般是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

排序算法需要随机访问迭代器的支持

不适用于关联容器和 list

 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0AeHFg2P-1666239138183)(C++ 面向对象程序设计.assets/image-20221018210520789.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IKQOEjgW-1666239138184)(C++ 面向对象程序设计.assets/image-20221018211046227.png)]

 

 

sort 快速排序:

template<class RanIt>
void sort(RanIt first, RanIt last);

按升序排序

判断 x 是否应比 y 靠前,就看 x < y 是否为 true

 

template<class RanIt, class Pred>
void sort(RanIt first, RanIt last, Pred pr);

按升序排序。判断 x 是否应比 y 靠前,就看 pr(x,y) 是否为 true

 

#include <iostream>
#include <algorithm>
using namespace std;
class MyLess {
public:
    bool operator()( int n1,int n2) {
        return (n1 % 10) < ( n2 % 10);
    }
};
int main() {
    int a[] = { 14,2,9,111,78 };
    sort(a,a + 5,MyLess());
    int i;
    for( i = 0;i < 5;i ++)
        cout << a[i] << " ";
     cout << endl;
    sort(a,a+5,greater<int>());
    for( i = 0;i < 5;i ++)
        cout << a[i] << " ";
}

按个位数大小排序,以及按降序排序

输出:
111 2 14 78 9
111 78 14 9 2

 

sort 实际上是快速排序,时间复杂度 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

  • 平均性能最优
  • 但是最坏的情况下,性能可能非常差

如果要保证“最坏情况下”的性能,那么可以使用

  • stable_sort
  • stable_sort 实际上是归并排序,特点是能保持相等元素之间的先后次序
  • 在有足够存储空间的情况下,复杂度为 n × l o g 2 n n\times log_2n n×log2n,否则复杂度为 n × l o g 2 n × l o g 2 n n\times log_2n\times log_2n n×log2n×log2n
  • stable_sort 用法和 sort相同

排序算法要求随机存取迭代器的支持,所以 list 不能使用排序算法,要使用list::sort

有序区间算法

要求所操作的区间是已经从小到大排好序的

需要随机访问迭代器的支持

有序区间算法不能用于关联容器和 list

 

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T9w53kKj-1666239138185)(C++ 面向对象程序设计.assets/image-20221018224141871.png)]

binary_search 里的相等是 x < y 和 y < x 同时不成立

binary_search、lower_bound、upper_bound、equal_range 的时间复杂度是 O ( l o g 2 n ) O(log_2n) O(log2n),includes 还取决于要找的区间里面有多少个元素。merge 的时间复杂度是 O ( n ) O(n) O(n)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cV3HyVmr-1666239138187)(C++ 面向对象程序设计.assets/image-20221018225116331.png)]

set_symmetric_difference 两个区间的并减去两个区间的交

 

 

binary_search:

折半查找

要求容器已经有序且支持随机访问迭代器,返回是否找到

template<class FwdIt, class T>
bool binary_search(FwdIt first, FwdIt last, const T& val);

上面这个版本,比较两个元素 x,y 大小时, 看 x < y

 

template<class FwdIt, class T, class Pred>
bool binary_search(FwdIt first, FwdIt last, const T& val, Pred pr);

上面这个版本,比较两个元素 x,y 大小时, 若 pr(x,y) 为 true,则认为 x 小于 y

 

#include <vector>
#include <bitset>
#include <iostream>
#include <numeric>
#include <list>
#include <algorithm>
using namespace std;
bool Greater10(int n)
{
    return n > 10;
}

int main() {
    const int SIZE = 10;
    int a1[] = { 2,8,1,50,3,100,8,9,10,2 };
    vector<int> v(a1,a1+SIZE);
    ostream_iterator<int> output(cout," ");
    vector<int>::iterator location;
    location = find(v.begin(),v.end(),10);
    if( location != v.end()) {
        cout << endl << "1) " << location - v.begin();
    }
    location = find_if( v.begin(),v.end(),Greater10);
    if( location != v.end())
        cout << endl << "2) " << location - v.begin();
    sort(v.begin(),v.end());
    if( binary_search(v.begin(),v.end(),9)) {
        cout << endl << "3) " << "9 found";
    }
}

输出:
1) 8
2) 3
3) 9 found

 

 

lower_bound:

template<class FwdIt, class T>
FwdIt lower_bound(FwdIt first, FwdIt last, const T& val);

要求 [first,last) 是有序的

查找 [first,last) 中的,最大的位置 FwdIt,使得 [first,FwdIt) 中所有的元素都比 val 小

 

upper_bound:

template<class FwdIt, class T>
FwdIt upper_bound(FwdIt first, FwdIt last, const T& val);

要求 [first,last) 是有序的

查找 [first,last) 中的,最小的位置 FwdIt,使得 [FwdIt,last) 中所有的元素都比 val 大

 

equal_range:

template<class FwdIt, class T>
pair<FwdIt, FwdIt> equal_range(FwdIt first, FwdIt last, const T& val);

要求 [first,last) 是有序的

返回值是一个 pair,假设为 p,则:

[first,p.first) 中的元素都比 val 小

[p.second,last) 中的所有元素都比 val 大

p.first 就是 lower_bound 的结果

p.last 就是 upper_bound 的结果

 

 

merge:

template<class InIt1, class InIt2, class OutIt>
OutIt merge(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x);

用 < 作比较器

 

template<class InIt1, class InIt2, class OutIt, class Pred>
OutIt merge(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x, Pred pr);

用 pr 作比较器

 

把 [first1,last1),[ first2,last2) 两个升序序列合并,形成第3个升序序列,第3个升序序列以 x 开头

 

 

 

includes:

template<class InIt1, class InIt2>
bool includes(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2);

template<class InIt1, class InIt2, class Pred>
bool includes(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, Pred pr);

判断 [first2,last2) 中的每个元素,是否都在 [first1,last1) 中

第一个用 < 作比较器。若 [ f i r s t 2 , l a s t 2 ] [first2,last2] [first2,last2] 里有一个元素 x x x ∃ y ∈ [ f i r s t 1 , l a s t 1 ] \exist y\in [first1, last1] y[first1,last1] 满足 x < y x < y x<y 和 $ y < x$ 都不成立,则 x x x [ f i r s t 1 , l a s t 1 ] [first1,last1] [first1,last1]

第二个用 pr 作比较器,pr(x,y) == true说明 x,y 相等

 

 

set_difference:

template<class InIt1, class InIt2, class OutIt>
OutIt set_difference(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x);

template<class InIt1, class InIt2, class OutIt, class Pred>
OutIt set_difference(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x, Pred pr);

求出 [first1,last1) 中,不在 [first2,last2) 中的元素,放到从 x 开始的地方

如果 [first1,last1) 里有多个相等元素不在 [first2,last2) 中,则这多个元素也都会被放入 x 代表的目标区间里

 

set_intersection:

template<class InIt1, class InIt2, class OutIt>
OutIt set_intersection(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x);

template<class InIt1, class InIt2, class OutIt, class Pred>
OutIt set_intersection(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x, Pred pr);

求出 [first1,last1) 和 [first2,last2) 中共有的元素,放到从 x 开始的地方

若某个元素 e 在 [first1,last1) 里出现 n1次,在 [first2,last2) 里出现 n2 次,则该元素在目标区间里出现 min(n1,n2) 次

 

set_symmetric_difference

template<class InIt1, class InIt2, class OutIt>
OutIt set_symmetric_difference(InIt1 first1, InIt1 last1, InIt2
first2, InIt2 last2, OutIt x);

template<class InIt1, class InIt2, class OutIt, class Pred>
OutIt set_symmetric_difference(InIt1 first1, InIt1 last1, InIt2
first2, InIt2 last2, OutIt x, Pred pr);

把两个区间里相互不在另一区间里的元素放入 x 开始的地方

 

 

set_union:

template<class InIt1, class InIt2, class OutIt>
OutIt set_union(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x);

用 < 比较大小

 

template<class InIt1, class InIt2, class OutIt, class Pred> OutIt set_union(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x, Pred pr); 

用 pr 比较大小

 

求两个区间的并,放到以 x 开始的位置

若某个元素 e 在 [first1,last1) 里出现 n1 次,在 [first2,last2) 里出现 n2 次,则该元素在目标区间里出现 max(n1,n2) 次

bitset
template<size_t N>
class bitset
{
    ......
}

用来实现多比特标志位,没有类型参数

实际使用的时候,N 是个整型常数

如:

  • bitset<40> bst;
  • bst 是一个由40位组成的对象
  • 用 bitset 的函数可以方便地访问任何一位

 

 

bitset 的成员函数:

bitset<N>& operator&=(const bitset<N>& rhs); //按位与
bitset<N>& operator|=(const bitset<N>& rhs); //按位或
bitset<N>& operator^=(const bitset<N>& rhs); //按位异或
bitset<N>& operator<<=(size_t num); //左移
bitset<N>& operator>>=(size_t num); //右移
bitset<N>& set();    //全部设成1
bitset<N>& set(size_t pos, bool val = true); //设置某位
bitset<N>& reset();  //全部设成0
bitset<N>& reset(size_t pos); //某位设成0
bitset<N>& flip();   //全部翻转
bitset<N>& flip(size_t pos);  //翻转某位


reference operator[](size_t pos);  //返回对某位的引用
bool operator[](size_t pos) const; //判断某位是否为1
reference at(size_t pos); //取某位,会进行下标是否越界检查
bool at(size_t pos) const;
unsigned long to_ulong() const;    //转换成整数
string to_string() const;          //转换成01字符串
size_t count() const;              //计算1的个数
size_t size() const; //求大小,一共有几个标志位
bool operator==(const bitset<N>& rhs) const;
//判断两个bitset是否相等
bool operator!=(const bitset<N>& rhs) const;

bool test(size_t pos) const; //测试某位是否为1
bool any() const;  //是否有某位为1
bool none() const; //是否全部为0
bitset<N> operator<<(size_t pos) const; //左移
bitset<N> operator>>(size_t pos) const; //右移
bitset<N> operator~(); //取反
static const size_t bitset_size = N;//静态成员变量N可以返回相应的值

比如 bitset 里有三个标志位,为111,用 to_ulong 转换成 unsigned long 就是7

 

注意:第0位在最右边

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值