第二章 数据结构 【完结】
栈那节表达式求值,并查集食物链还没做
堆,哈希表不熟,比赛的时候也很少遇到,故没有经常的练习。
目录
- 单链表[静态]
- 双链表[静态]
- 栈
- 队列
- 单调栈
- 单调队列
- KMP
- Trie
- 并查集
- 堆
- 哈希表
单链表[静态]
模板题 AcWing 826. 单链表
y神模板:
// 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];
}
需要注意的是: idx是一直递增的
完整的代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e5+10;
//head 指向头节点的指针
//e[N] 保存该节点的值
//ne[N]保存该节点的下一个节点的地址
//idx 表示目前分配到哪里的指针
int head,idx;
int e[N],ne[N];
void init()//初始化
{head=-1;idx=0;
}
void add_to_head(int x)//将x插到头节点
{ne[idx]=head;e[idx]=x;head=idx;idx++;
}
void add(int k,int x)//将x插入到下标是k的点后面
{ne[idx]=ne[k];e[idx]=x;ne[k]=idx;idx++;
}
void remove(int k)//将下标是k的点的后面的点删除
{ne[k]=ne[ne[k]];
}
int main(void)
{int n; cin>>n;init();while(n--){char op; cin>>op;if(op=='H'){int x; cin>>x;add_to_head(x);}if(op=='I'){int k,x; cin>>k>>x;add(k-1,x);}if(op=='D'){int k; cin>>k;if(!k) head=ne[head];remove(k-1);}}for(int i=head;i!=-1;i=ne[i]) cout<<e[i]<<" ";return 0;
}
双链表[静态]
模板题 AcWing 827. 双链表
双链表的初始状态
当我们从链表的最左边插入的时候,是从0的右边插入的。
当我们从链表的最右边插入的时候,是从1的左边插入的。
y神模板:
// 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];
}
完整的代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=100010;
int m;
int e[N],l[N],r[N],idx;//在节点a的右边插入一个数
void insert(int a,int x)
{e[idx]=x;l[idx]=a,r[idx]=r[a];l[r[a]]=idx,r[a]=idx++;
} //删除节点
void remove(int a)
{l[r[a]]=l[a];r[l[a]]=r[a];
} void init()
{r[0]=1,l[1]=0;idx=2;
}int main(void)
{cin>>m;init();while(m--){string op; cin>>op;int k,x;if(op=="L"){cin>>x;insert(0,x);}if(op=="R"){cin>>x;insert(l[1],x);}if(op=="D"){cin>>k;remove(k+1);}if(op=="IL"){cin>>k>>x;insert(l[k+1],x);}if(op=="IR"){cin>>k>>x;insert(k+1,x);}}for(int i=r[0];i!=1;i=r[i]) cout<<e[i]<<" ";cout<<endl;
}
栈
模板题 AcWing 828. 模拟栈
3302. 表达式求值 不会
#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <unordered_map>using namespace std;stack<int> num;
stack<char> op;void eval()
{auto b = num.top(); num.pop();auto a = num.top(); num.pop();auto c = op.top(); op.pop();int x;if (c == '+') x = a + b;else if (c == '-') x = a - b;else if (c == '*') x = a * b;else x = a / b;num.push(x);
}int main()
{unordered_map<char, int> pr{{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}};string str;cin >> str;for (int i = 0; i < str.size(); i ++ ){auto c = str[i];if (isdigit(c)){int x = 0, j = i;while (j < str.size() && isdigit(str[j]))x = x * 10 + str[j ++ ] - '0';i = j - 1;num.push(x);}else if (c == '(') op.push(c);else if (c == ')'){while (op.top() != '(') eval();op.pop();}else{while (op.size() && op.top() != '(' && pr[op.top()] >= pr[c]) eval();op.push(c);}}while (op.size()) eval();cout << num.top() << endl;return 0;
}
y神模板:
// tt表示栈顶
int stk[N], tt = 0;// 向栈顶插入一个数
stk[ ++ tt] = x;// 从栈顶弹出一个数
tt -- ;// 栈顶的值
stk[tt];// 判断栈是否为空
if (tt > 0)
{}
完整代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e5+10;
int st[N],tt;
int main(void)
{int m; cin>>m;while(m--){string op; cin>>op;if(op=="push"){int x; cin>>x;st[++tt]=x;}if(op=="pop") tt--;if(op=="query") cout<<st[tt]<<endl;if(op=="empty") if(tt>0) cout<<"NO"<<endl;else cout<<"YES"<<endl;}
}
队列
模板题 AcWing 829. 模拟队列
普通队列:
1. 普通队列:
// hh 表示队头,tt表示队尾
int q[N], hh = 0, tt = -1;// 向队尾插入一个数
q[ ++ tt] = x;// 从队头弹出一个数
hh ++ ;// 队头的值
q[hh];// 判断队列是否为空
if (hh <= tt)
{}
完整代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e5+10;
int q[N],hh,tt=-1;
int main(void)
{int m; cin>>m;while(m--){string op; cin>>op;if(op=="push"){int x; cin>>x;q[++tt]=x;}if(op=="pop") hh++;if(op=="empty"){if(hh<=tt) cout<<"NO"<<endl;else cout<<"YES"<<endl;}if(op=="query") cout<<q[hh]<<endl;}return 0;
}
循环队列
// 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)
{}
单调栈
关于单调栈的文章
模板题 AcWing 830. 单调栈
y神模板:
常见模型:找出每个数左边离它最近的比它大/小的数
int tt = 0;
for (int i = 1; i <= n; i ++ )
{while (tt && check(stk[tt], i)) tt -- ;stk[ ++ tt] = i;
}
完整代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e5+10;
int st[N],x;
int tt=0;
int main(void)
{int n; cin>>n;while(n--){cin>>x;while(st[tt]>=x) tt--;//栈顶元素大于当前元素,则出栈if(!tt) cout<<"-1"<<" ";;//栈空,输出-1if(tt) cout<<st[tt]<<" ";//非空,输出栈顶元素st[++tt]=x;}return 0;
}
单调队列
模板题 AcWing 154. 滑动窗口
y神模板:
常见模型:找出滑动窗口中的最大值/最小值
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;
}
总的代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=1e6+10;
int a[N],q[N];
int hh,tt;
int n,k;
int main(void)
{cin>>n>>k;for(int i=0;i<n;i++) scanf("%d",&a[i]);hh=0,tt=-1;for(int i=0;i<n;i++)//这里的q是一个递增的序列 , 队首元素就是所求的最小值{if(i-k+1>q[hh]) hh++;//窗口出界了while(hh<=tt&&a[i]<=a[q[tt]]) tt--;//不满足单调性q[++tt]=i;//q保存的是a数组的下标if(i+1>=k) printf("%d ",a[q[hh]]);}cout<<endl;hh=0,tt=-1;for(int i=0;i<n;i++)//这里的q是一个递减的序列{if(i-k+1>q[hh]) hh++;while(hh<=tt&&a[i]>a[q[tt]]) tt--;q[++tt]=i;if(i+1>=k) printf("%d ",a[q[hh]]);}return 0;
}
KMP
模板题 AcWing 831. KMP字符串
y神模板:
// 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];// 匹配成功后的逻辑}
}
总的代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=100010,M=1000010;int n,m;
int ne[N];
char s[M],p[N];
int main(void)
{cin>>n>>p+1>>m>>s+1;//求Next数组:// s[]是模式串,p[]是模板串, n是s的长度,m是p的长度// 为什么要从i = 2开始匹配?因为next[1] = 0,已经确定了,所以应该从i = 2开始匹配。// 注意:next[i]的定义是非平凡的最大后缀等于最大前缀,next[i]必须要小于i// 对于每一个i开始匹配过程for (int i = 2, j = 0; i <= n; i ++ ){// 如果p[i] != p[j + 1]那么,就跳到ne[j]再进行匹配p[i]与p[j + 1],直到p[i] == p[j + 1]或j = 0为止// j一定要大于0,因为j大于0才能跳转到next[j]嘛,ne[0]没有意义while (j && p[i] != p[j + 1]) j = ne[j]; // 如果一直跳转到 p[i] == p[j + 1],那么就可以移动jif (p[i] == p[j + 1]){j ++ ;ne[i] = j;}// 否则就说明i点没有最大后缀等于最大前缀else{ne[i] = 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;
}
Trie
Trie的概念: 高效地存储和查找字符串集合的数据结构。
835. Trie字符串统计
143. 最大异或对
240. 食物链 未解决
y神的模板:
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];
}
首先要注意的一点就是: N代表的是输入的字符串总长度。
也就是说,这个Trie树的结点个数和是小于等于N的。
第二点 idx的含义:其实和单链表里的那个含义差不多,如下图我已经用绿笔标出了结点对应的idx的值
需要注意的一点就是我们的每一个点,对应的idx都是唯一的,这样我们再找儿子的过程中就不会有一对多的情形。
第三点son[N] [26] 的含义
很多小伙伴,不知道这个的含义是啥。其是只要带个案例模拟一边你就懂了。
例: son[0][u] 这个的含义就是根节点它的孩子u结点的下标,跟单链表里的next指针其实是一样的。
不过有区别的一点是我们的单链表它的孩子只有一个,是一条链子。
但是我们的Trie是后面可能有26个孩子(因为英文字母只有26个),即有26种路径可以选的单链表。
例: abc
其实只要把字符串的每条路径当成一个单链表,就十分的容易理解了。
最后cnt[p] 的含义
就是该字符串出现的次数。比如上图的: abc 这条字符串单链表已经到头了,那么我们就 cnt[p]++,
p代表的就是c字符所对应的idx,idx我已经说了它和一个字符是唯一对应的,这里的唯一对应不仅是字符对应的关系还有位置(即树的层数)。
因为第1层的" a " 和 第二层的 " a ” 是不同的。
总的代码:
#include<cstdio>
#include<iostream>
using namespace std;
const int N=100010;
int s[N][26],cnt[N],idx;
void insert(string str)
{int p=0;for(int i=0;str[i];i++){int u=str[i]-'a';if(!s[p][u]) s[p][u]=++idx;p=s[p][u];}cnt[p]++;
}
int query(string str)
{int p=0;for(int i=0;str[i];i++){int u=str[i]-'a';if(!s[p][u]) return 0;//没有找到p=s[p][u];//下一个结点的指针}return cnt[p];
}
int main(void)
{int n; cin>>n;while(n--){string op,str;cin>>op>>str;if(op=="I") insert(str);else cout<<query(str)<<endl;}
}
还有一点就是我们每次是从根节点开始找的,找到就继续找,没有找到就创建子结点。
并查集
836. 合并集合
837. 连通块中点的数量
y神模板:
(1)朴素并查集:int p[N]; //存储每个点的祖宗节点// 返回x的祖宗节点int find(int x){if (p[x] != x) p[x] = find(p[x]);return p[x];}// 初始化,假定节点编号是1~nfor (int i = 1; i <= n; i ++ ) p[i] = i;// 合并a和b所在的两个集合:p[find(a)] = find(b);(2)维护size的并查集:int p[N], size[N];//p[]存储每个点的祖宗节点, size[]只有祖宗节点的有意义,表示祖宗节点所在集合中的点的数量// 返回x的祖宗节点int find(int x){if (p[x] != x) p[x] = find(p[x]);return p[x];}// 初始化,假定节点编号是1~nfor (int i = 1; i <= n; i ++ ){p[i] = i;size[i] = 1;}// 合并a和b所在的两个集合:size[find(b)] += size[find(a)];p[find(a)] = find(b);(3)维护到祖宗节点距离的并查集:int p[N], d[N];//p[]存储每个点的祖宗节点, d[x]存储x到p[x]的距离// 返回x的祖宗节点int find(int x){if (p[x] != x){int u = find(p[x]);d[x] += d[p[x]];p[x] = u;}return p[x];}// 初始化,假定节点编号是1~nfor (int i = 1; i <= n; i ++ ){p[i] = i;d[i] = 0;}// 合并a和b所在的两个集合:p[find(a)] = find(b);d[find(a)] = distance; // 根据具体问题,初始化find(a)的偏移量
堆
838. 堆排序
839. 模拟堆
y神模板:
// h[N]存储堆中的值, h[1]是堆顶,x的左儿子是2x, 右儿子是2x + 1
// ph[k]存储第k个插入的点在堆中的位置
// hp[k]存储堆中下标是k的点是第几个插入的
int h[N], ph[N], hp[N], size;// 交换两个点,及其映射关系
void heap_swap(int a, int b)
{swap(ph[hp[a]],ph[hp[b]]);swap(hp[a], hp[b]);swap(h[a], h[b]);
}void down(int u)
{int t = u;if (u * 2 <= size && h[u * 2] < h[t]) t = u * 2;if (u * 2 + 1 <= size && h[u * 2 + 1] < h[t]) t = u * 2 + 1;if (u != t){heap_swap(u, t);down(t);}
}void up(int u)
{while (u / 2 && h[u] < h[u / 2]){heap_swap(u, u / 2);u >>= 1;}
}// O(n)建堆
for (int i = n / 2; i; i -- ) down(i);
哈希表
840. 模拟散列表
一般哈希:
(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;}
字符串哈希:
841. 字符串哈希
y神模板:
核心思想:将字符串看成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];
}
第二章 数据结构 【完结】相关推荐
- 斗地主AI算法——第二章の数据结构
上一章我们已经确立了基本的业务逻辑处理流程.从本章开始,我们便进入开发阶段. 首先就是明确我们都需要哪些数据,且它们以怎样的形式存储. 首先从上一章反复提到的手牌权值结构说起,也就是F()的返回值,他 ...
- yxc_第二章 数据结构(一)_链表
写在前面:链表这一部分完结,我觉得链表最重要的还是根据题目来写代码(数组模拟指针),可能是大二上已经学过应试版数据结构的缘故吧.最后,还是希望自己能够持之以恒,在寒假完成算法基础课.(最近几天都睡到了 ...
- 第二章 数据结构(二)
文章目录 Trie 835 Trie 字符串统计 并查集 Trie 高效存储和查找字符串集合的数据结构 有结尾标记,边创建节点,边存储 835 Trie 字符串统计 维护一个字符串集合,支持两种操作: ...
- 第二章 数据结构(一)
文章目录 链表和邻接链表 单链表 826 单链表 双链表 827 双链表 栈与队列 830 单调栈 kmp 831 kmp字符串 链表和邻接链表 数据模拟的速度会快很多,每次 new 一个新的节点,速 ...
- yxc_第二章 数据结构(二)_Trie树
目录 一.基本概念 二.AcWing 835 Trie字符串统计 本题思路: 一.基本概念 接下来的案例类似于邻接矩阵,横轴表示str[ i ] - 'a'(省略了7~25),竖轴表示节点编号.红色的 ...
- yxc_第二章 数据结构(一)_kmp算法
目录 一.思路 二.AcWing 831 KMP字符串 三.时间复杂度 一.思路 当子串P与模式串S匹配一部分时,在两个串的接下来的一个元素不相同,而在已经匹配成功的串中存在前缀等于后缀.此时后缀最后 ...
- yxc_第二章 数据结构(一)_栈和队列
目录 一.栈和队列的代码操作 1.AcWing 828 模拟栈 2.AcWing 829 模拟队列 二.单调栈 1.AcWing 830 单调栈 三.单调队列 1.AcWing 154 滑动窗口 一. ...
- python (第二章)数据结构
文章目录 2.5 对序列使用 +和 × 建立由列表组成的列表 2.6序列的增量赋值(+=和×=) 关于 +=的谜题 补充:extend()方法和+有什么区别呢? 2.7 list.sort方法和内置函 ...
- ppk on JavaScript第二章:背景(完结篇)
无障碍规则 尽管无法预见可能损害一个有脚本网站的可用性的所有情形,但我已经总结出一些可以帮助您在基础上不犯错的规则.不要把它们当作JavaScript和无障碍的终极规则,这只是能防止一些常见低级错误的 ...
最新文章
- python3 django配置数据库(mysql)
- Python中使用数据库SQLite
- Ignite 的helloworld第二弹!(附源码!下载即用)
- C# 窗体输入个人信息 存入txt 窗体输出
- python与正则表达式(part4)--正则表达式分组
- c#中chart绘制曲线,柱状图等
- python画二次函数图像的顶点_画二次函数图像的步骤
- P1090-合并果子【离散化,队列,时间复杂度O(n)】
- 2020蓝桥杯省赛---java---B---10(整数小拼接)
- shell基础(上)
- Genymotion下载及安装(安卓虚拟机)
- SpringCloud微服务实战(一)-简介
- mock SpringMVC 测试控制器方法
- 洛谷P2024 食物链
- Win10系统安装失败?用这种方法一键就解决了
- 网上银行显示本服务器只显示,使用企业网上银行时常见报错提示有哪些,怎么解决?...
- windows 10 下载工具
- iperf3网络测速
- 电网企业 财务管理创新
- 电脑回收站在哪里?回收站删除的文件怎么恢复?