本专栏主要基于北大郭炜老师的程序设计与算法系列课程进行整理,包括课程笔记和OJ作业。该系列课程有三部分: (一) C语言程序设计;(二) 算法基础;(三) C++面向对象程序设计
1. STL概述
- STL: (Standard Template Library) 标准模板库
- 包含一些常用的算法如排序查找,还有常用的数据结构如可变长数组、链表 、字典等。
- 使用方便,效率较高
- 要使用其中的算法,需要#include
2. 排序算法 sort
用sort进行排序(用法一)
sort()基于快速排序。
- 对基本类型的数组从小到大排序:
sort(数组名+n1,数组名+n2);
n1和n2都是int类型的表达式,可以包含变量
如果n1=0,则 + n1可以不写
将数组中下标范围为[n1,n2)的元素从小到大排序。下标为n2的元素不在排序区间内。
int a[] = {15,4,3,9,7,2,6};
sort(a,a+7); //对整个数组从小到大排序 a[0]~a[6]
int a[] = {15,4,3,9,7,2,6};
sort(a,a+3); // 结果:{3,4,15,9,7,2,6} a[0]~a[2]
int a[] = {15,4,3,9,7,2,6};
sort(a+2,a+5); //结果:{15,4,3,7,9,2,6} a[2]~a[4]
用sort进行排序(用法二)
- 对元素类型为T的基本类型数组从大到小排序:
sort(数组名+n1,数组名+n2,greater());
int a[] = {15,4,3,9,7,2,6};
sort(a+1,a+4,greater<int>()); // 结果:{15,9,4,3,7,2,6} a[1]~a[3] 从大到小
用sort进行排序(用法三)
- 用自定义的排序规则,对任何类型T(包括基本类型和自定义类型)的数组排序
sort(数组名+n1,数组名+n2,排序规则结构名());
- 排序规则结构的定义方式:
struct 结构名 {
bool operator()( const T & a1,const T & a2) const {
//若a1应该在a2前面,则返回true。
//否则返回false。
}
};
排序规则返回 true,意味着 a1 必须在 a2 前面;返回 false,意味着 a1 并非必须在 a2 前面.
排序规则的写法,不能造成比较 a1,a2 返回 true;比较 a2,a1 也返回 true。否则sort会 runtime error
比较 a1,a2 返回 false 比较 a2,a1 也返回 false,则没有问题。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
//自定义排序规则
struct Rule1 //按从大到小排序
{
bool operator()( const int & a1,const int & a2) const {
return a1 > a2; //为true是 a1必须在a2前面
}
};
struct Rule2 //按个位数从小到大排序
{
bool operator()( const int & a1,const int & a2) const {
return a1%10 < a2%10;
}
};
void Print(int a[],int size) {
for(int i = 0;i < size;++i)
cout << a[i] << "," ;
cout << endl;
}
int main()
{
int a[] = { 12,45,3,98,21,7};
sort(a,a+sizeof(a)/sizeof(int)); //从小到大
cout << "1) "; Print(a,sizeof(a)/sizeof(int));
sort(a,a+sizeof(a)/sizeof(int),Rule1()); //从大到小
cout << "2) "; Print(a,sizeof(a)/sizeof(int));
sort(a,a+sizeof(a)/sizeof(int),Rule2()); //按个位数从小到大
cout << "3) "; Print(a,sizeof(a)/sizeof(int));
return 0;
}
用sort对结构数组(自定义类型数组)进行排序(用法3)
#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) const {
if( stricmp(s1.name,s2.name) < 0)
return true;
return false;
}
};
struct StudentRule2 { //按id从小到大排
bool operator() (const Student & s1,const Student & s2) const {
return s1.id < s2.id;
}
};
struct StudentRule3 {//按gpa从高到低排
bool operator() (const Student & s1,const Student & s2) const {
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;
}
3. 二分查找算法
- STL提供在排好序的数组上进行二分查找的算法
binary_search
lower_bound
upper_bound
用binary_search进行二分查找(用法一)
- 在从小到大排好序的基本类型数组上进行二分查找
binary_search(数组名+n1,数组名+n2,值);
n1和n2都是int类型的表达式,可以包含变量
如果n1=0,则 + n1可以不写
查找区间为下标范围为[n1,n2)的元素,下标为n2的元素不在查找区间内
在该区间内查找"等于"值的元素,返回值为true(找到)或false(没找到)
"等于"的含义: a 等于 b <=> a < b和b < a都不成立
用binary_search进行二分查找(用法二)
- 在用自定义排序规则排好序的、元素为任意的T类型的数组中进行二分查找
binary_search(数组名+n1,数组名+n2,值,排序规则结构名());
n1和n2都是int类型的表达式,可以包含变量
如果n1=0,则 + n1可以不写
查找区间为下标范围为[n1,n2)的元素,下标为n2的元素不在查找区间内
在该区间内查找"等于"值的元素,返回值为true(找到)或false(没找到)
查找时的排序规则,必须和排序时的规则一致! (否则就算有结果,也没有意义)
"等于"的含义: a 等于 b <=> "a必须在b前面"和"b必须在a前面"都不成立
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
//排序规则
struct Rule //按个位数从小到大排 {
bool operator()( const int & a1,const int & a2) const {
return a1%10 < a2%10;
}
};
void Print(int a[],int size) {
for(int i = 0;i < size;++i)
cout << a[i] << "," ;
cout << endl;
}
int main()
{
int a[] = { 12,45,3,98,21,7};
sort(a,a+6); //排序时的规则 默认从小到大排序
Print(a,6);
cout <<"result:"<< binary_search(a,a+6,12) << endl; //查找的排序规则默认是从小到大排序
cout <<"result:"<< binary_search(a,a+6,77) << endl;
sort(a,a+6,Rule()); //按个位数从小到大排
Print(a,6);
cout <<"result:"<< binary_search(a,a+6,7) << endl; //查找的排序规则默认是从小到大排序,而此时排序的规则时 按个位数从小到大排。 不一致,结果无意义。
cout <<"result:"<< binary_search(a,a+6,8,Rule()) << endl; //返回true可以找到,也就是98 因为按个位数从小到大排序时,98必须排在8前面不成立 8必须排在98前面也不成立
}
"等于"的含义: a 等于 b <=> "a必须在b前面"和"b必须在 a前面"都不成立
#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) const {
if( stricmp(s1.name,s2.name) < 0)
return true;
return false;
}
};
struct StudentRule2 { //按id从小到大排
bool operator() (const Student & s1,const Student & s2) const {
return s1.id < s2.id;
}
};
struct StudentRule3 {//按gpa从高到低排
bool operator() (const Student & s1,const Student & s2) const {
return s1.gpa > s2.gpa;
}
};
int main()
{
Student s;
strcpy(s.name,"Mary");
s.id= 117;
s.gpa = 0;
int n = sizeof(students) / sizeof(Student);
sort(students,students+n,StudentRule1()); //按姓名从小到大排
cout << binary_search( students , students+n,s,StudentRule1()) << endl; //true可以找到 只按名字来找
strcpy(s.name,"Bob");
cout << binary_search( students , students+n,s,StudentRule1()) << endl; //false
sort(students,students+n,StudentRule2()); //按id从小到大排
cout << binary_search( students , students+n,s, StudentRule2()) << endl; //true可以找到 只按id来找
}
用lower_bound二分查找下界(用法一)
- 对元素类型为T的从小到大排好序的基本类型的数组中进行查找
T * lower_bound(数组名+n1,数组名+n2,值);
返回一个指针 T * p;
*p 是查找区间里下标最小的,大于等于"值" 的元素。如果找不到,p指向下标为n2的元素
用lower_bound二分查找下界(用法二)
- 在元素为任意的T类型、按照自定义排序规则排好序的数组中进行查找
T * lower_bound(数组名+n1,数组名+n2,值,排序规则结构名());
返回一个指针 T * p;
*p 是查找区间里下标最小的,按自定义排序规则,可以排在"值"后面的元素。如果找 不到,p指向下标为n2的元素。
用upper_bound二分查找上界(用法一)
- 在元素类型为T的从小到大排好序的基本类型的数组中进行查找
T * upper_bound(数组名+n1,数组名+n2,值);
返回一个指针 T * p;
*p 是查找区间里下标最小的,大于"值"的元素。如果找不到,p指向下标为n2的元素
用upper_bound二分查找上界(用法二)
- 在元素为任意的T类型、按照自定义排序规则排好序的数组中进行查找
T * upper_bound(数组名+n1,数组名+n2,值,排序规则结构名());
返回一个指针 T * p;
*p 是查找区间里下标最小的,按自定义排序规则,必须排在"值"后面的元素。如果找 不到,p指向下标为n2的元素
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
//排序规则
struct Rule //按个位数从小到大排 {
bool operator()( const int & a1,const int & a2) const {
return a1%10 < a2%10;
}
};
void Print(int a[],int size) {
for(int i = 0;i < size;++i)
cout << a[i] << "," ;
cout << endl;
}
#define NUM 7
int main()
{
int a[NUM] = { 12,5,3,5,98,21,7};
sort(a,a+NUM); //默认从小到大
Print(a,NUM); // => 3,5,5,7,12,21,98,
int * p = lower_bound(a,a+NUM,5); //返回指向 下标最小的 >=5的元素
cout << *p << "," << p-a << endl; //=> 5,1
p = upper_bound(a,a+NUM,5); //返回指向 下标最小的 >5的元素
cout << *p << endl; //=>7
cout << * upper_bound(a,a+NUM,13) << endl; //=>21 //下标最小的 >13的元素
sort(a,a+NUM,Rule()); //按个位数从小到大排
Print(a,NUM); //=>21,12,3,5,5,7,98,
cout << * lower_bound(a,a+NUM,16,Rule()) << endl; // 7 下标最小的 按照个位数排序 可以排在16后面的元素
cout << lower_bound(a,a+NUM,25,Rule()) - a<< endl; // 3
cout << upper_bound(a,a+NUM,18,Rule()) - a << endl; // 7 没有找到 a+NUM-a
if( upper_bound(a,a+NUM,18,Rule()) == a+NUM) //超过查找范围
cout << "not found" << endl; //=> not found
cout << * upper_bound(a,a+NUM,5,Rule()) << endl; // =>7 下标最小的 按照个位数排序 必须排在5后面的元素
cout << * upper_bound(a,a+NUM,4,Rule()) << endl; // =>5 下标最小的 按照个位数排序 必须排在4后面的元素
return 0;
}
4. STL中的平衡二叉树
- 有时需要在大量增加、删除数据的同时, 还要进行大量数据的查找
- 希望增加数据、删除数据、查找数据都能在 log(n)复杂度完成
- 排序+二分查找显然不可以,因加入新数据就要重新排序
- 可以使用“平衡二叉树”数据结构存放数据,体现在STL中,就是以 下四种“排序容器” :
multiset set multimap map
如果固定死数据量,没有删除和新插入的数据,那么可以用排序+二分查找,如果有新加入的数据,这种方法必须重新排序,效率很低。而以上四种容器,插入新数据后,可以自动排序,插入数据、删除数据、查找数据都能在 log(n)复杂度完成。
5. multiset
multiset st;
- 定义了一个multiset变量st,st里面可以存放T类型的数据,并且能自动排序。开始st为空
- 排序规则:表达式 “a < b” 为true,则 a 排在 b 前面
- 可用 st.insert添加元素,st.find查找元素,st.erase删除元素,复杂度 都是 log(n)
#include <iostream>
#include <cstring>
#include <set> //使用multiset和set需要此头文件 using namespace std;
int main()
{
multiset<int> st; //默认从小到大排
int a[10]={1,14,12,13,7,13,21,19,8,8 };
for(int i = 0;i < 10; ++i)
st.insert(a[i]); //插入的是a[i]的复制品
multiset<int>::iterator i; //迭代器,近似于指针
for(i = st.begin(); i != st.end(); ++i)
cout << * i << ","; //1,7,8,8,12,13,13,14,19,21,
cout << endl;
i = st.find(22); //查找22,返回值是迭代器
if( i == st.end()) //找不到则返回值为 end()
cout << "not found" << endl;
st.insert(22); //插入 22 插入新元素会自动排序 logn
i = st.find(22);
if( i == st.end())
cout << "not found" << endl;
else
cout << "found:" << *i <<endl; //找到则返回指向找到的元素的迭代器
i = st.lower_bound(13); //返回最靠后的迭代器 it,使得[begin(),it)中的元素 都在 13 前面 ,复杂度 log(n)
cout << * i << endl; //13 >=13 下标最小的
i = st.upper_bound(8); //返回最靠前的迭代器 it,使得[it,end())中的元素都在 8 后面,复杂度 log(n)
cout << * i << endl; //12 >8 下标最小的
st.erase(i); //删除迭代器 i 指向的元素,即12
for(i = st.begin(); i != st.end(); ++i)
cout << * i << ",";
return 0;
}
multiset 上的迭代器
multiset::iterator p;
- p是迭代器,相当于指针,可用于指向multiset中的元素。访问multiset中的元素要通 过迭代器。
- 与指针的不同: multiset上的迭代器可 ++ ,–, 用 != 和 == 比较,不可比大小,不可加减整数,不 可相减
multiset st;
- st.begin() 返回值类型为 multiset::iterator, 是指向st中的头一个元素的迭代器
- st.end() 返回值类型为 multiset::iterator, 是指向st中的最后一个元素后面的迭代器
- 对迭代器 ++ ,其就指向容器中下一个元素,-- 则令其指向上一个元素
自定义排序规则的multiset用法
#include <iostream>
#include <cstring>
#include <set>
using namespace std;
struct Rule1 {
bool operator()( const int & a,const int & b) const {
return (a%10) < (b%10); }//返回值为true则说明a必须排在b前面
};
int main()
{
multiset<int,greater<int> > st; //排序规则为从大到小
int a[10]={1,14,12,13,7,13,21,19,8,8 };
for(int i = 0;i < 10; ++i)
st.insert(a[i]);
multiset<int,greater<int> >::iterator i;
for(i = st.begin(); i != st.end(); ++i)
cout << * i << ",";
cout << endl; //21,19,14,13,13,12,8,8,7,1,
multiset<int,Rule1 > st2; //排序规则为自定义排序规则
//st2的元素排序规则为:个位数小的排前面
for(int i = 0;i < 10; ++i)
st2.insert(a[i]);
multiset<int,Rule1>::iterator p;
for(p = st2.begin(); p != st2.end(); ++p)
cout << * p << ",";
cout << endl;
p = st2.find(133);
cout << * p << endl; //13 按照个位数排序 13必须排在133前面不成立 133必须排在13前面也不成立
}
find(x): 在排序容器中找一个元素y,使得 “x必须排在y前面”和 “y必须排在x前面” 都不成立
#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;
struct Student {
char name[20];
int id;
int score;
};
Student students [] = { {"Jack",112,78},{"Mary",102,85},
{"Ala",333,92},{"Zero",101,70},{"Cindy",102,78}};
struct Rule { //如果分数不一样 按分数从大到小排序 若分数一样,则按名字的字典序排序
bool operator() (const Student & s1,const Student & s2) const {
if( s1.score != s2.score)
return s1.score > s2.score;
else
return (strcmp(s1.name,s2.name) < 0);
}
};
int main()
{
multiset<Student,Rule> st; //自定义类型 自定义规则
for(int i = 0;i < 5;++i)
st.insert(students[i]); //插入的是students[i]的复制品
multiset<Student,Rule>::iterator p;
for(p = st.begin(); p != st.end(); ++p)
cout << p->score <<" "<<p->name<<" "<< p->id <<endl;
Student s = { "Mary",1000,85};
p = st.find(s); //查找时的排序规则 和 排序时一致。
if( p!= st.end()) //找到了
cout << p->score <<" "<< p->name<<" " << p->id <<endl;//85 Mary 102 Rule和id无关 s = { "Mary",1000,85}和结构数组中的 { "Mary",102,85}按照规则 二者谁前谁后无所谓。
return 0;
}
6. set
set的用法
-
set和multiset的区别在于容器里不能有重复元素
a和b重复 等价于 “a必须排在b前面” 和“b必须排在a前面”都不成立 -
set插入元素可能不成功
#include <iostream>
#include <cstring>
#include <set>
using namespace std;
int main()
{
set<int> st; //默认排序规则 从小到大
int a[10] ={ 1,2,3,8,7,7,5,6,8,12 };
for(int i = 0;i < 10; ++i)
st.insert(a[i]);
cout << st.size() << endl; //输出:8 去除了重复元素
set<int>::iterator i;
for(i = st.begin(); i != st.end(); ++i)
cout << * i << ","; //输出:1,2,3,5,6,7,8,12,
cout<<endl;
pair<set<int>::iterator, bool> result = st.insert(2); //set的insert函数返回的是pair类型 pair类型的第一个元素是set迭代器类型 第二个元素是bool类型 表示插入是否成功
if(! result.second ) //条件成立说明插入不成功
cout << * result.first <<" already exists." << endl;
else
cout << * result.first << " inserted." << endl;
return 0;
}
pair模板的用法
pair<T1,T2>类型等价于:
struct {
T1 first;
T2 second;
}
例如:pair<int, double> a;
等价于:
struct {
int first;
double second;
} a;
a.first = 1;
a.second = 93.93;
7. multimap
multimap的用法
multimap容器里的元素,都是pair形式的
multimap<T1,T2> mp;
则mp里的元素都是如下类型:
struct {
T1 first; //关键字
T2 second; //值
};
multimap中的元素按照first排序,并可以按first进行查找
缺省的排序规则是 “a.first < b.first” 为true,则a排在b前面
multimap的应用
一个学生成绩录入和查询系统,接受以下两种输入:
Add name id score
Query score
name是个不超过16字符的字符串,中间没有空格(可以用cin直接读),代表学生姓名。id 是个整数,代表学号。score是个整数,表示分数。学号不会重复,分数 和姓名都可能重复。
两种输入交替出现。第一种输入表示要添加一个学生的信息,碰到这 种输入,就记下学生的姓名、id和分数。第二种输入表示要查询,碰到这 种输入,就输出已有记录中分数比score低的最高分获得者的姓名、学号 和分数。如果有多个学生都满足条件,就输出学号最大的那个学生的信息。如果找不到满足条件的学生,则输出“Nobody”
#include <iostream>
#include <map> //使用multimap和map需要包含此头文件
#include <cstring>
using namespace std;
struct StudentInfo {
int id;
char name[20];
};
struct Student { //按分数进行排序 和 查询
int score;
StudentInfo info;
};
typedef multimap<int,StudentInfo> MAP_STD;
// 此后 MAP_STD 等价于 multimap<int,StudentInfo> 更简便
// typedef int * PINT;
//则此后PINT等价于int*。即PINTp;等价于 int*p;
int main()
{
MAP_STD mp;
Student st;
char cmd[20];
while( cin >> cmd ) {
if( cmd[0] == 'A') { //添加学生信息
cin >> st.info.name >> st.info.id >> st.score ;
mp.insert(make_pair(st.score,st.info )); //成对插入
} //make_pair生成一个 pair<int,StudentInfo>变量 //其first 等于 st.score, second 等于 st.info
else if( cmd[0] == 'Q' ){ //根据分数进行查询
int score;
cin >> score;
MAP_STD::iterator p = mp.lower_bound (score); //迭代器指向 下标最小的 >=score的那个元素
if(p! = mp.begin()){ //我们要找的是比score小的最高分
--p; //往前移动一个元素
score = p->first; //比要查询分数低的最高分
MAP_STD::iterator maxp = p;
int maxId = p->second.id;
for(; p != mp.begin() && p->first == score; --p) { //分数相同的话 找学号最大的
if( p->second.id > maxId ) {
maxp = p;
maxId = p->second.id ;
}
}
//如果上面循环是因为 p == mp.begin() 而终止,如果此时p指向的元素的score仍然相同,那么还要进行处理
if(p->first == score){
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;
}
8. map
map的用法
和multimap区别在于:
- 不能有关键字重复的元素
- 可以使用 [] ,下标为关键字,返回值为first和关键字相同的元素的second
- 插入元素可能失败
#include <iostream>
#include <map>
#include <string> //字符串对象
using namespace std;
struct Student {
string name;
int score;
};
Student students[5] = { {"Jack",89},{"Tom",74},{"Cindy",87},{"Alysa",87},{"Micheal",98}};
typedef map<string,int> MP;
int main()
{
MP mp;
for(int i = 0;i < 5; ++i)
mp.insert(make_pair(students[i].name,students[i].score));
cout << mp["Jack"] << endl; // 输出 89
mp["Jack"] = 60; //修改名为"Jack"的元素的second
for(MP::iterator i = mp.begin(); i != mp.end(); ++i)
cout << "(" << i->first << "," << i->second << ") ";
//输出:(Alysa,87) (Cindy,87) (Jack,60) (Micheal,98) (Tom,74)
cout << endl;
Student st;
st.name = "Jack";
st.score = 99;
pair<MP::iterator, bool> p = mp.insert(make_pair(st.name,st.score));
if( p.second ) //插入成功
cout << "(" << p.first->first << ","<< p.first->second << ") inserted" <<endl;
else
cout << "insertion failed" << endl; //输出此信息
mp["Harry"] = 78; //插入一元素,其first为"Harry",然后将其second改为78
MP::iterator q = mp.find("Harry");
cout << "(" << q->first << "," << q->second <<")" <<endl;
//输出 (Harry,78)
return 0;
}
map例题:单词词频统计程序
输入大量单词,每个单词,一行,不超过20字符,没有 空格。 按出现次数从多到少输出这些单词及其出现次数 。出现次数相同的,字典序靠前的在前面.
#include <iostream>
#include <set>
#include <map>
#include <string>
using namespace std;
struct Word {
int times;
string wd;
};
struct Rule {
bool operator () ( const Word & w1,const Word & w2) const {
if( w1.times != w2.times)
return w1.times > w2.times;
else
return w1.wd < w2.wd;
}
}
int main()
{
string s;
set<Word,Rule> st;
map<string,int> mp;
while( cin >> s ) //读入一个单词 插入map中,并统计出现次数 map的关键字是单词 值为出现的次数
++ mp[s] ;
for( map<string,int>::iterator i = mp.begin();i != mp.end(); ++i) {
Word tmp;
tmp.wd = i->first; tmp.times = i->second;
st.insert(tmp);
}
for(set<Word,Rule>::iterator i = st.begin();i != st.end(); ++i)
cout << i->wd << " " << i->times << endl;
}
return 0;
}