基础数据结构
这里的python全是python3的
力求快、准、狠、短
数据结构
#单链表
----c++版
数组模拟链表
https://www.acwing.com/problem/content/828/
#include<iostream>
using namespace std;
const int N=1e5+10;
//* head 表示头节点的下标
//* e[i] 表示节点 i 的值
//* ne[i] 表示节点 i 的 next 指针是多少
//* idx 存储当前已经用到了哪个节点,节点下标/编号
//* -1<--@<--@<--@<--@<--@<--head
int head,e[N],ne[N],idx;
//* 初始化
void init(){
head=-1;
idx=0;
}
//* 插入到头节点操作
void add_to_head(int x){
e[idx]=x;
ne[idx]=head;
head=idx;
idx++;
}
//* 插入到下标是k的点后面
void add(int k,int x){
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx;
idx++;
}
//* 将位置k的后一个节点删掉
void remove(int k){
ne[k]=ne[ne[k]];
}
int main(void){
ios::sync_with_stdio(false);//加速输入
int m;cin>>m;
init();
while(m--){
int x,k;
char op;
cin>>op;
if(op=='H'){
cin>>x;
add_to_head(x);
}
else if(op=='D'){
cin>>k;
if(k==0)head=ne[head];
else remove(k-1);
}
else{
cin>>k>>x;
add(k-1,x);
}
}
for(int i=head;i!=-1;i=ne[i])cout<<e[i]<<" ";
cout<<endl;
return 0;
}
#双链表
----c++版
https://www.acwing.com/problem/content/829/
#include<iostream>
using namespace std;
const int N=1e5+10;
int m;
int e[N],l[N],r[N];
int idx;
//初始化
void init(){
l[1]=0;r[0]=1;//0和1只作为表的头尾,本身并不放值,0最左,1最右
idx=2;//已经用掉两个点了
}
//在第k个点右边插入一个x
void add(int k,int x){
e[idx]=x;
l[idx]=k;
r[idx]=r[k];
l[r[k]]=idx;
r[k]=idx;
idx++;
}
//删除第k个点
void remove(int k){
r[l[k]]=r[k];
l[r[k]]=l[k];
}
int main(){
ios::sync_with_stdio(false);
cin>>m;
init();
while(m--){
string op;
cin>>op;
int k,x;
if(op=="R"){
cin>>x;
add(l[1],x);
}else if(op=="L"){
cin>>x;
add(0,x);
}else if(op=="D"){
cin>>k;
remove(k+1);
}else if(op=="IL"){
cin>>k>>x;
add(l[k+1],x);
}else{
cin>>k>>x;
add(k+1,x);
}
}
for(int i=r[0];i!=1;i=r[i])cout<<e[i]<<" ";
return 0;
}
#栈
----c++版
https://www.acwing.com/problem/content/830/
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e6+10;
int stack[N],top;
int main(){
int m;cin>>m;
while(m--){
string op;cin>>op;
int x;
if(op=="push"){
cin>>x;top++;
stack[top]=x;
}else if(op=="pop")top--;
else if(op=="empty")cout<<(top?"NO":"YES")<<endl;
else cout<<stack[top]<<endl;
}
return 0;
}
#队列
----c++版
https://www.acwing.com/problem/content/831/
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e6+10;
int q[N];
int main(){
int m;cin>>m;
int i=1,j=1;//双指针算法
while(m--){
string op;cin>>op;
if(op=="push"){
int x;scanf("%d",&x);
q[j++]=x;
}else if(op=="pop"){
q[i++];
}else if(op=="query"){
printf("%d\n",q[i]);
}else if(op=="empty"){
j-i==0?printf("YES\n"):printf("NO\n");
}
}
return 0;
}
#单调栈
----c++版
先拿栈顶的数比较,不行了就出栈,换下一个比
https://www.acwing.com/problem/content/832/
#include<iostream>
using namespace std;
const int N=1e5+10;
int stack[N],tt;
//tt是单调栈顶数的下标,stack的每一项存的是当前数左边第一个比其小的数
//过滤掉了其他大数,维护了最紧凑、最连续的单调性,所以新加入的数不需要看其他大数,只需要看单调栈里的数就行
int main(){
ios::sync_with_stdio(false);
int n;cin>>n;
for(int i=1;i<=n;i++){
int x;cin>>x;
while(tt&&stack[tt]>=x)tt--;
if(tt)cout<<stack[tt]<<" ";
else cout<<"-1 ";
stack[++tt]=x;//最新的数必须先入栈,因为必须与之后的数比较
}return 0;
}
#单调队列
----c++版
单调队列给我的感觉是,你必须从队列的头找起,而且头的数可能被舍弃
https://www.acwing.com/problem/content/156/
#include<iostream>
using namespace std;
const int N=1e6+10;
int a[N],q[N];//每滑动一个窗口所得到的最小值的下标存在队列q里
int main(){
//ios::sync_with_stdio(false);不能加这句话
int n,k;cin>>n>>k;
for(int i=0;i<n;i++)scanf("%d",&a[i]);
int hh=0,tt=-1;//hh当前单调队列中最小值的下标,
for(int i=0;i<n;i++){
if(hh<=tt&&i-k+1>q[hh])hh++;//若当点队列最小值所存的下标不在窗口中,去找下一个
//维护单调性
while(hh<=tt&&a[q[tt]]>a[i])tt--;//找当前还在队列中的最小值中第一个比a[i]小的值的下标
q[++tt]=i;//下标加一的位置即可放i
if(i>=k-1)printf("%d ",a[q[hh]]);//有窗口时再输出
}
puts("");
hh=0;tt=-1;
for(int i=0;i<n;i++){
if(hh<=tt&&i-k+1>q[hh])hh++;
while(hh<=tt&&a[q[tt]]<a[i])tt--;
q[++tt]=i;
if(i>=k-1)printf("%d ",a[q[hh]]);
}
return 0;
}
#KMP算法
kmp可以快速找到子串
----c++版
https://www.acwing.com/problem/content/description/833/
#include<iostream>
using namespace std;
const int N=100100,M=1000010;
int n,m;
char p[N],s[M];
int ne[N];
int main(){
cin>>n>>p+1>>m>>s+1;
//要从第一位开始给字符串赋值,这样输出比较方便
//生成next数组,next数组中每一位数表示,包括当前下标,从当前下标开始往前计,有几位字符与该单词从开头数的字符相同
for(int i=2,j=0;i<=n;i++){
while(j&&p[i]!=p[j+1])j=ne[j];
if(p[i]==p[j+1])j++;
ne[i]=j;
}
//for(int i=1;i<=n;i++)cout<<ne[i];
//next数组的第一位永远都是0,如果实在没有匹配了,咱还是从头开始吧
for(int i=1,j=0;i<=m;i++){
while(j&&s[i]!=p[j+1])j=ne[j];
if(s[i]==p[j+1])j++;
if(j==n){
printf("%d ",i-n);
j=ne[j];
}
}
return 0;
}
----python版
当然python有re库和正则表达式
#Trie字典树
----c++版
o(n)
https://www.acwing.com/problem/content/837/
#include<iostream>
using namespace std;
const int N=200010;
int son[N][26],cnt[N],idx;
char str[N];
bool st[N];
int n;
void insert(char str[]){
int p=0;
for(int i=0;str[i];i++){
int u=str[i]-'a';
if(!son[p][u])son[p][u]=++idx;
p=son[p][u];
}
cnt[p]++;//标记一个单词的结尾
}
int query(char str[]){
int p=0;
for(int i=0;str[i];i++){
int u=str[i]-'a';
if(!son[p][u])return 0;
p=son[p][u];
}
return cnt[p];
}
int main(){
cin>>n;
while(n--){
string op;
cin>>op>>str;
if(op=="I")insert(str);
else cout<<query(str)<<endl;
}
return 0;
}
#并查集
----c++版
https://www.acwing.com/problem/content/838/
#include<iostream>
using namespace std;
const int N=1e5+10;
int f[N];
int find(int x){//寻找根节点
if(f[x]!=x)f[x]=find(f[x]);//如果不是根节点
return f[x];
}//真正的核心操作只有find
int main(){
int n,m;
cin.tie(0);
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++)f[i]=i;//初始化
while(m--){
char op;cin>>op;
int a,b;cin>>a>>b;
if(op=='M'){
if(find(a)!=find(b))f[find(a)]=find(b);//包含了路径压缩
}else{
if(find(a)!=find(b))cout<<"No"<<endl;
else cout<<"Yes"<<endl;
}
}
return 0;
}
----并查集习题增补
https://blog.csdn.net/lafea/article/details/107243946
----高级数据结构:并查集
https://blog.csdn.net/lafea/article/details/113354366
#树状数组
----高级数据机构:树状数组
https://blog.csdn.net/lafea/article/details/113401186
#线段树
----高级数据机构:线段树
https://blog.csdn.net/lafea/article/details/114866738
#堆
----c++版
https://www.acwing.com/problem/content/840/
#include<iostream>//小根堆
#include<algorithm>
using namespace std;
const int N=100010;
int h[N],sizee;
int n,m;
void down(int u){
int t=u;
if(2*u<=sizee&&h[t]>h[2*u])t=2*u;
if(2*u+1<=sizee&&h[t]>h[2*u+1])t=2*u+1;//选出左右节点中最小的那个
if(u!=t){
swap(h[u],h[t]);
down(t);
}
}
int main(){
cin>>n>>m;
sizee=n;
for(int i=1;i<=n;i++)scanf("%d",&h[i]);
for(int i=n/2;i;i--)down(i);
while(m--){
cout<<h[1]<<" ";
h[1]=h[sizee--];
down(1);
}
return 0;
}
模拟堆
https://www.acwing.com/problem/content/description/841/
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int h[N];
int ph[N]; //存放第k个插入点的下标
int hp[N];//存放堆中点的插入次序
int sizee;//size 记录的是堆当前的数据多少
//这个交换过程其实有那么些绕 但关键是理解 如果hp[u]=k 则ph[k]=u 的映射关系
//之所以要进行这样的操作是因为 经过一系列操作 堆中的元素并不会保持原有的插入顺序
//从而我们需要对应到原先第K个堆中元素
//如果理解这个原理 那么就能明白其实三步交换的顺序是可以互换
//h,hp,ph之间两两存在映射关系 所以交换顺序的不同对结果并不会产生影响
void heap_swap(int u,int v){
swap(h[u],h[v]);
swap(hp[u],hp[v]);
swap(ph[hp[u]],ph[hp[v]]);
}
void down(int u){
int t=u;
if(u*2<=sizee&&h[t]>h[u*2])t=u*2;
if(u*2+1<=sizee&&h[t]>h[u*2+1])t=u*2+1;
if(u!=t){
heap_swap(u,t);
down(t);
}
}
void up(int u){
if(u/2>0&&h[u]<h[u/2]){
heap_swap(u,u/2);
up(u>>1);
}
}
int main(){
int n,m=0;cin>>n;
while(n--){
string op;cin>>op;
int k,x;
if(op=="I"){
cin>>x;
m++;
h[++sizee]=x;
ph[m]=sizee;
hp[sizee]=m;
up(sizee);
}
else if(op=="PM")cout<<h[1]<<endl;
else if(op=="DM"){
heap_swap(1,sizee);
sizee--;
down(1);
}else if(op=="D"){
cin>>k;
int u=ph[k];
heap_swap(u,sizee);
sizee--;
up(u);
down(u);
}else if(op=="C"){
cin>>k>>x;
h[ph[k]]=x;
down(ph[k]);
up(ph[k]);
}
}
return 0;
}
#哈希表
哈希表一般有 1.开放寻址法,2.拉链法 来实现
##模拟散列表
https://www.acwing.com/problem/content/842/
----c++版
###拉链法
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+3;
int h[N],e[N],ne[N],idx;
void insert(int x){
//把数映射到小范围内,最坏有N条链
int k=(x%N+N)%N; //一般模上一个质数,且与2的整次幂尽可能远,加N再模是为了防止负数
e[idx]=x;//加链子扣
ne[idx]=h[k];//头插入
h[k]=idx++;
}
bool find(int x){
int k=(x%N+N)%N;
for(int i=h[k];i!=-1;i=ne[i])
if(e[i]==x)
return true;
return false;
}
int main(){
int n;scanf("%d",&n);
memset(h,-1,sizeof h);
while(n--){
char op[2];
int x;scanf("%s%d",op,&x);
if(*op=='I')insert(x);
else{
if(find(x))puts("Yes");
else puts("No");
}
}
return 0;
}
###开放寻址法
只开一维数组,一般是题目数据量的2-3倍
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200003,null=0x3f3f3f3f;//神奇的数字,可以视为无穷大
int h[N];
int find(int x){
int k=(x%N+N)%N;//找位置
while(h[k]!=null&&h[k]!=x){//有空位了,或者已经有了 就结束
k++;
if(k==N)k=0;//不然从这个位置开始往下一个位置找
}
return k;
}
int main(){
int n;scanf("%d", &n);
memset(h, 0x3f,sizeof h);
while(n--){
char op[2];int x;
scanf("%s%d",op,&x);
int k=find(x);
if(*op=='I')h[k]=x;
else{
if(h[k]!=null)puts("Yes");
else puts("No");
}
}
return 0;
}
###关于memset
memset是按照字节赋值的,所以如果给2的话,每个数组元素的每一个字节都会被赋值2,然后累加起来,数组元素不会得到2,而是很大的数
所以memset可以对得上的数只有1,0,-1 或者0x3f(无穷大)
##字符串哈希方式
https://www.acwing.com/problem/content/843/
前缀哈希法
将字符串中的每一位看作某( p )进制数的数字
可以百度某进制数的公式
为了限制p进制数的大小,我们常常把该数mod 某个数Q
但这样可能有冲突
所以前提是人品足够好,且不能映射成0
研究表明,当p为131或者13331时,Q为 2 64 2^{64} 264时在99.99%的情况下不会有冲突
----c++版
技巧:用unsigned long long 存数就不用取模了,溢出相当于取模
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef unsigned long long ULL;
const int N=1e6+10,P=131;
int n,m;
char str[N];
ULL h[N],p[N];
ULL get(int l,int r){
return h[r]-h[l-1]*p[r-l+1];
}
int main(){
scanf("%d%d%s",&n,&m,str+1);//不能映射到0上
p[0]=1;
for(int i=1;i<=n;i++){
p[i]=p[i-1]*P;//先把进制的幂次算好
h[i]=h[i-1]*P+str[i];//把字符串按前缀哈希好
}
while(m--){
int l1,l2,r1,r2;
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
if(get(l1,r1)==get(l2,r2))puts("Yes");
else puts("No");
}
return 0;
}