浅谈 C++之 STL

Posted Club4Funny

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈 C++之 STL相关的知识,希望对你有一定的参考价值。

写在前面

文章来源于本人向18计科本01唐孝顺©同学邀稿,特别感谢唐同学的无私、辛苦付出。内容编排上采用基本概念与竞赛案例相结合的方式展开。本文可作为蓝桥杯-C++竞赛入门参考。

浅谈 C++之 STL


一、sort

  • sort 使用方便,效率较高,底层实现是用快排的方式进行排序

  • 要使用该算法,需要#include <algorithm>头文件(算法头文件)

  • sort 底层实现用的是快速排序

1. sort 用法一

  • 对基本类型的数组从小到大排序:

  • sort(数组名+n1,数组名+n2);

  • n1 和 n2 都是 int 类型的表达式,可以包含变量

  • 如果 n1=0,则 + n1 可以不写

  • 将数组中下标范围为[n1,n2)的元素从小到大排序。下标为 n2 的元素不在排序 区间内

「程序样例:对无序数组进行递增排序」

#include<bits/stdc++.h>
using namespace std;
int main(){
 int a[10]={12,23,423,2,1,53,65,10,32,45};
 sort(a, a+10);
 for(int i=0; i<10; i++){
  cout << a[i] << " ";
 }
 return 0;
}
//结果:1 2 10 12 23 32 45 53 65 423

2. sort 用法二

  • 对元素类型为 T 的基本类型数组从大到小排序:

  • sort(数组名+n1,数组名+n2,greater());

  • 「greater()」 是从大到小排序规则,<>中的 T 是数据类型,例如 int,float,double 等等

    泛型概念:

    ①.泛型:就是不使用具体数据类型,而是使用一种通用类型 T 来进行程序设计;T 只是一个占位符,实际在 T 的位置真实的数据类型取决于用户的需求;占位符的替换由编译器在编译阶段完成。

    ②.泛型编程:为了避免因数据类型的不同,而被迫重复编写大量相同业务逻辑的代码,因此发展了泛型及泛型编程技术;泛型编程就是独立于任何特定类型的方式编写代码,常用到 STL 容器、迭代器、和算法都是泛型编程的例子。

「程序样例:对数组进行从大到小排序」

#include<bits/stdc++.h>
using namespace std;
int main(){
 int a[10]={12,23,423,2,1,53,65,10,32,45};
 sort(a, a+10,greater<int>());
 for(int i=0; i<10; i++){
  cout << a[i] << " ";
 }
 return 0;
}
//结果:423 65 53 45 32 23 12 10 2 1

3. sort 用法三

  • 用自定义的排序规则,对任何类型 T 的数组排序

  • sort(数组名+n1,数组名+n2,排序规则结构名());

  • 排序规则结构的定义方式:

struct 结构名{
    bool operator()(const T & a1, const T & a2){
         //若a1应该在a2前面,则返回true, 否则返回false
    }
};

「程序样例:」

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
struct Student {
 char name[20]; //姓名
                 int id; //学号
                 double gpa;//绩点
};
Student students [] = { {"Jack",112,3.4},{"Mary",102,3.8},
                        {"Mary",117,3.9}, {"Ala",333,3.5},
                        {"Zero",101,4.0}
                      };
struct StudentRule1 { //按姓名从小到大排
       bool operator() (const Student & s1,const Student & s2) {
            if( stricmp(s1.name,s2.name) < 0)
                return truereturn false;
       }
};
struct StudentRule2 { //按id从小到大排
       bool operator() (const Student & s1,const Student & s2) {
            return s1.id < s2.id;
       }
};
struct StudentRule3 {//按gpa从高到低排
       bool operator() (const Student & s1,const Student & s2) {
            return s1.gpa > s2.gpa;
       }
};
void PrintStudents(Student s[],int size){
     for(int i = 0;i < size;++i)
        cout << "(" << s[i].name << "," << s[i].id <<"," << s[i].gpa <<")";
     cout << endl;
}
int main() {
    int n = sizeof(students) / sizeof(Student);
    sort(students,students+n,StudentRule1()); //按姓名从小到大排
    PrintStudents(students,n);
    sort(students,students+n,StudentRule2()); //按id从小到大排
    PrintStudents(students,n);
    sort(students,students+n,StudentRule3()); //按gpa从高到低排
    PrintStudents(students,n);
    return 0;
结果:
    (Ala,333,3.5) (Jack,112,3.4) (Mary,102,3.8) (Mary,117,3.9) (Zero,101,4)
    (Zero,101,4) (Mary,102,3.8) (Jack,112,3.4) (Mary,117,3.9) (Ala,333,3.5)
    (Zero,101,4) (Mary,117,3.9) (Mary,102,3.8) (Ala,333,3.5) (Jack,112,3.4)
}

4. sort 例题:奖学金

「题目描述:」

某小学最近得到了一笔赞助,打算拿出其中一部分为学习成绩优秀的前 5 名学生发奖学金。期末,每个学生都有 3 门课的成绩:语文、数学、英语。先按总分从高到低排序,如果两个同学总分相同,再按语文成绩从高到低排序,如果两个同学总分和语文成绩都相同,那么规定学号小的同学 排在前面,这样,每个学生的排序是唯一确定的。

任务:先根据输入的 3 门课的成绩计算总分,然后按上述规则排序,最后按排名顺序输出前五名名学生的学号和总分。注意,在前 5 名同学中,每个人的奖学金都不相同,因此,你必须严格按上述规则排序。例如,在某个正确答案中,如果前两行的输出数据(每行输出两个数:学号、总分) 是:

7 2795 279

这两行数据的含义是:总分最高的两个同学的学号依次是 77 号、55 号。这两名同学的总分都是 279279 (总分等于输入的语文、数学、英语三科成绩之和) ,但学号为 77 的学生语文成绩更高一些。如果你的前两名的输出数据是:

5 2797 279

则按输出错误处理,不能得分。

「输出格式:」

共 5 行,每行是两个用空格隔开的正整数,依次表示前 55 名学生的学号和总分

「输入输出格式:」

sort

代码实现:

#include<bits/stdc++.h>
using namespace std;
struct Students {
 int id;   // 学号
 int c; // 语文成绩
 int m; // 数学成绩
 int e; // 英语成绩
 int sum;
} S[305];
struct cmp {
 bool operator()(const Students & s1, const Students & s2) {
  if(s1.sum < s2.sum) { // 总成绩高的排在前面
   return 0;
  } else if(s1.sum > s2.sum) {
   return 1;
  } else  { // 总成绩相等,按语文成绩排名
   if(s1.c > s2.c) {  // 语文成绩相等,学号小的排在前面
    return 1;
   } else if(s1.c < s2.c) {
    return 0;
   } else {
    if(s1.id > s2.id)
     return 0;
    else
     return 1;
   }
  }
 }
};
int main() {
 int n;
 cin >> n;
 for(int i=1; i<=n; i++) {
  S[i].id = i;  // 学号
  cin >> S[i].c >> S[i].m >> S[i].e;
  S[i].sum = S[i].c + S[i].m + S[i].e;
 }
 sort(S+1, S+1+n, cmp());
 for(int i=1; i<=5; i++) {
  cout << S[i].id << " " << S[i].sum << endl;
 }
 return 0;
}

二、string 类(字符串)

  • 使用 string 类要包含头文件 #include

  • string 对象的初始化:

  • – string s1("Hello");

  • – string month = “March”; //常用方法

  • – string s2(8,’x’);//初始化 8 个 x 字符

  • 可以将字符赋值给 string 对象

  • – string s;

  • – s = ‘n’;

「程序样例:」

#include<bits/stdc++.h>
using namespace std;
int main(){
 string s1("hello worle");
 cout << s1 << endl;
 string s2(8,'x');
 cout << s2 << endl;
 string month = "March";
 cout << month << endl;
 string s;
 s = 'n';
 cout << s << endl;
 return 0;
}
运行结果:
hello worle
xxxxxxxx
March
n

1. 成员函数 length 用法

  • string 对象的长度用成员函数 「length()」 读取
#include<bits/stdc++.h>
using namespace std;
int main(){
 string str = "hello world";
 cout << str.length();
 return 0;
}
运行结果:11

2. string 的赋值和连接

  • 单个字符复制

s2[5] = s1[3] = 'a';

  • 逐个访问 string 对象中的字符

string s1("hello");

for(int i=0; i<s1.length(); i++){

cout << s1.at(i) << endl; //可以改成 s1[i] 数组的方式使用

}

  • 成员函数 「at()」 会做范围检查,如果超过范围,会抛出 out_of_range 异常,而「下标运算符 []」 不会做范围检查。

  • 「+」 运算符连接字符串

    string s1("good "), s2("morring!");

    s1 += s2;

    cout << s1; //结果是:good morring!

  • 用成员函数 「append()」 连接字符串

    string s1("good "), s2("morring!");

    s1.append(s2);

    cout << s1; //结果是:good morring!

    s2.append(s1, 3, s1.size()); //s1.size()和 s1.length()的功能一样

    cout << s2; //下标为 3 开始,s1.size()个字符,如果字符串没有足够字符,则复制到字符串最后一个字符

    //结果是:morring!d 。

3. 比较 string

  • 用关系运算符比较 string 的大小:「== , > , >= , < , <= , !=」 返回值是「bool」 类型,成立返回 true,否则返回 false

    例如:

    string s1("hello"), s2("hello"), s3("hell");

    bool b = (s1 == s2); //结果:1

    b = (s1 == s3); //结果:0

    b = (s1 > s3); //结果:1

  • 用成员函数 「compare()」 比较 string 的大小

    string s1("hello"), s2("hello"), s3("hell");

    int f1 = s1.compare(s2); //结果:0 hello == hello

    int f2 = s1.compare(s3); //结果:1 hello > hell

    int f3 = s3.compare(s1); //结果:-1 hell < hello

    int f4 = s1.compare(1,2,s3,0,3); //结果-1 注:前面两个数字表示从下标 1 到下标 2 之间的范围 el < hell

    int f5 = s1.compare(0, s1.size(), s3); // 结果:1 hello > hell

4. 子串

  • 成员函数 「substr()」

    string s1("hello world"), s2;

    s2 = s1.substr(4, 5); //从下标 4 开始 5 个字符

    cout << s2 << endl; //结果:o wor

5. 交换 string

  • 「成员函数 swap()」

    string s1("hello world"), s2("really");

    s1.swap(s2);

    cout << s1 << endl; // 结果:really

    cout << s2 << endl; // 结果:hello world

6. 删除 string 中的字符

  • 「成员函数 erase()」

    string s1("hello world");

    s1.erase(5); //删除下标及之后的字符

    cout << s1 << endl; // 结果:hello

    cout << s1.length() << endl; // 结果:5

    cout << s1.size() << endl; //结果:5

7. 在 string 中插入字符

  • 成员函数 「insert()」

    string s1("hello world");

    string s2("show insert");

    s1.insert(5, s2); //将 s2 插入 s1 下标 5 的位置

    cout << s1 << endl; //结果:helloshow insert world

    s1.insert(2, s2, 5, 3); //将 s2 中下标 5 开始的 3 个字符插入 s1 下标 2 的位置

    cout << s1 << endl; //结果:heinslloshow insert world

8. 转换成 C 语言式 char* 字符串

  • 成员函数 「c_str()」

    string s1("hello world");

    printf("%s\n", s1.c_str()); // 结果:hello world

    //s1.c_str() 返回传统的 const char * 类型字符串,且该字符串以 ”\0“ 结尾

三、容器概述

「可以用于存放各种类型的数据(基本类型的变量,对象等)的数据结构,都是类模板分为三种」:

  • 「顺序容器」
    • 「vector deque list」
  • 「关联容器」
    • 「set multiset map multimap」
  • 「容器适配器」
    • 「stack queue priority_queue」

1. 顺序容器简介

容器并非排序的,元素的插入位置同元素的值无关。有 vector,deque,list 三种。

  • 「vector 头文件 < vector >」

动态数组。元素在内存连续存放,随机存取任何元素都能在「常数时间」完成。在尾端增删元素具有较佳的性能(大部分情况下是常数时间)

浅谈 C++之 STL

  • 「deque 头文件< deque >」

    双向队列,又称循环队列。元素在内存连续存放。随机存取任何元素都能在常数时间完成(但次于 vector )。在两端增删元素具有较佳的性能(大部分情况下是常数时间)。

    浅谈 C++之 STL
    3
浅谈 C++之 STL
4
  • 「list 头文件< list >」

    双向链表,又称循环连边。元素在内存不连续存放,在任何位置增删元素都能在常数时间完成。不支持随机存取。

浅谈 C++之 STL
5

2. 容器适配器简介

  • 「stack 头文件< stack>」

    栈,是项的有限序列,并满足序列中别删除、检索和修改的项只能是最近插入序列的项(栈顶的项)。后进后出。

浅谈 C++之 STL
6
  • queue 头文件< queue >

    队列。插入只可以在尾部进行,删除、检索和修改只允许从头部进行。先进先出。

浅谈 C++之 STL
7

3. 顺序容器和关联容器中都有的成员函数

  • begin 返回指向容器中第一个元素的迭代器
  • end 返回指向容器中最后一个元素后面的位置的迭代器
  • rbegin 返回指向容器中最后一个元素的迭代器
  • rend 返回指向容器中第一个元素前面的位置的迭代器
  • clear 从容器中删除所有元素
  • erase 从容器中删除一个或几个元素
  • front 返回容器中第一个元素的引用
  • back 返回容器中最后一个元素的引用
  • push_back 在容器末尾添加新元素
  • pop_bakc 删除容器末尾的元素

4. 迭代器

  • 用于指向顺序容器和关联容器中的元素
  • 迭代器用法和指针类似
  • 有 const 和非 const 两种
  • 通过迭代器可以读取它指向的元素
  • 通过非 const 迭代器能 修改器指向的元素

「定义一个容类的迭代器的方法:」

「容器类型::iterator 变量名 或者 容器类名:: const_iterator 变量名」

「访问一个迭代器指向的元素:」

「* 迭代器变量名」

「迭代器上可以执行 ++ 操作,以使其指向容器中的下一个元素。如果迭代器到达了容器中的最后一个元素的后面,此时在使用它,就会出错,类似与使用 NULL 或」

「未初始化的指针一样。」

迭代器的的方法如何使用,后面结合其他容器一起介绍

(1)双向迭代器

「若 p 和 q 都是双向迭代器,则可对 p、q 可进行一下操作:」

  • ++p,p++ 使 p 指向容器中下一个元素
  • --p,p-- 使 p 指向容器中上一个元素
  • *p 取 p 指向的元素
  • p = q 赋值
  • p == q,p != q 判断是否相等、不等
(2)随机访问迭代器

「若 p 和 q 都是随机访问迭代器,则可对 p、q 进行一下操作:」

  • p += i 将 p 向后移动 i 个元素
  • p -= i 将 p 向前移动 i 个元素
  • p + i 指向 p 后面的第 i 个元素的迭代器
  • p - i 指向 p 前面的第 i 个元素的迭代器
  • p[i] p 后面的第 i 个元素的引用
  • p - q p 和 q 之间的元素的个数
  1. 浅谈 C++之 STL
    8
浅谈 C++之 STL
9

四、动态数组 vector

1. vector 数组定义

头文件:#include

定义:vector v;

2. vector 常用的成员函数有

  • 插入元素 v.push_back(n)

  • 检查这个容器是否为空 v.empty()

  • 容器所容纳的元素数 v.size()

  • 清空容器的所有元素 v.clear()

  • 移除末尾的元素 v.pop_back()

3.程序代码

#include<bits/stdc++.h>//万能头文件
using namespace std;
int main(){
 vector<int> v;   //一个存放 int 元素的数组,一开始容器里为空
 v.push_back(1);
 v.push_back(2);
 v.push_back(3);
 v.push_back(4);
 v.push_back(5);
 vector<int>::const_iterator it;  //常量迭代器
 //遍历vector容器中的元素
 for(it = v.begin(); it != v.end(); it++){
  cout << *it << " ";
 }
 cout << endl;

 vector<int>::iterator i;//非常量迭代器
 for(i = v.begin(); i != v.end(); i++){
  *i = 100;  //赋值
 }
 for(i = v.begin(); i != v.end(); i++){
  cout << *i << " ";
 }
 cout << endl;

 v.pop_back();  //删除容器中最后一个元素
   for(i = v.begin(); i != v.end(); i++){
  cout << *i << " ";
 }
 return 0;
}

运行结果:
1 2 3 4 5
100 100 100 100 100
100 100 100 100

4. vector 实现二维数组

#include<bits/stdc++.h>//万能头文件
using namespace std;
int main(){
 vector<vector<int> > v(3);// v 有3个元素,每个月元素都是vector<int>容器
 for(int i=0; i<v.size(); i++){
  for(int j=0; j<4; j++){
   v[i].push_back(j);
  }
 }

 // vector二维数组的遍历和一般的二维数组相似
 for(int i=0; i<v.size(); i++){
  for(int j=0; j<4; j++){
   cout << v[i][j] << " ";
  }
  cout << endl;
 }
 return 0;
}
运行结果:
0 1 2 3
0 1 2 3
0 1 2 3

5. vector 例题:询问学号

浅谈 C++之 STL
10

代码实现:

#include<bits/stdc++.h>
using namespace std;
vector<int> v;
int main() {
 int n, m;
 cin >> n >> m;
 for(int i=1; i<=n; i++) {
  int n1;
  cin >> n1;
  v.push_back(n1);
 }
 auto it = v.begin();
 for(int j=0;j<m; j++) {
  int m1;
  cin >> m1;
  cout << *(it+m1-1) << endl;
 }
 return 0;
}

五、链表 list

1. 链表定义

listlst1; //创建空 list

list lst2(5); //创建含有 5 个元素的 list

2. list 常用的成员函数

  • 在 list 容器末尾插入或删除元素 l.push_back / l.pop_back()

  • 在 list 容器头部插入或删除元素 l.push_front / l.pop_front()

  • 判断 list 容器是否为空 l.empty()

  • 对元素进行排序 l.sort()

  • 返回元素的个数 l.size()

3. 程序代码

#include<bits/stdc++.h>
#include<list>
using namespace std;
int main() {
 list<int> l;
 cout << "在list容器末尾插入元素:" ;
 l.push_back(1);
 l.push_back(2);
 l.push_back(3);
 l.push_back(4);
 l.push_back(5);
 list<int>::iterator it;
 for(it = l.begin(); it != l.end(); it++) {
  cout << *it << " ";
 }
 cout << endl;
 cout << "在list容器末尾删除元素:";
      l.pop_back();
 l.pop_back();
 l.pop_back();
 for(it = l.begin(); it != l.end(); it++) {
  cout << *it << " ";
 }
 cout << endl;
 cout << "在list容器头部插入元素:";
 l.push_front(6);
 l.push_front(7);
 l.push_front(8);
 for(it = l.begin(); it != l.end(); it++) {
  cout << *it << " ";
 }
 cout << endl;
 cout << "在list容器头部删除元素:";
 l.pop_front();
 l.pop_front();
  for(it = l.begin(); it != l.end(); it++) {
  cout << *it << " ";
 }
 cout << endl;
 cout << "判断list容器是否为空(为空返回1,否则返回为0):";
 cout << l.empty() << endl;
 cout << "对元素进行排序:";
 l.sort();
  for(it = l.begin(); it != l.end(); it++) {
  cout << *it << " ";
 }
 cout << endl;
 cout << "返回元素的个数:";
 cout << l.size() << endl;
 return 0;
}

运行结果:
在list容器末尾插入元素:1 2 3 4 5
在list容器末尾删除元素:1 2
在list容器头部插入元素:8 7 6 1 2
在list容器头部删除元素:6 1 2
判断list容器是否为空(为空返回1,否则返回为0):0
对元素进行排序:1 2 6
返回元素的个数:3

4. list 例题:

浅谈 C++之 STL
list

「代码实现:」

#include<bits/stdc++.h>
using namespace std;
list<int> s;
int main() {
 int M,N;
 cin >> M >> N;
 int count = 0;
 for(int i = 0; i < N; i++) {
  int m;
  cin >> m;

  auto t = s.begin();
  int temp = 1;
  while(t != s.end()) {
   if(*t == m)
    temp = 0;
   t++;
  }

  if(temp) {
   count++;
   if(s.size() == M) {
    s.pop_front();
    s.push_back(m);
   } else
    s.push_back(m);
  }
 }
 cout << count << endl;
 return 0;
}

六、栈 stack

1. 栈的介绍

头文件:#include

定义:stack s;

限定仅在表尾进行插入或删除操作的线性表,表尾—栈顶,表头—栈底,不含元素的空表称空栈。

特点:先进后出(FILO)或后进先出(LIFO)

浅谈 C++之 STL
11

2. 图解 stack

浅谈 C++之 STL
12

3. stack 常用成员函数

  • 栈为空返回真 s.empty()
  • 移除栈顶元素 s.pop()
  • 在栈顶增加元素 s.push()
  • 返回栈中元素个数 s.size()
  • 返回栈顶元素 s.top()

4. 程序代码

#include<bits/stdc++.h>
#include<stack>
using namespace std;
int main(){
 stack<int> s;
 //在栈顶增加元素
 s.push(12);
 s.push(32);
 s.push(33);
 s.push(122);
 cout << "栈顶元素:" << s.top() << endl;
 //从栈顶弹出两个元素
 s.pop();
 s.pop();
 cout <<"栈顶元素:" << s.top() << endl;
 cout << "判断stack容器是否为空(为空返回1,否则返回为0):" << s.empty() << endl;
 cout << "返回元素的个数:" << s.size() << endl;
 return 0;
}
运行代码:
栈顶元素:122
栈顶元素:32
判断stack容器是否为空(为空返回1,否则返回为0):0
返回元素的个数:2

5. stack 例题:括号序列

浅谈 C++之 STL

「代码实现:」

#include<bits/stdc++.h>
using namespace std;
stack<int>q;//定义一个栈
string s,b;//s是输入字符串,b是配对的字符串
int main() {
 cin>>s;//输入
 int l=s.size();
 for (int i=0; i<s.length(); i++) {
  if (s[i]=='(') {
   q.push(i); //入栈
   b[i]=')';//相应的括号匹配上
  }
  if (s[i]=='[') {
   q.push(i); //同理
   b[i]=']';
  }
  if (s[i]==')' || s[i]==']')  //如果是后半边括号
   if (q.empty() || b[q.top()]!=s[i]) { //这里一定是q.empty不要写错了
    if (s[i]==')')
     b[i]='(';
    else
     b[i]='[';//相应的配对
   } else
    b[q.top()]=' ',q.pop();
 }
 for (int i=0; i<l; i++) {
  if (b[i]=='(' or b[i]=='[')
   cout<<b[i];//输出配对
  cout<<s[i];//输出原来的字符
  if (b[i]==')'||b[i]==']')
   cout<<b[i];
 }
 cout<<endl;
 return 0;
}

七、队列 queue

1. 队列的介绍

头文件:#include

定义:queue q;

队列是一种特殊的线性表; 插入位置只有 1 个,限制在表尾进行;删除位置也只有 1 个,限制在表头进行。

特点:先进先出「FIFO(First In First Out)」

2. 图解队列

队列

3. queue 常用成员函数

back() 返回最后一个元素
「empty」 如果队列空则返回真
「front()」 返回第一个元素
「pop()」 删除第一个元素
「push()」 在末尾加入一个元素
「size()」 返回队列中元素的个数

4. 程序代码

#include<bits/stdc++.h>
#include<queue>
using namespace std;
int main() {
 queue<int> q;
 //向空队列添加元素
 q.push(23);
 q.push(12);
 q.push(2);
 cout << "返回队列中元素的个数:";
 cout << q.size() << endl;

 cout << "返回最后一个元素:";
 cout << q.back() << endl;

 cout << "返回第一个元素:";
 cout << q.front() << endl;

 //删除第一元素
 q.pop();
 cout << "返回队列中元素的个数:";
 cout << q.size() << endl;

 cout << "判断stack容器是否为空(为空返回1,否则返回为0):";
 cout << q.empty() << endl;
 return 0;

}
运行结果:
返回队列中元素的个数:3
返回最后一个元素:2
返回第一个元素:23
返回队列中元素的个数:2
判断stack容器是否为空(为空返回1,否则返回为0):0

5. queue 例题:约瑟夫问题

队列例题

「代码实现:」

#include<bits/stdc++.h>
using namespace std;
queue<int> q;
int main() {
 int n, m;
 cin >> n >> m;
 for(int i=1; i<=n; i++) { // 将元素入队
  q.push(i);
 }
 int sum=0;
 while(!q.empty()) {  // 从头开始,如果不是被点到的人,就把该序号进入队列,然后出队
  sum++;           //  否则,就输出并出队
  if(sum==m) {
   cout << q.front() << " ";
   q.pop();
   sum=0;
  }
  else{
   q.push(q.front());
   q.pop();
  }
 }
 return 0;
}

八、优先队列 priority_queue (不常用)

1. 优先队列的介绍

头文件:#include

定义:priority_queue p;

优先队列是队列的一种延伸,优先队列时一种比较重要的数据结构,它是有二项队列编写而成的,可以以「O(log n)」 的效率查找一个队列中的最大值或者最小值,其中是最大值还是最小值是根据「创建的优先队列的性质来决定的。」

2. 优先输出大数据

priority_queue<Type, Container, Functional>Type 为数据类型, Container 为保存数据的容器,Functional 为元素比较方式。如果不写后两个参数,那么容器默认用的是 vector,比较方式默认用 operator<,也就是优先队列是大顶堆,队头元素最大。

示例:

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

int main(){
 priority_queue<int> p;
 p.push(1);
 p.push(2);
 p.push(8);
 p.push(5);
 p.push(43);
 for(int i=0;i<5;i++){
  cout<<p.top()<<endl;
  p.pop();
 }
 return 0;
}
运行结果:
43
8
5
2
1

3. 「优先输出小数据」

(1)方法一

priority_queue<int, vector, greater > p;

示例:

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

int main(){
 priority_queue<int, vector<int>, greater<int> >p;
 p.push(1);
 p.push(2);
 p.push(8);
 p.push(5);
 p.push(43);
 for(int i=0;i<5;i++){
  cout<<p.top()<<endl;
  p.pop();
 }
 return 0;
}
运行结果:
1
2
5
8
43

(2)方法二

自定义优先级,重载默认的 < 符号 (重载先不用理解)

示例:

#include<iostream>
#include<queue>
#include<cstdlib>
using namespace std;
struct Node{
 int x,y;
 Node(int a=0, int b=0):
  x(a), y(b) {}
};

struct cmp{
 bool operator()(Node a, Node b){
  if(a.x == b.x) return a.y>b.y;
  return a.x>b.x;
 }
};

int main(){
 priority_queue<Node, vector<Node>, cmp>p;

 for(int i=0; i<10; ++i)
  p.push(Node(rand(), rand()));//随机生成整数

 while(!p.empty()){
  cout<<p.top().x<<' '<<p.top().y<<endl;
  p.pop();
 }
 return 0;
}
运行结果:
491 9961
5436 4827
11942 2995
15724 19169
16827 23281
18467 41
24464 26962
26500 6334
28145 5705
29358 11478

九、集合 set

1. 集合的介绍

头文件:#include

定义:set s;

在 set 中每个元素的值都唯一,而且系统能根据元素的值自动进行排序。set 中元素的值不能直接被改变。set 内部采用的是一种非常高效的平衡检索二叉树:红黑树,也称为 RB 树(Red-Black Tree)。RB 树的统计性能要好于一般平衡二叉树。

特点:

  • set 中的元素都是排序好的
  • set 中的元素都是唯一的,没有重复的

2. set 常用成员函数

begin() 返回指向第一个元素的迭代器
clear() 清除所有元素
count() 返回某个值元素的个数
empty() 如果集合为空,返回 true
end() 返回指向最后一个元素的迭代器
erase() 删除集合中的元素
find() 返回一个指向被查找到元素的迭代器
insert() 在集合中插入元素
lower_bound() 返回指向大于(或等于)某值的第一个元素的迭代器
max_size() 返回集合能容纳的元素的最大限值
rbegin() 返回指向集合中最后一个元素的反向迭代器
rend() 返回指向集合中第一个元素的反向迭代器
size() 集合中元素的数目
swap() 交换两个集合变量
upper_bound() 返回大于某个值元素的迭代器
value_comp() 返回一个用于比较元素间的值的函数

3. 程序代码

#include<bits/stdc++.h>
#include<set>
using namespace std;
int main()
{
     set<int> s;
     s.insert(1);
     s.insert(2);
     s.insert(3);
     s.insert(1);
     s.insert(9);
     s.insert(7);
    cout<<"set 的 size 值为 :"<<s.size()<<endl;
     cout<<"set 的 maxsize的值为 :"<<s.max_size()<<endl;
     cout<<"set 中的第一个元素是 :"<<*s.begin()<<endl;
     cout<<"set 中的最后一个元素是:"<<*s.end()<<endl;//end()就是size
     set<int>::iterator it;
    for(it=s.begin ();it!=s.end ();it++)
    {
        cout << *it << " ";
    }
    cout << endl;
     s.clear();
     if(s.empty())
     {
         cout<<"set 为空 !!!"<<endl;
     }
     cout<<"set 的 size 值为 :"<<s.size()<<endl;
     cout<<"set 的 maxsize的值为 :"<<s.max_size()<<endl;
     return 0;
}
运行结果:
set 的 size 值为 :5
set 的 maxsize的值为 :461168601842738790
set 中的第一个元素是 :1
set 中的最后一个元素是:5
1 2 3 7 9
set 为空 !!!
set 的 size 值为 :0
set 的 maxsize的值为 :461168601842738790

十、map

1. map 介绍

Map 是 STL 的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在 map 中出现一次,第二个可能称为该关键字的值)的数据 处理能力,由于这个特性,它完成有可能在我们处理一对一数据的时候,在编程上提供快速通道。这里说下 map 内部数据的组织,map 内部自建一颗红黑树(一 种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能,所以在 map 内部所有的数据都是有序的。map 是一类关联式容器。它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。对于迭代器来说,可以修改实值,而不能修改 key。

2. map 的功能

自动建立 Key - value 的对应。key 和 value 可以是任意你需要的类型。

根据 key 值快速查找记录,查找的复杂度基本是 Log(N),如果有 1000 个记录,最多查找 10 次,1,000,000 个记录,最多查找 20 次。

快速插入 Key -Value 记录。

快速删除记录

根据 Key 修改 value 记录。

遍历所有记录。

3. map 的使用

头文件:#include

定义:map<T1, T2> m T1 表示关键字的数据类型, T2 表示值的数据类型

4. map 常用成员函数

begin() 返回指向 map 头部的迭代器

clear() 删除所有元素

count() 返回指定元素出现的次数

empty() 如果 map 为空则返回 true

end() 返回指向 map 末尾的迭代器

equal_range() 返回特殊条目的迭代器对

erase() 删除一个元素

find() 查找一个元素

insert() 插入元素

lower_bound() 返回键值>=给定元素的第一个位置

max_size() 返回可以容纳的最大元素个数

rbegin() 返回一个指向 map 尾部的逆向迭代器

rend() 返回一个指向 map 头部的逆向迭代器

size() 返回 map 中元素的个数

swap() 交换两个 map

upper_bound() 返回键值>给定元素的第一个位置

5. 数据的插入

(1)用 insert 函数插入 pair 数据

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main(){
 map<int, string> mapStudent;
 mapStudent.insert(pair<int, string>(1, "student_one"));
 mapStudent.insert(pair<int, string>(2, "student_two"));
 mapStudent.insert(pair<int, string>(3, "student_three"));
 map<int, string>::iterator iter;
 for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
  cout<<iter->first<<' '<<iter->second<<endl;
 return 0;
}
运行结果:
1 student_one
2 student_two
3 student_three

(2)用 insert 函数插入 value_type 数据

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()  {
    map<int, string> mapStudent;
    mapStudent.insert(map<int, string>::value_type (1, "student_one"));
    mapStudent.insert(map<int, string>::value_type (2, "student_two"));
    mapStudent.insert(map<int, string>::value_type (3, "student_three"));
    map<int, string>::iterator iter;
    for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
       cout<<iter->first<<' '<<iter->second<<endl;
   return 0;
}
运行结果:
1 student_one
2 student_two
3 student_three

(3)用数组方式插入数据(常用)

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main() {
 map<int, string> mapStudent;
 mapStudent[1] = "student_one";
 mapStudent[2] = "student_two";
 mapStudent[3] = "student_three";
 map<int, string>::iterator iter;
 for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
  cout<<iter->first<<' '<<iter->second<<endl;
 return 0;
}
运行结果:
1 student_one
2 student_two
3 student_three

以上三种用法,虽然都可以实现数据的插入,但是它们是有区别的,当然了第一种和第二种在效果上是完成一样的,用 insert 函数插入数据,在数据的 插入上涉及到集合的唯一性这个概念,即当 map 中有这个关键字时,insert 操作是插入数据不了的,但是用数组方式就不同了,它可以覆盖以前该关键字对 应的值,用程序说明。

mapStudent.insert(map<int, string>::value_type (1, "student_one"));

mapStudent.insert(map<int, string>::value_type (1, "student_two"));

上面这两条语句执行后,map 中 1 这个关键字对应的值是“student_one”,第二条语句并没有生效,那么这就涉及到我们怎么知道 insert 语句是否插入成功的问题了,可以用 pair 来获得是否插入成功,程序如下

pair<map<int, string>::iterator, bool> Insert_Pair;

insert_Pair = mapStudent.insert(map<int, string>::value_type (1, "student_one"));

通过 pair 的第二个变量来知道是否插入成功,它的第一个变量返回的是一个 map 的迭代器,如果插入成功的话 Insert_Pair.second 应该是 true 的,否则为 false。

(4)验证插入函数的作用效果

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main() {
 map<int, string> mapStudent;
 pair<map<int, string>::iterator, bool> Insert_Pair;
 Insert_Pair = mapStudent.insert(pair<int, string>(1, "student_one"));
 if(Insert_Pair.second == true)
  cout<<"Insert Successfully"<<endl;
 else
  cout<<"Insert Failure"<<endl;
 Insert_Pair = mapStudent.insert(pair<int, string>(1, "student_two"));
 if(Insert_Pair.second == true)
  cout<<"Insert Successfully"<<endl;
 else
  cout<<"Insert Failure"<<endl;
 map<int, string>::iterator iter;
 for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
  cout<<iter->first<<' '<<iter->second<<endl;
 return 0;
}
运行结果:
Insert Successfully
Insert Failure
1 student_one

(5)验证数组形式插入数据的效果

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()  {
 map<int, string> mapStudent;
 mapStudent[1] = "student_one";
 mapStudent[1] = "student_two";
 mapStudent[2] = "student_three";
 map<int, string>::iterator iter;
 for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
  cout<<iter->first<<' '<<iter->second<<endl;
 return 0;
}
运行结果:
1 student_two
2 student_three

6. 数据的遍历

(1)应用前向迭代器

上面举例程序中到处都是了,略过不表

(2)利用反向迭代器

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main() {
 map<int, string> mapStudent;
 mapStudent.insert(pair<int, string>(1, "student_one"));
 mapStudent.insert(pair<int, string>(2, "student_two"));
 mapStudent.insert(pair<int, string>(3, "student_three"));
 map<int, string>::reverse_iterator iter;
 for(iter = mapStudent.rbegin(); iter != mapStudent.rend(); iter++)
  cout<<iter->first<<"  "<<iter->second<<endl;
 return 0;
}
运行结果:
3  student_three
2  student_two
1  student_one

(3)用数组方式

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()  {
 map<int, string> mapStudent;
 mapStudent.insert(pair<int, string>(1, "student_one"));
 mapStudent.insert(pair<int, string>(2, "student_two"));
 mapStudent.insert(pair<int, string>(3, "student_three"));
 int nSize = mapStudent.size();  //map中元素的个数
//此处应注意,应该是 for(int nindex = 1; nindex <= nSize; nindex++)
//而不是 for(int nindex = 0; nindex < nSize; nindex++)
 for(int nindex = 1; nindex <= nSize; nindex++)
  cout<<mapStudent[nindex]<<endl;
 return 0;
}
运行结果:
student_one
student_two
student_three

7. 查找并获取 map 中的元素

在这里我们将体会,map 在数据插入时保证有序的好处。

要判定一个数据(关键字)是否在 map 中出现的方法比较多,这里标题虽然是数据的查找,在这里将穿插着大量的 map 基本用法。

这里给出三种数据查找方法

第一种:用 count 函数来判定关键字是否出现,其缺点是无法定位数据出现位置,由于 map 的特性,一对一的映射关系,就决定了 count 函数的返回值只有两个,要么是 0,要么是 1,出现的情况,当然是返回 1 了

第二种:用 find 函数来定位数据出现位置,它返回的一个迭代器,当数据出现时,它返回数据所在位置的迭代器,如果 map 中没有要查找的数据,它返回的迭代器等于 end 函数返回的迭代器。

查找 map 中是否包含某个关键字条目用 find()方法,传入的参数是要查找的 key,在这里需要提到的是 begin()和 end()两个成员,

分别代表 map 对象中第一个条目和最后一个条目,这两个数据的类型是 iterator.

「程序代码:」

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()  {
    map<int, string> mapStudent;
    mapStudent.insert(pair<int, string>(1, "student_one"));
    mapStudent.insert(pair<int, string>(2, "student_two"));
    mapStudent.insert(pair<int, string>(3, "student_three"));
    map<int, string>::iterator iter;
    iter = mapStudent.find(1);
    if(iter != mapStudent.end())
       cout<<"Find, the value is "<<iter->second<<endl;
    else
       cout<<"Do not Find"<<endl;
    return 0;
}
运行结果:
Find, the value is student_one

8. 从 map 中删除元素

移除某个 map 中某个条目用 erase()

该成员方法的定义如下:

iterator erase(iterator it);//通过一个条目对象删除

iterator erase(iterator first,iterator last)//删除一个范围

size_type erase(const Key&key);//通过关键字删除

clear()就相当于 enumMap.erase(enumMap.begin(),enumMap.end());

这里要用到 erase 函数,它有三个重载了的函数,下面在例子中详细说明它们的用法

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()  {
 map<int, string> mapStudent;
 mapStudent.insert(pair<int, string>(1, "student_one"));
 mapStudent.insert(pair<int, string>(2, "student_two"));
 mapStudent.insert(pair<int, string>(3, "student_three"));
 //如果你要演示输出效果,请选择以下的一种,你看到的效果会比较好
 //如果要删除1,用迭代器删除
 map<int, string>::iterator iter;
 iter = mapStudent.find(1);
 mapStudent.erase(iter);
 //如果要删除1,用关键字删除
 int n = mapStudent.erase(1);//如果删除了会返回1,否则返回0
 //用迭代器,成片的删除
 //一下代码把整个map清空
 mapStudent.erase( mapStudent.begin(), mapStudent.end() );
 //成片删除要注意的是,也是STL的特性,删除区间是一个前闭后开的集合
 //自个加上遍历代码,打印输出吧
 return 0;
}

9. map 中的 sort 的用法

map 中的元素是自动按 Key 升序排序,所以不能对 map 用 sort 函数;

这里要讲的是一点比较高深的用法了,排序问题,STL 中默认是采用小于号来排序的,以上代码在排序上是不存在任何问题的,因为上面的关键字是 int 型,它本身支持小于号运算,在一些特殊情况,比如关键字是一个结构体,涉及到排序就会出现问题,因为它没有小于号操作,insert 等函数在编译的时候过 不去,下面给出两个方法解决这个问题。

「这里只解析第一种方法」

(1)小于号重载

#include <iostream>
#include <string>
#include <map>
using namespace std;
typedef struct tagStudentinfo  {
 int      niD;
 string   strName;
 bool operator < (tagStudentinfo const& _A) const  {     //这个函数指定排序策略,按niD排序,如果niD相等的话,按strName排序
  if(niD < _A.niD)
   return true;
  if(niD == _A.niD)
   return strName.compare(_A.strName) < 0;
  return false;
 }

} Studentinfo, *PStudentinfo; //学生信息

int main()  {

 int nSize;   //用学生信息映射分数
 map<Studentinfo, int>mapStudent;
 map<Studentinfo, int>::iterator iter;
 Studentinfo studentinfo;
 studentinfo.niD = 1;
 studentinfo.strName = "student_one";
 mapStudent.insert(pair<Studentinfo, int>(studentinfo, 90));
 studentinfo.niD = 2;
 studentinfo.strName = "student_two";
 mapStudent.insert(pair<Studentinfo, int>(studentinfo, 80));
 for (iter=mapStudent.begin(); iter!=mapStudent.end(); iter++)
  cout<<iter->first.niD<<' '<<iter->first.strName<<' '<<iter->second<<endl;

 return 0;
}
运行结果:
1 student_one 90
2 student_two 80


以上是关于浅谈 C++之 STL的主要内容,如果未能解决你的问题,请参考以下文章

C++ STL之pair详解

我的Android进阶之旅NDK开发之在C++代码中使用Android Log打印日志,打印出C++的函数耗时以及代码片段耗时详情

C++ STL之queue详解

C++ STL之set详解

C++ STL之set详解

STL库之集合基本使用方法