第一周算法复习
快排
虽然在c++中有内置的sort函数帮助我们进行时间复杂度为nlogn的快速排序,不过快排的思想和双指针算法还是很值得我们学习的
快排的思想:
-
选一个分界点,可以是q[]的任何一个数,q[l],q[r],q[l+r>>1]都可以,一般是用q[l+r>>1]
-
以分界点的值为界,把所有小于等于分界点的数都移动到分界点左边,把所有大于等于分界点的数都移动到分界点右边
-
不断递归
很显然,快排的关键就在于我们怎么以分界点为界,移动值到分界点的两边。
-
开另外两个数组a[],b[],把所有小于等于分界点的数都输入到a[]中,大于等于分界点的数都输入到b[]中,然后再赋值给q[],这样虽然要多扫描一遍q[]和额外开了两个数组,不过无伤大雅
-
利用双指针算法能够进行优雅地处理,首先将两个指针放在q[]的最左端和最右端,因为每次都要先移动指针再判断情况,当左侧指针遇到一个不小于分界点的数那么就停止,当右侧指针遇到一个不大于分界点的数,也停止,两个指针都停止之后,交换值继续往前走
板子:
void quick_sort(int q[],int l,int r){
if (l>=r) return ;
int i=l-1,j=r+1,x=q[l+r>>1];
while(i<j){
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if (i<j) swap(q[i],q[j]);
}
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
sort
用法:
const int N=1e6+10;
int q[N];
sort(q,q+N);
//将整个q数组按从小到大排列
归并
归并的思想:
-
也是分支和双指针
-
时间复杂度也为nlogn
-
它先进行递归,再进行处理
-
这是一个稳定排序
板子:
void merge_sort(int q[],int l,int r){
if(l>=r) return ;
int mid=r+l>>1;
int k=0,i=l.j=mid+1;
merge_sort(q,l,mid);
merge_sort(q,j,r);
while(i<=mid&&j<=r)
if (q[i]<q[j]) tmp[k++]=q[i++];
else tmp[k++]=q[j++];
while(i<=mid) tmp[k++]=q[i++];
while(j<=r) tmp[k++]=q[j++];
for(int i=0,j=1;j<=r;j++,i++) q[j]=tmp[i];
}
scanf和cin
-
当有很多数需要读入的时候,最好使用scanf而不是cin
-
scanf的速度比cin快十几倍甚至几十倍
-
如果是string类型,那么只能通过cin读入
二分查找
二分查找也称折半查找,它是一种效率较高的查找方式,但是二分查找要求线性表是采用顺序结构的。一般是一个有单调性的数组,但也不是绝对的。一般来说,有单调性的题目一定可以使用二分,没有单调性的题目也可能可以使用二分。
思想:一个有单调性的题目如果要满足某个条件,那么这个边界肯定是可以被二分出来的。
举一个简单的栗子:如果一个升序的线性表,我们要找到某个数的左边界,if(q[mid]>=x) 满足这个条件,且我们要找到的时候左边界,那么就把r=mid l=mid+1,直至找到。
整数二分板子:
bool check(int x)
int bsearch_1(int l,int r){
while(l<r) {
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
return l;
}
int bsearch_2(int l,int r){
while(l<r){
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
return l;
}
浮点数二分板子
bool check(double x)
double bsearch_3(double l,double r)
{
const double eps=1e-6
while(r-l>eps)
{
double mid=(l+r).2;
if(check(mid)) r=mid;
else l=mid;
}
return l;
}
关于精确位数
在处理浮点数的时候,我们通常计算到题目要求的精度的后两位,比如题目要求的答案是六位小数,那么我们计算的时候通常用的是八位小数。
高精度
关于高精度的说明
两个大数或者一个大数和一个小数的运算
大数是指几千位长度的数字,这种数明显是爆了longlong的,所以我们输入的时候应该使用string类型,存储的时候用vector
加
vector <int> Add(vector<int> &A,vector<int> &B){
vector<int> C;
int t=0;
for(int i=0;i<A.size()||i<B.size();i++){
if(i<A.size()) t+=A[i];
if (i<B.size()) t+=B[i];
C.push_back(t%10);
t/=10;
}
if (t) C.push_back(t);
return C;
}
减
先要比较a,b的大小,如果a<b,那么a-b=-(b-a)
vector <int> sub(vector<int> A,vector<int> B){
vector<int> C;
for(int i=0,t=0;i<A.size();i++){
t=A[i]-t;
if(i<B.size()) t-=B[i];
C.push_back((t+10)%10);
if(t<0) t=1;
else t=0;
}
}
乘
大数乘小数
vector <int> mul(vector<int> A,int b){
vector <int> C;
int t=0;
for(int i=0;i<A.size();i++){
t+=A[i]*b;
C.push_back(t%10);
t/=10;
}
while(t)
{
C.push_back(t%10);
t/=10;
}
}
除
前面几种运算都是从低位进行运算,直到高位,而除法显然是从高位开始运算的,所以运算出的vector需要倒序
vector<int> div(vector<int> A,int b,int &c){
vector<int> C;
c=0;
for(int i=A.size()-1;i>=0;i--){
c=c*10+A[i];
C.push_back(c/b);
c%=b;
}
reverse(C.begin(),C.end());
}
引用传递
int func(int &a);
int main(){
int a;
func(a);
} //直接对a进行修改,而不是对a的副本进行修改
//引用传递和值传递的区别:
引用传递是直接对传入的变量或者数组等容器进行修改的,传递的过程中并不会拷贝一个副本,这样当我们传长度为几十万的数组或者更大的数据。就不会浪费空间。
vector(动态数组)
定义:长度可变的数组
常用操作:
vector <typename> xxx ; //定义一个vector typename可以是简单的int 类型 也可以是数组之类的容器
几个常用的内置函数
vector.begin() //返回vector头地址
vector.end() //返回vector尾地址的后面一位
因为美国人的思维大多是左闭右开
vector.push_back(xxx) //末尾加入元素
vector.pop_back(xxx) //末尾删除元素
vector.insert(vector.begin()+3,xxx) //下表为3位置插入元素
vector.earse(vector.begin()+3) //下表为3位置删除元素
vector.size() //vector长度
vector.clear() //清除所有元素
//访问vector元素有两种方法
1.下标访问
vi[0] //和array一个用法
2.迭代器
for(vector<int>::iterator it=vi.begin();it!=vi.end();it++){
printf("%d",*it);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现