一、 快速排序(QuickSort)
算法思想:
由冒泡排序改进,在冒泡排序过程中,只对相邻的两个记录进行比较,每次交换只能消除一个逆序;
而快速排序时对不一定相邻的两个记录进行比较,每次交换可以消除多个逆序。
快速排序是不稳定的。
时间复杂度:最差: O ( n 2 ) O(n^2) O(n2);平均: O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
- 如果
left>=right
,则退出; - 在数组中随即确定一个分界点x,假设
x=(left+right)/2
; - 设指针i,j,分别指向left,right;
- 当
i<j
时,循环执行5,6,7步; - 如果i指针所指向的数小于x,则
i++
;否则,跳出循环; - 如果j指针所指向的数大于x,则
j--
;否则,跳出循环; - 如果
i<j
,则swap(arr[i],arr[j])
; - 调整区间,递归处理左右两段,
quick_sort(arr,left,i);quick_sort(arr,i+1,right);
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
const int N = 1e6+10;
int n;
int q[N];
void quick_sort(int q[],int l,int r)
{
if(l >= r) return;
int x = q[l],i = l-1,j = r+1;
while(i < j)
{
//如果12 12不先进行++,--操作的话,会造成死循环
do i++; while(q[i] < x);
do j--; while(q[j] > x);
if(i < j) swap(q[i],q[j]);
}
//会一直存在(0,i)区间,死循环
quick_sort(q,l,j);
quick_sort(q,j+1,r);
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&q[i]);
quick_sort(q,0,n-1);
for(int i=0;i<n;i++)
printf("%d ",q[i]);
return 0;
}
二、 归并排序(MergeSort)
分治法思想
归并排序是稳定的。
时间复杂度: O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
算法思想:
- 取数组的中间值作为分界点mid;
- 将mid左边的序列,右边的序列分别排好序;
- 对左右序列进行合并。
代码:
#include <iostream>
#include <stdio.h>
using namespace std;
const int N = 1e6+10;
int q[N],tmp[N];
int n;
void merge_sort(int q[],int l,int r)
{
if(l>=r)
return;
int mid = l+r >> 1;
merge_sort(q,l,mid);
merge_sort(q,mid+1,r);
int k=0,i=l,j=mid+1;
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 <= mid)
tmp[k++] = q[j++];
for(i=l,j=0;i<=r;i++,j++)
q[i] = tmp[j];
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&q[i]);
merge_sort(q,0,n-1);
for(int i=0;i<n;i++)
printf("%d ",q[i]);
return 0;
}
三、 二分查找(BinarySearch)
算法思想:
- 首先对数组进行排序。
- 将要查找值x与数组中间值mid相比较;
- 如果
x=mid
,则找到x,退出程序; - 如果
x<mid
,则将边界设为[left,mid]; - 如果
x>mid
,则将边界设为[mid+1,right]; - 循环执行4,5两步骤,若直到数组长度为1,还没找打x,则数组中不存在x。
代码:
//整数二分查找
#include <iostream>
#include <stdio.h>
using namespace std;
const int N = 1e6+10;
int q[N];
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++)
scanf("%d",&q[i]);
while(m--)
{
int x;
scanf("%d",&x);
int l=0,r=n-1;
//取第一个等于x的数,右边界等于mid
while(l<r)
{
int mid = l+r >> 1;
if(x <= q[mid])
r = mid;
else
l = mid + 1;
}
if(q[l] != x)
cout << "-1 -1" << endl;
else
{
cout << l << " ";
int l=0,r=n-1;
//取最后一个等于x的数,左边界等于mid
while(l<r)
{
int mid = l+r+1 >> 1;
if(x >= q[mid])
l = mid;
else
r = mid -1;
}
cout << l << endl;
}
}
return 0;
}
//浮点数二分查找
#include <iostream>
#include <stdio.h>
using namespace std;
int main()
{
double x;
scanf("%lf",&x);
double l = 0,r = x;
while(r-l > 1e-8)
{
double mid = (l+r)/2;
if(mid*mid >= x)
r = mid;
else
l = mid;
}
printf("%lf\n",l);
return 0;
}
四、 高精度算法
存储: 数组存储,低位数存在低位。
算法思想:
- 加减法:
- 用字符串读入两个大整数a,b;
- 用
vector<int>
分别倒序存储a,b,得到A,B,设和数向量C; - 如果是减法则执行步骤6;
- 设中间变量t来存储进位,t分别加A[i],B[i],将
t%10
push到C,令t/=10
; - 循环执行4,直到i大于A,B两向量的长度。
- 首先根据A,B的长度,高位的值比较大小;
- 设中间变量t来存储进位,A[i]-B[i]-t,将
(t+10)%10
push到C,若t<0
,则令t=1
,否则令t=0
;
- 乘除法:
- 用字符串读入大整数a,整型读入整数b;
- 用
vector<int>
倒序存储a,得到A,设向量C; - 如果是除法则执行步骤6;
- 设中间变量t来存储进位,t加
A[i]*b
,将t%10
push到C,令t/=10
; - 循环执行4,直到i大于A的长度。
- 设余数r,
r=r*10+A[i]
,将r/b
push到C,令r%=b
; - 将C中的值反转,去除前置0。
代码:
加法,减法:
#include <iostream>
#include <vector>
using namespace std;
//大整数加法
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(1);
return C;
}
//大整数减法
bool cmp(vector<int> &A,vector<int> &B)
{
if(A.size() != B.size())
return A.size()>B.size();
else
{
for(int i=A.size()-1;i>=0;i--)
if(A[i] != B[i])
return A[i] > B[i];
return true;
}
}
vector<int> sub(vector<int> &A,vector<int> &B)
{
vector<int> C ;
int t = 0;
for(int i=0;i<A.size();i++)
{
if(B.size()<i)
t = A[i]-t;
else
t = A[i] - B[i] - t;
C.push_back((t+10)%10);
if(t<0)
t = 1;
else
t = 0;
}
//去掉前导0
while(C.size()>1 && C.back()==0)
C.pop_back();
return C;
}
int main()
{
string a,b;
vector<int> A,B;
cin >> a >> b;
for(int i=a.size()-1;i>=0;i--)
A.push_back(a[i]-'0');
for(int i=b.size()-1;i>=0;i--)
B.push_back(b[i]-'0');
vector<int> C = add(A,B);
for(int i=C.size()-1;i>=0;i--)
cout << C[i];
cout << endl;
if(cmp(A,B))
C = sub(A,B);
else
{
C = sub(A,B);
cout << "-";
}
for(int i=C.size()-1;i>=0;i--)
cout << C[i];
return 0;
}
乘法、除法:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
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;
}
if(t)
C.push_back(t);
return C;
}
vector<int> div(vector<int> &A,int b,int &r)
{
vector<int> C;
r = 0;
for(int i=A.size()-1;i>=0;i--)
{
r = r*10+A[i];
C.push_back(r/b);
r %= b;
}
reverse(C.begin(),C.end());
while(C.size()>1 && C.back()==0)
C.pop_back();
return C;
}
int main()
{
string a;
int b;
vector<int> A;
cin >> a >> b;
for(int i=a.size()-1;i>=0;i--)
A.push_back(a[i]-'0');
vector<int> C = mul(A,b);
for(int i=C.size()-1;i>=0;i--)
cout << C[i];
cout << endl;
int r;
C = div(A,b,r);
for(int i=C.size()-1;i>=0;i--)
cout << C[i];
cout << " " << r;
return 0;
}
五、 一维前缀和
算法思想:
- 前缀和数组,原数组的下标均由1开始。
- 求前缀和:将S[0]置0,
S[i]=S[i-1]+a[i]
。 - 区间[l,r]的数组的和等于
S[r]-S[l-1]
。
代码:
#include <iostream>
using namespace std;
const int N = 100010;
int a[N],s[N];
int m,n;
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++)
cin >> a[i];
for(int i=1;i<=n;i++)
s[i] = s[i-1] + a[i];
while(m--)
{
int l,r;
cin >> l >> r;
cout << s[r] - s[l-1] << endl;
}
return 0;
}
六、 子矩阵前缀和
算法思想:
- 先求前缀和:
- 求子矩阵的和:
代码:
#include <iostream>
using namespace std;
const int N = 1010;
int a[N][N],s[N][N];
int m,n,q;
int main()
{
cin >> n >> m >> q;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin >> a[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
while(q--)
{
int x1,x2,y1,y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << s[x2][y2] - s[x1-1][y2] - s[x2][y1-1] + s[x1-1][y1-1]<< endl;
}
return 0;
}
七、 差分
若a[n]是b[n]的前缀和,则b[n]是a[n]的差分。
使区间[l,r]中的每一个a[i]+c,则只需b[l]+c
,b[r+1]-c
。
算法思想:
- 假设a数组初始值全为0,则b数组的值也全为0。
- 将a[i]的值插入a数组,则可以理解为在区间[i,i]中a[i]+c,那么需对
b[i]+a[i],b[i+1]-a[i]
。 - 最终对b[i]求前缀和即可。
代码:
#include <iostream>
using namespace std;
const int N = 100010;
int a[N],b[N];
int m,n;
void inserta(int l,int r, int c)
{
b[l] += c;
b[r+1] -= c;
}
int main()
{
cin >> n >> m;
for(int i=1;i<=n;i++)
cin >> a[i];
for(int i=1;i<=n;i++)
inserta(i,i,a[i]);
while(m--)
{
int l,r,c;
cin >> l >> r >> c;
inserta(l,r,c);
}
for(int i=1;i<=n;i++)
b[i] += b[i-1];
for(int i=1;i<=n;i++)
cout << b[i] << " ";
return 0;
}
八、 差分矩阵
算法思想:
同上思想相同。
代码:
#include <iostream>
using namespace std;
const int N = 1010;
int a[N][N],b[N][N];
int m,n,q;
void inserta(int x1,int y1,int x2,int y2,int c)
{
b[x1][y1] += c;
b[x2+1][y1] -= c;
b[x1][y2+1] -= c;
b[x2+1][y2+1] += c;
}
int main()
{
cin >> n >> m >> q;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
cin >> a[i][j];
inserta(i,j,i,j,a[i][j]);
}
while(q--)
{
int x1,x2,y1,y2,c;
cin >> x1 >> y1 >> x2 >> y2 >> c;
inserta(x1,y1,x2,y2,c);
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
b[i][j] += b[i-1][j] + b[i][j-1] - b[i-1][j-1];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
cout << b[i][j] << " ";
cout << endl;
}
return 0;
}
九、 双指针算法
算法思想:
- 设一个计数数组S[n],记录在[i,j]区间内数每个数出现的次数。
- i每向右移一位,S[a[i]]++;j向右移之前,先S[a[j]]–。
- 记录不重复子数组的最长长度。
代码:
#include <iostream>
using namespace std;
const int N = 100010;
int a[N],s[N];
int main()
{
int n;
cin >> n;
for(int i=0;i<n;i++)
cin >> a[i];
int res = 0;
for(int i=0,j=0;i<n;i++)
{
s[a[i]]++;
while(s[a[i]] > 1)
{
s[a[j]]--;
j++;
}
res = max(res,i-j+1);
}
cout << res << endl;
return 0;
}
十、 位运算
算法思想:
- 求n的二进制表示中第k+1位是几,n>>k&1。
- 返回x的最后一位1是多少,x&-x,x=1010,lowbit(x)=2。(求x的二进制中多少个1)
代码:
#include <iostream>
using namespace std;
int lowbit(int x)
{
return x&-x;
}
int main()
{
int n;
cin >> n;
while(n--)
{
int x;
cin >> x;
int res = 0;
while(x)
{
x -= lowbit(x);
res++;
}
cout << res << " ";
}
return 0;
}
十一、 离散化
算法思想:
- 用到前缀和求区间内值的和。
- 去重:先将数组进行排序,利用unique,erase去重。
- 二分查找。
代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int,int> p;
const int N = 300010;
int n,m;
int a[N],s[N];
vector<int> alls;
vector<p> query,add;
int findf(int x)
{
int l=0,r=alls.size()-1;
while(l < r)
{
int mid = l+r>>1;
if(alls[mid]>=x)
r = mid;
else
l = mid + 1;
}
return r+1;
}
int main()
{
cin >> n >> m;
for(int i=0;i<n;i++)
{
int x,c;
cin >> x >> c;
add.push_back({x,c});
alls.push_back(x);
}
for(int i=0;i<m;i++)
{
int l,r;
cin >> l >> r;
query.push_back({l,r});
alls.push_back(l);
alls.push_back(r);
}
//去重
sort(alls.begin(),alls.end());
alls.erase(unique(alls.begin(),alls.end()),alls.end());
//处理插入
for(int i=0;i<add.size();i++)
{
int x = findf(add[i].first);
a[x] += add[i].second;
}
//预处理前缀和
for(int i=1;i<=alls.size();i++)
s[i] = s[i-1] + a[i];
//处理询问
for(int i=0;i<query.size();i++)
{
int l = findf(query[i].first);
int r = findf(query[i].second);
cout << s[r] - s[l-1] << endl;
}
return 0;
}
十二、 区间合并
算法思想:
- 按照区间左端点排序。
- 初始化左st右ed边界为-2e9。
- 比较ed与当前所在区间左端点,更新区间,并将更新后的区间记录下来。
- 返回数组长度。
代码:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef pair<int,int> p;
const int N = 100010;
int n;
vector<p> seg;
void mergea(vector<p> &seg)
{
vector<p> res;
sort(seg.begin(),seg.end());
int st = -2e9, ed = -2e9;
for(int i=0;i<seg.size();i++)
{
if(ed < seg[i].first)
{
if(st != -2e9)
res.push_back({st,ed});
st = seg[i].first;
ed = seg[i].second;
}
else
ed = max(ed,seg[i].second);
}
if(st != -2e9)
res.push_back({st,ed});
seg = res;
}
int main()
{
cin >> n;
for(int i=0;i<n;i++)
{
int l,r;
cin >> l >> r;
seg.push_back({l,r});
}
mergea(seg);
cout << seg.size() << endl;
return 0;
}