数据结构第二部分学习ing
学习前的一点点记录:今日有许些感叹,虽然总会有低谷的时候,但一定要坚信你的信仰,即使在黑暗,也要相信未来你会遇到你想要的,你期待的,以及你期望的人是存在的,也总有人过着你期望的生活。
为了自己的信仰,加油学习吧!
trie字典树
trie,又称前缀树或字典树,是一种有序树,用于保存关联数组,其中的键通常是字符串。
与二叉查找树不同,键不是直接保存在节点中,而是由节点在树中的位置决定。
一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。
应用:
Trie的典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。
它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
题目
维护一个字符串集合,支持两种操作:
“I x”向集合中插入一个字符串x;
“Q x”询问一个字符串在集合中出现了多少次。
1
2
共有N个操作,输入的字符串总长度不超过 10万,字符串仅包含小写英文字母
输入格式
第一行包含整数N,表示操作数。
接下来N行,每行包含一个操作指令,指令为”I x”或”Q x”中的一种。
输出格式
对于每个询问指令”Q x”,都要输出一个整数作为结果,表示x在集合中出现的次数。
每个结果占一行。
数据范围
1≤N≤2∗104
输入样例:
5
I abc
Q abc
Q ab
I ab
Q ab
1
2
3
4
5
6
输出样例:
1
0
1
AC代码:
//Trie字符串统计
#include<iostream>
using namespace std;
const int N=100010;
//son[i][j]记录第i个子节点,对应的字母是第j个时,下一个节点的下标
//cnt[i]记录的是第i个节点出现的次数
//idx记录的是此时节点的编号
int son[N][26],cnt[N],idx;
//下标为0的节点既是根节点也是空节点
char str[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(){
int n;
cin>>n;
while(n--){
char op[6];
cin>>op>>str;
if(op[0]=='I') insert(str);
else cout<<query(str)<<endl;
}
}
并查集
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。比如说,我们可以用并查集来判断一个森林中有几棵树、某个节点是否属于某棵树等。
题目链接
https://www.acwing.com/problem/content/838/
一共有 n 个数,编号是 1∼n,最开始每个数各自在一个集合中。
现在要进行 m 个操作,操作共有两种:
M a b,将编号为 a 和 b 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
Q a b,询问编号为 a 和 b 的两个数是否在同一个集合中;
输入格式
第一行输入整数 n 和 m。
接下来 m 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。
输出格式
对于每个询问指令 Q a b,都要输出一个结果,如果 a 和 b 在同一集合内,则输出 Yes,否则输出 No。
每个结果占一行。
数据范围
1≤n,m≤105
输入样例:
4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4
输出样例:
Yes
No
Yes
错误点总结:
并查集简短时间最重要的就是路径压缩,路径压缩是通过让这条路径上的每一个点都指向根节点实现的
代码小技巧:
用%s而不用%c,是因为%s会自动过滤掉空格,而%c则会全部读入。
AC代码:
//并查集
#include<iostream>
using namespace std;
const int MAX=100010;
int p[MAX];
//查询
//返回祖宗节点,同时对路径压缩!
int find(int x){
if(p[x]!=x) p[x]=find(p[x]);//路径压缩(第一次就错在这里)
return p[x];
}
int main(){
int n,m;
scanf("%d %d",&n,&m);
//根节点等于它自己的编号
for(int i=1;i<=n;i++) p[i]=i;
while(m--){
char op[2];
int a,b;
scanf("%s%d%d",op,&a,&b);
if(op[0]=='M') p[find(a)]=find(b);
else{
if(find(a)==find(b)) puts("Yes");
else puts("No");
}
}
return 0;
}
变型题目关键:需要维护的那个东西怎么是实现
堆
一:堆
1.概念
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1} ,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<=K2i+2 ,则称为小堆(或大堆)。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
2. 性质
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
3. 结构
4.将数组转换成最小堆
从倒数第二排开始,进行向下排序,排序到顶点,就成了一个最小堆
代码
for(int i=n/2;i;i--) down(i);
二、向下调整
1.思路
先设定根节点为当前节点(通过下标获取,标记为u),比较左右子树的值,找出更小的值,用x来标记。
比较x和u的值,如果不相同,则交换
处理完一个节点之后,从当前的x出发,循环之前的过程。
2.代码
void down(int x){
int u=x;
if(u*2<=siz&&h[u*2]<h[x]) x=u*2;
if(u*2+1<=siz&&h[u*2+1]<h[x]) x=u*2+1;
if(u!=x){
swap(h[u],h[x]);
down(x);
}
}
三、向上调整
1.思路
先设定根节点为当前节点(通过下标获取,标记为x),比较父节点值,如果父节点更大,则交换
处理完一个节点之后,从当前的x/2出发,循环之前的过程。
2.代码
void up(int x){
while(x/2&&h[x/2]>h[x]){
swap(h[x/2],h[x]);
x/=2;
}
}