目录
字符串
字符串类型转换KMP排序
排名堆排序快速排序线性第K大归并排序判断插入排序二分
整数二分链表
单链表双链表栈和队列
单调栈单调队列树
反转二叉树构建二叉搜索树判断完全二叉树二叉树遍历重建二叉树AVL树插入判断红黑树DFS判断堆最低公共祖先并查集图
最短路径与扩展问题判断哈密顿回路欧拉路径顶点覆盖最大团拓扑排序染色问题数学
高精度分解质因子分数运算(和差积商)素数筛快速幂动态规划
背包问题最大子序列和最长公共子序列状态机模型正文
字符串
字符串类型转换0 1to_string()这个太常用了不多说了
int a = 4;
double b = 3.14;
string str1, str2;
str1 = to_string(a);
str2 = to_string(b);
cout <endl;
cout <endl;
0 2stoi()和atoi()
作用是将字符串转化为int型。区别是stoi的形参直接传入string类型即可,而atoi的形参是const char*
string s1("1234567");
char* s2 = "1234567";
int a = stoi(s1);
int b = atoi(s2);
cout <endl;
cout <endl;
进化版:stol(),stoll(),stoul(),stoull(),atol(),atoll(),atoul(),atoull()
l为long int,ll为long long,ul为unsigned long int,ull为unsigned long long
03string类的方法c_str()c_str()就是将C++的string转化为C的字符串数组,c_str()生成一个const char *指针,指向字符串的首地址。
char *p=s[10];
string a=“welcome”;
strcpy(p,a.c_str());
cout<
如果需要使用C语言printf的格式化输出string时可以使用,很方便,比如
string name="PAT";
printf("%s\n",name.c_str());
04string类的方法find()
在string中找到目标字符的位置,返回数组下标
string s = "hello world";
cout <"e") <endl;//结果为1
未找到会返回一个特殊的标志npos(一个很大的数字)
string s = "hello world";
if (s.find("a") == s.npos)
cout <"not found" <endl;
从指定位置开始查找
string s = "hello world";
//从下标为5的数组位置开始查找(也就是忽略前面的)
cout <"l",5) <endl;//结果为9
05string类的方法substr()
截取子串
单参数形式(给出截取开始的位置,末尾默认为原string末尾)
string s("12345abcd");
string a = s.substr(5);//获得字符串s中从下标为5开始一直到末尾的字符串
cout <endl;//结果为abcd
双参数形式(给出截取开始的位置,并给出截取长度)
string s("12345abcd");
string a = s.substr(0,5);//获得字符串s中从下标为0开始的长度为5的字符串
cout <endl;//结果为12345
KMP
给定一个模式串S,以及一个模板串P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串P在模式串S中多次作为子串出现。
求出模板串P在模式串S中所有出现的位置的起始下标。
输入格式
第一行输入整数N,表示字符串P的长度。
第二行输入字符串P。
第三行输入整数M,表示字符串S的长度。
第四行输入字符串S。
输出格式
共一行,输出所有出现位置的起始下标(下标从0开始计数),整数之间用空格隔开。
数据范围
1≤N≤10^5
1≤M≤10^6
输入样例
3
aba
5
ababa
输出样例
0 2
代码
#include
using namespace std;
const int N=1e6+10,M=1e5+10;
char t[N],p[M];
int n,m;
int* build(char *p){
int *ne=new int[m];
int i=0,j=-1;
ne[0]=-1;
while(i-1){
if(j<0||p[i]==p[j]){
i++,j++;
ne[i]=j;
}else j=ne[j];
}return ne;
}void match(char *t,char* p){
int *ne=build(p);int i=0,j=0;while(i while(j if(j<0||t[i]==p[j])
i++,j++;else j=ne[j];if(j==m){ printf("%d ",i-j);
i--;
j=ne[j-1];
}
}
}int main(){
scanf("%d%s%d%s",&m,p,&n,t);
match(t,p);
}
排序
PAT常考的排名方式(同分布排名)sort(a.begin(),a.end());
for(int i=0;i if(!i||a[i].grade!=a[i-1].grade)
a[i].rank=i+1;
else a[i].rank=a[i-1].rank;
}
堆排序
输入一个长度为n的整数数列,从小到大输出前m小的数。
输入格式
第一行包含整数n和m。
第二行包含n个整数,表示整数数列。
输出格式
共一行,包含m个整数,表示整数数列中前m小的数。
输入样例
5 3
4 5 1 3 2
输出样例
1 2 3
代码
#include
using namespace std;
const int maxn=1e5+10;
int h[maxn],size;
void down(int x){
int t=x;
if(2*x<=size&&h[2*x]2*x;if(2*x+1<=size&&h[2*x+1]2*x+1;if(t!=x){
swap(h[t],h[x]);
down(t);
}
}void up(int x){
while(x/2&&h[x]2]){
swap(h[x],h[x/2]);
x/=2;
}
}int main(){
int n,m;scanf("%d%d",&n,&m);
size=n;for(int i=1;i<=n;i++)scanf("%d",&h[i]);for(int i=n/2;i>0;i--)
down(i);while(m--){
printf("%d ",h[1]);
h[1]=h[size--];
down(1);
}return 0;
}
快速排序
这里默认为升序排序,降序只要对比较的符号进行调整就可以了
快速排序分为三个过程:
将数列划分为两部分(不是直接分,要求保证相对大小关系)
递归到两个子序列中分别进行快速排序
不用合并,因为此时数列已经完全有序
首先对于第一步来说我们需要先找到一个基准值(基准值用于将整个数组切分成小和大两部分)
然后使用两个指针和来进行操作,大概原理就是先将指向的位置,指向的位置,然后使用do while循环使i指针疯狂向前直到遇到第一个比基准值大的数字停下,接下来对j指针也同样操作使其停在第一个比基准值小的数值上,然后互换,的数字。一直操作到ij指针相遇,那么由于对一路上不符合条件的数字都进行了互换,所以i和j相遇的就是整个数组的切分点。
OK我们找到了切分点,接下来就是递归操作(l,j),(j+1,r)了
注意事项:当切分点取a[l]时,递归取(l,j),(j+1,r)。当切分点取a[r]时,递归取(l,i-1),(i,r)。当切分点其余数字时,递归二者皆可(避免出现死循环)
每个递归直到l>=r停止,因为一个点是不需要再“划分”的
#include
using namespace std;
const int maxn=1e5+10;
int a[maxn],n;
void quick_sort(int l,int r){
if(l>=r) return;
int x=a[l],i=l-1,j=r+1;
while(i do i++; while(a[i] do j--; while(a[j]>x);
if(i }
quick_sort(l,j);
quick_sort(j+1,r);
}
int main(){
scanf("%d",&n);
for(int i=0;iscanf("%d",&a[i]);
quick_sort(0,n-1);for(int i=0;iprintf("%d ",a[i]);return 0;
}
线性第K大(快排思想应用)
找第 k 大的数(K-th order statistic),最简单的方法是先排序,然后直接找到第 k 大的位置的元素。这样做的时间复杂度是O(nlogn),对于这个问题来说很不划算。事实上,我们有时间复杂度 的解法。
考虑快速排序的划分过程,在快速排序的“划分”结束后,我们会将数组分成两部分,我们可以通过检查k是否落在左边的范围内来减少复杂度,在排序的途中解决这个问题,当然这时候快排就不会全部排完了,而是找到了k就中途退出
#include
using namespace std;
const int maxn=1e5+10;
int a[maxn];
int quick_sort(int l,int r,int k){
if(l==r&&l==k) return a[k];
int x=a[l],i=l-1,j=r+1;
while(i do i++; while(a[i] do j--; while(a[j]>x);
if(i }
if(l<=k&&k<=j) quick_sort(l,j,k);
else quick_sort(j+1,r,k);
}
int main(){
int n,k;
scanf("%d%d",&n,&k);
for(int i=0;iscanf("%d",&a[i]);printf("%d\n",quick_sort(0,n-1,k-1));return 0;
}
归并排序
归并排序是一种采用了 分治 思想的排序算法,其本质是一种 CDQ 分治。
归并排序分为三个过程:
将数列随意划分为两部分(在均匀划分时时间复杂度为 O(nlogn))
递归地分别对两个子序列进行归并排序
合并两个子序列
不难发现,归并排序的核心是如何合并两个子序列,前两步都很好实现。
其实合并的时候也不难操作。注意到两个子序列在第二步中已经保证了都是有序的了,第三步中实际上是想要把两个 有序 的序列合并起来。
#include
using namespace std;
const int maxn=1e5+10;
int a[maxn],t[maxn];
void merge_sort(int l,int r){
if(l>=r) return;
int mid=l+r>>1;
merge_sort(l,mid);
merge_sort(mid+1,r);
int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r){
if(a[i]<=a[j]) t[k++]=a[i++];
else t[k++]=a[j++];
}
while(i<=mid) t[k++]=a[i++];
while(j<=r) t[k++]=a[j++];
for(int i=l,j=0;j a[i]=t[j];
}
int main(){
int n;
scanf("%d",&n);
for(int i=0;i scanf("%d",&a[i]);
merge_sort(0,n-1);
for(int i=0;i printf("%d ",a[i]);
printf("\n");
return 0;
}
判断插入排序
PAT出过两次的考点,判断一个排到一半的排序是否是插入排序还是别的排序(PAT会保证答案唯一)
一般可以使用这个方法,将数组分成两段,一段是排好序的(非降序),另一段是没有排好序的,我们找到没排序的那一段的起点,往下比对是否与原数组相同即可,不同就不是插入排序(因为插入排序是不会改变未排序的数组元素的顺序的)
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++) cin>>b[i];
int p=2;
while(p<=n&&b[p]>=b[p-1]) p++;
int k=p;
while(p<=n&&a[p]==b[p]) p++;
if(p==n+1){
puts("Insertion Sort");
while(k>1&&b[k]-1]) swap(b[k],b[k-1]),k--;
}
二分
整数二分二分搜索,也称折半搜索、二分查找,是用来在一个有序数组中查找某一元素的算法。
以在一个升序数组中查找一个数为例。
它每次考察数组当前部分的中间元素,如果中间元素刚好是要找的,就结束搜索过程;如果中间元素小于所查找的值,那么左侧的只会更小,不会有所查找的元素,只需要到右侧去找就好了;如果中间元素大于所查找的值,同理,右侧的只会更大而不会有所查找的元素,所以只需要到左侧去找。
在二分搜索过程中,每次都把查询的区间减半,因此对于一个长度为n的数组,至多会进行O(logn)次查找。
下面是第一种整数二分形式,用于找到>=x的区间的第一个数
bool check(int x) {
/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r){
while (l {
int mid = l + r >> 1;
if (check(mid)) r = mid; // check()判断mid是否满足性质
else l = mid + 1;
}
return l;
}
下面是第二种整数二分形式,用于找到<=x的最后一个数
bool check(int x) {
/* ... */} // 检查x是否满足某种性质
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r){
while (l {
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
链表
单链表#include
using namespace std;
const int maxn=1e5+10;
int e[maxn],ne[maxn],head,idx;
void init(){
head=-1;
idx=0;
}
void add_to_head(int x){
e[idx]=x;
ne[idx]=head;
head=idx++;
}
void add(int k,int x){
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx++;
}
void remove(int k){
ne[k]=ne[ne[k]];
}
int main(){
int n,k,x;
char c;
init();
cin>>n;
while(n--){
cin>>c;
if(c=='H'){
cin>>x;
add_to_head(x);
}
else if(c=='I'){
cin>>k>>x;
add(k-1,x);
}
else{
cin>>k;
if(!k) head=ne[head];
else remove(k-1);
}
}
for(int i=head;i!=-1;i=ne[i]){
if(i!=head) cout <' ';
cout < }
cout <endl;
return 0;
}
双链表
#include
using namespace std;
const int maxn=1e5+10;
int l[maxn],r[maxn],e[maxn],idx;
void init(){
r[0]=1;
l[1]=0;
idx=2;
}
void insertR(int k,int x){
e[idx]=x;
l[idx]=k;
r[idx]=r[k];
l[r[k]]=idx;
r[k]=idx++;
}
void insertL(int k,int x){
insertR(l[k],x);
}
void addL(int x){
insertR(0,x);
}
void addR(int x){
insertL(1,x);
}
void remove(int k){
r[l[k]]=r[k];
l[r[k]]=l[k];
}
int main(){
int n,k,x;
string s;
init();
cin>>n;
while(n--){
cin>>s;
if(s=="L"){
cin>>x;
addL(x);
}
else if(s=="R"){
cin>>x;
addR(x);
}
else if(s=="D"){
cin>>k;
remove(k+1);
}
else if(s=="IL"){
cin>>k>>x;
insertL(k+1,x);
}
else{
cin>>k>>x;
insertR(k+1,x);
}
}
for(