代码模板
单链表
/ head存储链表头,e[]存储节点的值,ne[]存储节点的next指针,idx表示当前用到了哪个节点
int head, e[N], ne[N], idx;
// 初始化
void init()
{
head = -1;
idx = 0;
}
// 在链表头插入一个数a
void insert(int a)
{
e[idx] = a, ne[idx] = head, head = idx ++ ;
}
// 将头结点删除,需要保证头结点存在
void remove()
{
head = ne[head];
}
双链表
// e[]表示节点的值,l[]表示节点的左指针,r[]表示节点的右指针,idx表示当前用到了哪个节点
int e[N], l[N], r[N], idx;
// 初始化
void init()
{
//0是左端点,1是右端点
r[0] = 1, l[1] = 0;
idx = 2;
}
// 在节点a的右边插入一个数x
void insert(int a, int x)
{
e[idx] = x;
l[idx] = a, r[idx] = r[a];
l[r[a]] = idx, r[a] = idx ++ ;
}
// 删除节点a
void remove(int a)
{
l[r[a]] = l[a];
r[l[a]] = r[a];
}
栈
// tt表示栈顶
int stk[N], tt = 0;
// 向栈顶插入一个数
stk[ ++ tt] = x;
// 从栈顶弹出一个数
tt -- ;
// 栈顶的值
stk[tt];
// 判断栈是否为空
if (tt > 0)
{
}
队列
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;
// 向队尾插入一个数
q[ ++ tt] = x;
// 从队头弹出一个数
hh ++ ;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh <= tt)
{
}
循环队列
// hh 表示队头,tt表示队尾的后一个位置
int q[N], hh = 0, tt = 0;
// 向队尾插入一个数
q[tt ++ ] = x;
if (tt == N) tt = 0;
// 从队头弹出一个数
hh ++ ;
if (hh == N) hh = 0;
// 队头的值
q[hh];
// 判断队列是否为空
if (hh != tt)
{
}
单调栈
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{
while (tt && check(stk[tt], i)) tt -- ;
stk[ ++ tt] = i;
}
单调队列
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口
while (hh <= tt && check(q[tt], i)) tt -- ;
q[ ++ tt] = i;
}
KMP
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
求模式串的Next数组:
for (int i = 2, j = 0; i <= m; 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, j = 0; i <= n; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++ ;
if (j == m)
{
j = ne[j];
// 匹配成功后的逻辑
}
}
Tire树
int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
// 插入一个字符串
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 findFather(int x) {
int a=x;
while(x!=father[x]) {
x=father[x];
}
while(a!=father[a]) {//路径压缩
int z=a;
a=father[a];
father[z]=x;
}
return x;
}
void Union(int a,int b) {//合并
int faA=findFather(a);
int faB=findFather(b);
if(faA!=faB) father[faA]=faB;
}
哈希
(1) 拉链法
int h[N], e[N], ne[N], idx;
// 向哈希表中插入一个数
void insert(int x)
{
int k = (x % N + N) % 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;
}
(2) 开放寻址法
int h[N];
// 如果x在哈希表中,返回x的下标;如果x不在哈希表中,返回x应该插入的位置
int find(int x)
{
int t = (x % N + N) % N;
while (h[t] != null && h[t] != x)
{
t ++ ;
if (t == N) t = 0;
}
return t;
}
字符串哈希
核心思想:将字符串看成P进制数,P的经验值是131或13331,取这两个值的冲突概率低
小技巧:取模的数用2^64,这样直接用unsigned long long存储,溢出的结果就是取模的结果
typedef unsigned long long ULL;
ULL h[N], p[N]; // h[k]存储字符串前k个字母的哈希值, p[k]存储 P^k mod 2^64
// 初始化
p[0] = 1;
for (int i = 1; i <= n; i ++ )
{
h[i] = h[i - 1] * P + str[i];
p[i] = p[i - 1] * P;
}
// 计算子串 str[l ~ r] 的哈希值
ULL get(int l, int r)
{
return h[r] - h[l - 1] * p[r - l + 1];
}
堆
//向下调整
void downAdjust(int low,int high) {
int i=low,j=i*2;
while(j <= high) {
if(j+1<=high && heap[j+1]>heap[j]) {
j=j+1;
}
if(heap[j]>heap[i]) {
swap(heap[j],heap[i]);
i=j;
j=i*2;
} else {
break;
}
}
}
//构建大顶堆
void createHeap() {
for(int i=n/2; i>=1; i--) {
downAdjust(i,n);
}
}
//向上调整
void upAdjust(int low,int high) {
int i=high,j=i/2;
while(j>=low) {
if(heap[j]<heap[i]) {
swap(heap[j],heap[i]);
i=j;
j=i/2;
} else {
break;
}
}
}
//向大顶堆中插入数据x
void insert(int x) {
heap[++n]=x;
upAdjust(1,n);
}
//堆排序
void heapSort() {
createHeap();
for(int i=n; i>1; i--) {
swap(heap[i],heap[1]);
downAdjust(1,i-1);
}
}
LCA
node* LCA(node *root,node *p,node *q) {
if(root==NULL) return NULL;
if(root->data==p->data||root->data==q->data) return root;
node *left=LCA(root->lchild,p,q);
node *right=LCA(root->rchild,p,q);
if(left!=NULL&&right!=NULL) return root;
return left==NULL?right:left;//p q 分别位于左右子树的情况
}
打表 埃式筛法
int prime[maxn],pNum=0;
bool p[maxn]= {0}
void Find_Prime() {
fot(int i=2; i<maxn; i++) {
if(p[i]==false) {
prime[pNum++]=i;
for(int j=i*i; j<maxn; j+=i) {
p[j]=true;
}
}
}
}
冒泡
void Bubble_sort(int s[],int len) {
bool flag;
for(int i=1; i<len-1; ++i) {
flag=false;
for(int j=1; j<=len-i; ++j)
if(s[j]>s[j+1]) {
flag=true;
swap(s[j],s[j+1]);
}
if(!flag)break;
}
}
选择
void Select_sort(int s[],int len) {
int k;
for(int i=1; i<len; ++i) {
k=i;
for(int j=i+1; j<=len; ++j)
if(s[k]>s[j])k=j;
if(k!=i)swap(s[i],s[k]);
}
}
快排
int Quick_sort(int s[],int low,int high) {
int tmp=s[low];//首元素是枢轴
while(low<high) { //待排序序列长度大于1
while(low<high&&tmp<=s[high])--high;//大于枢轴的元素依旧在右边
s[low]=s[high];//将小于枢轴的元素放左边
while(low<high&&tmp>=s[low])++low;//小于枢轴的元素依旧在左边
s[high]=s[low];//将大于枢轴的元素放右边
}
s[low]=tmp;//枢轴记录到位
return low;//返回枢轴的位置,表示该位置已经排好序
}
void Qsort(int s[],int low,int high) {
if(low<high) {
int key=Quick_sort(s,low,high); // 第key个元素已经排好序,继续两边搜索排序
Qsort(s,low,key-1);
Qsort(s,key+1,high);
}
}
归并
//非递归
void mergeSort() {//这道题戏剧性地不能用归并递归
for(int step=2; step/2<=n; step*=2) {
for(int i=1; i<=n; i+=step) {
int mid=i+step/2-1;
if(mid+1<=n) {//右边区间存在
merge(i,mid,mid+1,min(i+step-1,n));//这个函数不写了
}
}
}
}
//递归版本自己看一下
关键路径
应用举例:有向无环图的最长路径
#include <iostream>
#include <stack>
#include <queue>
#include <vector>
#include <cstring>
using namespace std;
const int MAXV=100;
vector<int> G[MAXV];
stack<int> topOrder;
int inDegree[MAXV];
bool topologicalSort() {
queue<int> q;
for(int i=0; i<n; i++) {
if(inDegree[i]==0) {
q.push(i);
}
}
while(!q.empty()) {
int u=q.front();
q.pop();
topOrder.push(u);
for(int v=0; v<G[u].size; v++) {
int vex=G[u][v].v;
inDegree[vex]--;
if(inDegree[v]==0) {
q.push(v);
}
if(ve[u]+G[u][v].w>ve[vex]) {//计算ve[...]
ve[v]=ve[u]+G[u][v];
}
}
}
if(topOrder.size()==n) return true;
else return false;
}
int CriticalPath(bool falg) {//求关键路径长度
memset(ve,0,sizeof(ve));//ve初始化
if(topologicalSort()==false) {
return -1;//不是有向无环图
}
fill(vl,vl+n,ve[n-1]);//vl数组初始化,如果不确定汇点,就在ve中求出最大值来代替ve[n-1]
while(!topOrder.empty()) {
int u=topOrder.top();
topOrder.pop();
for(int i=0; i<G[u].size(); i++) {
int v=G[u][i].v;
if(vl[v]-G[u][i].w<vl[u]) {
vl[u]=vl[v]-G[u][i].w;
}
}
}
//遍历领接表所有边,计算获得最早时间和最迟时间
for(int u=0; u<n; u++) {
for(int i=0; i<G[u].size(); i++) {
int v=G[u][i].v,w=G[u][i].w;
int e=ve[u],l=vl[v]-w;
if(e==l) {
printf("%d->%d\n",u,v);//输出关键活动
}
}
}
return ve[n-1];//返回关键路径长度
}
int main() {
int len=CriticalPath(flag);
return 0;
}
最小生成树
// 邻接矩阵实现
int G[maxn][maxn];// 邻接矩阵
int vis[maxn];// 访问矩阵
int d[maxn];// 顶点与集合 S 的最短距离
int n;
int Prim() {// 默认 0 号为初始点,函数返回最小生成树的边权之和
int i,j;
int ans,u,v,min;
for(i=0; i<n; i++) {
d[i]=INF;
}
d[0]=0;// 初始化 d[] ,除 0 号为 0 外其余全为 INF
ans=0;// 存放最小生成树边权之和
for(i=0; i<n; i++) {
u=-1,min=INF;
for(j=0; j<n; j++) {
if(vis[j]==0&&d[j]<min) {
u=j;
min=d[j];
}
}
// 找不到小于 INF 的 的 d[u], 则剩下的顶点和集合 S 不连通
if(u=-1) {
return -1;
}
vis[u]=1;// 设置 u 为已访问
ans+=d[u]// 加入最小生成树
for(v=0; v<n; v++) {
//v 未访问&&u 能到达 v&&以 以 u 为中介点可以使 v 离集合 S 更近
if(vis[v]==0&&G[u][v]!=INF&&G[u][v]<d[v]) {
d[v]=G[u][v];//将 将 G[u][v] 赋值给 d[v]
}
}
}
return ans;// 返回边权之和
哈夫曼
只给一个例子求最小权重,多个苹果不同重量,怎么提到目的地花力气最少。
#include <iostream>
#include <queue>
using namespace std;
priority_queue<int,vector<int>,greater<int>> q;//构建小顶堆
int main() {
int n,weight,ans=0;//ans计算最后总权重
cin>>n;
for(int i=0; i<n; i++) {
cin>>weight;
q.push(weight);
}
while(q.size()>1) {//当优先队列内元素最少有2个时候才开始合并
int x=q.top();
q.pop();
int y=q.top();
q.pop();
q.push(x+y);
ans+=x+y;
}
cout<<ans;
return 0;
}
剩余补充
进制转换代码模板:
int z[50],num=0;
do{
//来做进制的转换
z[num++]=n%b;
n=n/b;
}while(n!=0);
转换为b进制后转换回来为十进制的代码模板
for(int i=num-1;i≥0;i--){
n=n*b+z[i];
}
求最大最小公约数代码模板:求最大公约数
int gcd(int a,int b){
return !b ? a: gcd(b,a%b)
}
最小公倍数就是求出来的最大公倍数m后让a*b/m
尽量用long long类型

浙公网安备 33010602011771号