C++竞赛常用算法及函数
# C++算法竞赛常用函数及算法
# 一、 string 类在算法中的常见用法
# 构造,赋值,存取,拼接,查找,替换,比较,子串,插入,删除
#include<iostream>
#include<string>
//#include<cstring>
//#include<algorithm>
using namespace std;
string s="string";
char a[20]={"char[]"};
int main(){
//1、字符串拼接
//支持使用 + 连接字符串
//2、字符串输入输出
//cin >> s; //输入Hello World! cin是以空格,回车作为结束输入的标志
//cout<< s <<endl; //输出为Hello
//3、读取一行字符(cin.getline())
//cin.getline定义在<iostream>下,如:a[n] 则最多读取 n-1个字符
//cin.getline(a,8);
//4、获取字符串的长度
//size() , length()
//5、可以把字符串当成字符数组使用
//6、字符串与字符数组的互换,C-风格的字符数组可以与字符串互相转换
//字符串转字符数组
//内置函数 copy
//int lens = s.copy(a,s.size());
//a[lens] = '\0';
//strcpy 函数
//strcpy(a,s.c_str());//头文件使用 cstring
//字符数组转字符串
//string s(a);
//7、字符串的比较 ,数组用strcmp(),相等为0,不等为1 (或者循环遍历)
//8、字符串的子串(截取字符串) substr()
//cout<<s.substr(1,3)<<endl;
//9、查找字符串的子串 find()
//cout<<s.find("tr")<<endl;// 1
//cout<<s.find("sss")<<endl; //输出18446744073709551615 额,反正就是没有找到
//10、替换字符串 replace(起始位置,个数,"替换内容")
//s.replace(3,1,"XI");// 输出 strXI
//指定替换
//int n=s.find("t");
//s.replace(n,3,"HUA");// 输出 sHUAIng
//11、删除子串
//s.erase(position,nummer); //起始位置,删除个数
//12、添加子串
//s.insert(5,"aaa");//5表示位置 "aaa"表示增加的字符串 ,输出 strinaaag
//13、reverse 反转函数
//reverse(s.begin(),s.end());// 使用头文件 <algorithm>
//14、sort 排序
//sort(s.begin(),s.end());// 使用头文件 <algorithm>
return 0;
}
# 二、 sort 自定义排序(结构体,数字)
#include<iostream>
#include<algorithm>
using namespace std;
bool cmp(int x,int y){
return x < y;
}
struct Student{
string name;
int score;
Student() {}
Student(string n,int s):name(n),score(s) {}
};
bool cmp_score(Student x,Student y){
return x.score>y.score;
}
int main(){
//自定义排序
//1、对整型数组的排序
int num[5] = {1,2,3,5,4};
sort(num,num+5,cmp);
for(int i=0;i<5;i++){
cout<<num[i]<<" ";
}
//2、对结构体排序
Student stu[3];
stu[0]=Student("小希",100);
stu[1]=Student("小乐",88);
stu[2]=Student("小明",95);
sort(stu,stu+3,cmp_score);
cout<<endl;
for(int i=0;i<3;i++){
cout<<stu[i].name<<" "<<stu[i].score<<endl;
}
return 0;
}
# 三、 max_element()和 min_element() 寻找最值
# 使用 vector(数组类似)
#include <iostream>
#include <vector>
#include <algorithm>
#define pb push_back
using namespace std;
int main() {
vector<int> v;
int n;
cin >> n;
while (n--) {
int num;
cin >> num;
v.pb(num);
}
auto minv = min_element(v.begin(), v.end());
auto maxv = max_element(v.begin(), v.end());
cout << *minv << " " << *maxv;
return 0;
}
# 四、二分查找的函数
C++的头文件 algorithm 中有用于二分查找的函数,分别为lower_bound()
、upper_bound()
以及binary_search()
:
使用这些函数的前提是数组必须是有序的(升/降)。
lower_bound()
:返回大于或等于目标值的第一个位置upper_bound()
:返回大于目标值的第一个位置binary_search()
:若目标值存在则返回 true,否则返回 false
这三个函数都有三个参数:分别为数组的起始位置、数组的终止位置(取不到)以及要查找的目标值,返回值为物理地址,因此要获得对应的逻辑地址,需要减去数组的起始位置。
#include<iostream>
#include<algorithm>
using namespace std;
int a[8] = {1, 2, 4, 4, 5, 8, 10, 22};
int main(){
int x = lower_bound(a, a + 8, 4)-a;
int y = upper_bound(a, a + 8, 4)-a;
bool z = binary_search(a, a + 8, 4);
cout<<x<<" "<<y<<" "<<z<<endl; //输出 2 4 1
return 0;
}
# 二分查找算法
核心思想:不断缩小搜索区域,降低查找目标元素的难度
以在升序序列中查找目标元素为例,二分查找算法的实现思路是:
- 初始状态下,将整个序列作为搜索区域(假设为 [B, E]);
- 找到搜索区域内的中间元素(假设所在位置为 M),和目标元素进行比对。如果相等,则搜索成功;如果中间元素大于目标元素,表明目标元素位于中间元素的左侧,将 [B, M-1] 作为新的搜素区域;反之,若中间元素小于目标元素,表明目标元素位于中间元素的右侧,将 [M+1, E] 作为新的搜素区域;
- 重复执行第二步,直至找到目标元素。如果搜索区域无法再缩小,且区域内不包含任何元素,表明整个序列中没有目标元素,查找失败。
# 五、tolower(),toupper()大小写转换
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
void mytolower(char *s){
int len=strlen(s);
for(int i=0;i<len;i++){
if(s[i]>='A'&&s[i]<='Z'){
s[i]=tolower(s[i]);
//s[i]+=32;//+32转换为小写
//s[i]=s[i]-'A'+'a';
}
}
}
void mytoupper(char *s){
int len=strlen(s);
for(int i=0;i<len;i++){
if(s[i]>='a'&&s[i]<='z'){
s[i]=toupper(s[i]);
//s[i]-=32;//+32转换为小写
//s[i]=s[i]-'a'+'A';
}
}
}
int main() {
cout<<"请输入一个含大写的字符串:";
char s[201];
gets(s);
mytolower(s);
cout<<"转化为小写后为:"<<s<<endl;
mytoupper(s);
cout<<"转化为大写后为:"<<s<<endl;
return 0;
}
# 六、 全排列函数( next_permutation() )
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
int num[3]={1,2,3};
do{
cout<<num[0]<<" "<<num[1]<<" "<<num[2]<<endl;
}while(next_permutation(num,num+3));
/* 输出:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
*/
return 0;
}
# 七、 动态规划(dp)
动态规划(Dynamic Programming,简称 DP)是一种求解决策过程中的最优化问题的方法,它的核心思想是将大问题分解为小问题进行求解,再从这些小问题的答案构建大问题的答案。 动态规划的基本思路:
- 划分子问题:将原问题分解为多个相似的子问题。子问题之间通常存在依赖关系。
- 存储子问题的解:每个子问题只解决一次,并将答案存储在一个表格中,这样在需要时可以直接查找,不必重新计算。
- 自底向上的计算:根据子问题之间的依赖关系,从小的子问题开始计算,直到得到原问题的答案。
动态规划的关键要素:
- 状态:表示问题的某种局部情况或属性。
- 状态转移方程:描述了状态之间如何转移,或者说如何从一个或多个已知状态得到另一个状态的解。
- 初始状态和边界状态:问题的最小子问题的答案以及边界情况。
动态规划的步骤:
- 定义子问题。
- 实现要存储子问题解的数据结构。
- 找到状态转移方程。
- 确定初始状态和边界状态。
- 自底向上计算。
动态规划与递归和分治的区别:
- 递归:是一种程序调用自身的编程技巧。
- 分治:是一种将问题分解为独立的子问题来解决的策略。
- 动态规划:与分治类似,但是子问题不是独立的,动态规划需要存储子问题的解来避免重复计算。
# 八、 贪心算法
贪心算法是算法设计中的一种方法,它在每一步选择中都采取在当前看来最好的选择,也就是局部最优选择,以希望这样的选择能导致全局最优解。 贪心算法的特点:
- 局部最优选择:在每个决策点,选择当前最佳的解决方案,而不考虑此选择会导致的后果。
- 不回退( 无后效性 ):一旦做出选择,就不再改变,也就是说它不像回溯算法那样能返回上一步。
贪心算法的基本结构:
从问题的某一初始解出发。采用循环结构,当可以向求解目标前进一步时:
选择当前状态下的最佳选择。更新当前状态,通常是向前一步。
直到达到问题的一个解为止。
贪心算法与动态规划的区别:
- 解决问题的角度:贪心算法是从前往后,每步都采取局部最优选择,希望通过每一步的局部最优得到全局最优;而动态规划则是基于子问题的最优解,通过已知的子问题的最优解推导出全局最优解。
- 是否保存子问题的解:动态规划需要保存子问题的解以避免重复计算,而贪心算法则不需要。
- 应用范围:贪心策略对某些问题能得到整体最优解,但对某些问题不能。动态规划则可以保证得到全局最优解,但可能需要更多的计算和存储空间。
虽然贪心算法不能保证总是得到整体最优解,但由于其高效性,它在很多情况下仍然是一个非常有用的方法。当然,确定一个问题是否适合使用贪心策略是非常关键的。