0x02 数据结构
数据结构
链表与邻接表
单链表
\(e[i]:\)某点的值,\(ne[i]:\)某点的next指针
A((head)) --ne[0]--> B((node)) --ne[1]--> C((node)) --ne[2]--> D((NULL))
//head 表示头节点下标 (ne[-1])
//e[i] 表示节点 i 的值
//ne[i] 表示结点 i 的 next 指针的值 (下一结点下标)
//idx 存储当前已经用到了哪个点
int head,e[N],ne[N],idx;
//初始化
void init()
{
head=-1;
idx=0;
}
//将 x 插到头结点
void add_to_head(int x)
{
e[idx]=x;
ne[idx]=head;
head=idx++;
}
//将 x 插到下标是 k 的点后面
void add(int k,int x)
{
e[idx]=x;
ne[idx]=ne[k];
ne[k]=idx++;
}
//将下标是 k 的点后面的点删除
void remove(int k)
{
ne[k]=ne[ne[k]];
}
邻接表:存储图和数
双链表
A((Head)) --right--> B((Node)) --right--> C((Node)) --right--> D((Tail))
D --left--> C --left--> B --left--> A
int e[N],l[N],r[N],idx;
void init()
{
//0 表示左端点, 1 表示右端点
r[0]=1,l[1]=0;
idx=2;
}
void add(int k,int x)//在 k 的右边插入 x
{
e[idx]=x;
l[idx]=k;r[idx]=r[k];
l[r[k]]=idx;r[k]=idx++;
}
void del(int k)
{
r[l[k]]=r[k];
l[r[k]]=l[k];
}
栈与队列
栈:先进后出
- 定义:
int stk[N],tt=0;
- 插入:
stk[++tt]=x;
- 弹出:
tt--;
- 判断栈是否为空:
if(tt>0)
- 栈顶:
stk[tt]
- 定义:
队列:先进后出
- 初始化:
int q[N],hh=0,tt=-1;
- 插入:
q[++tt]=x;
- 弹出:
hh++;
- 判断是否为空:
if(hh<=tt)
- 取出队头:
q[hh]
- 初始化:
单调栈与队列
单调栈
应用:给定序列,求每个数左边最近的比它大/小的数
for(int i=0;i<n;i++)//生成单调增的栈
{//tt 为栈顶
cin>>x;
while(tt&&stk[tt]>=x) tt--;//出栈 从栈顶弹出
stk[++tt]=x;//入栈
}
单调队列
应用:求滑动窗口最大值最小值
for(int i=0;i<n;i++)//生成单调增的队列
{//hh,tt 为队头队尾 ;k 为窗口长度
while(hh<=tt&&i-k+1>q[hh]) hh++;//从队头出队
while(hh<=tt&&a[q[tt]]>=a[i]) t--;//从队尾出队
q[++tt]=i;//从队尾入队
}
KMP
基本思想:利用已匹配过的信息来跳过重复的比较,复杂度从\(O(n*m)\)降到\(O(n+m)\)
前缀函数:\(\pi_i\) 表示 第\(i\)个前缀的最长匹配真前后缀的长度
朴素算法
for(int i=1;i<=n;i++)
{
bool flag=true;
for(int j=1;j<=m;j++)
if(s[i+j]!=p[j])
{
flag=false;
break;
}
}
KMP算法
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
//要从 s+1,p+1 开始读
//求模式串的 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];
// 匹配成功后的逻辑
}
}
Trie字典树
Root((Root))---A((A))---B((B))---D((*B))
A---G((*C))---H((E))---I((*D))
B---K((E))---L((*F))
Root---M((B))---O((D))---P((*F))
Root---Q((C))---R((D))---S((A))---T((*A))
Q---U((*F))
高效地存储和查找字符串集合
带星号标记表示一个字符串的结束
son[i][j]
表示第i
个节点的第j
个子节点,cnt[N]
记录字符串插入次数,idx
给节点编号(节点标识)
int son[N][26],cnt[N],idx;//下标为 0 的点既是根节点又是空节点
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];//返回出现次数
}
并查集
快速处理以下问题,复杂度近乎\(O(1)\):
- 将两个集合合并
- 询问两个元素是否在一个集合当中
基本原理:每个集合用一棵树来表示,树根的编号就是整个集合的编号,每个节点存储它的父节点,p[x]
表示x
的父节点
- 判断树根:
if(p[x]==x)
- 求
x
的集合编号:while(p[x]!=x) x=p[x];
- 合并集合:
p[x]=y
(集合x
插入到集合y
)
路径压缩:将查找时沿途经过的所有节点直接连接到根节点,优化查找操作时间
按秩合并:合并时,将“秩”较小的数合并到“秩”较大的树下,优化合并操作时间
朴素并查集
int p[N]; //存储每个点的祖宗节点
// 返回x的祖宗节点 + 路径压缩
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
// 初始化,假定节点编号是1~n
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并a和b所在的两个集合:
p[find(a)] = find(b);
维护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~n
for (int i = 1; i <= n; i ++ )
{
p[i] = i;
size[i] = 1;
}
// 合并a和b所在的两个集合:
void merge(int a,int b)
{
size[find(b)] += size[find(a)];
p[find(a)] = find(b);
}
维护到祖宗节点距离的并查集
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~n
for (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)的偏移量
堆
一种特殊的完全二叉树结构,除最后一层外每一层都被填满,最后一层从左到右依次排列
数组存储时,\(x\)的左儿子是\(2x\),右儿子是\(2x+1\),下标从 \(1\) 开始(\(0\) 的左儿子还是 \(0\)),\(dowu(x)\)和\(up(x)\)复杂度均为\(O(logn)\)
小根堆:父节点的值都小于等于其子节点的值,大根堆:父节点的值都大于等于其子节点的值
R((1))---L1((2))
R---R1((3))
L1---L2((4))
L1---R2((5))
R1---L3((6))
R1---R4((7))
L2---L5((8))
L2---R6((9))
- 插入一个数:
heap[++size]=x;up(size);
- 求集合中的最小值:
heap[1]
- 删除最小值:
heap[1]=heap[size--];down(1);
- 删除任意元素(STL不能):
heap[k]=heap[size--];down(k);up(k);
- 修改任意元素(STL不能):
heap[k]=x;douwn(k);up(k);
// 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);
哈希表
哈希表(Hash Table)是一种基于哈希函数实现的快速查找数据结构。通过哈希函数将复杂数据结构映射,实现快速查找和插入
哈希冲突:多个不同元素通过哈希函数计算映射到哈希表中同一个槽
A(哈希表)---B(存储结构)---C(开放寻址法)
B---D(拉链法)
A---E(字符串哈希方式)
拉链法
\(h[N]\)是哈希表,存储哈希表的索引链表头,即链表头指针
int h[N], e[N], ne[N], idx;
memset(h,-1,sizeof(h));
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;
}
开放寻址法
所有元素直接存储在哈希表的数组中。当发生冲突时,探测下一个槽(通过线性探测、二次探测或双重哈希等方法)直到找到空槽
const int N = 200003,null = 0x3f3f3f;
int h[N];//一般开数据的两到三倍
memset(h,0x3f,sizeof h);
// 如果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;
}
int k=find[x];
h[k]=x;//插入元素
if(h[k]!=null) puts("Yes");//查找元素
字符串哈希方式
字符串前缀哈希法:
\(H(S[1, i]) = (s_1 \cdot b^{i-1} + s_2 \cdot b^{i-2} + \cdots + s_i \cdot b^0) \mod p\)
\(s_i\) 是字符串第 \(i\) 个字符的数值,\(b\) 是大于字符集大小的基数,\(p\) 是大质数防止哈希值过大
经验值:\(p=131\)或\(13331\),\(Q=2^{64}\)(可用unsigned long long
存储)
递推计算前缀哈希值 :
\(H(S[1, i]) = (H(S[1, i-1]) \cdot b + s_i) \mod p\)
字符串 \(S\) 的子串 \(S[l, r]\) 的哈希值:
\(H(S[l, r]) = (H(S[1, r]) - H(S[1, l-1]) \cdot b^{r-l+1}) \mod p\)
typedef unsigned long long ULL;//用 ULL 存储自动 mod 2^64
const int N = 100010,P=131;
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];
}
0x02 数据结构的更多相关文章
- [从源码学设计]蚂蚁金服SOFARegistry 之 ChangeNotifier
[从源码学设计]蚂蚁金服SOFARegistry 之 ChangeNotifier 目录 [从源码学设计]蚂蚁金服SOFARegistry 之 ChangeNotifier 0x00 摘要 0x01 ...
- [从源码学设计]蚂蚁金服SOFARegistry之配置信息
[从源码学设计]蚂蚁金服SOFARegistry之配置信息 目录 [从源码学设计]蚂蚁金服SOFARegistry之配置信息 0x00 摘要 0x01 业务范畴 1.1 配置作用 1.2 学习方向 0 ...
- Python2 基本数据结构源码解析
Python2 基本数据结构源码解析 Contents 0x00. Preface 0x01. PyObject 0x01. PyIntObject 0x02. PyFloatObject 0x04. ...
- linux内核数据结构学习总结
目录 . 进程相关数据结构 ) struct task_struct ) struct cred ) struct pid_link ) struct pid ) struct signal_stru ...
- MPEG2_TS流基本概念和数据结构
时对应network_PID,program_number等于其它值时对应program_map_PID. (3)PMT PMT数据结构如下: TS_program_map_section(){ ...
- Java数据结构和算法 - 递归
三角数字 Q: 什么是三角数字? A: 据说一群在毕达哥拉斯领导下工作的古希腊的数学家,发现了在数学序列1,3,6,10,15,21,……中有一种奇特的联系.这个数列中的第N项是由第N-1项加N得到的 ...
- [转]Redis内部数据结构详解-sds
本文是<Redis内部数据结构详解>系列的第二篇,讲述Redis中使用最多的一个基础数据结构:sds. 不管在哪门编程语言当中,字符串都几乎是使用最多的数据结构.sds正是在Redis中被 ...
- 20172302《程序设计与数据结构》实验三 敏捷开发与XP实践报告
课程:<程序设计与数据结构> 班级: 1723 姓名: 侯泽洋 学号:20172302 实验教师:王志强老师 实验日期:2018年5月16日 必修/选修: 必修 1.实验内容 (1)代码规 ...
- Redis学习笔记之Redis中5种数据结构的使用场景介绍
原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构 ...
- 探索Redis设计与实现3:Redis内部数据结构详解——sds
本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...
随机推荐
- IDEA神器插件-40款
IDEA 插件安装 步骤 IDEA里面,选择打开 File --> Settings --> Plugins 在Plugins里面, 可以搜索需要的插件 (下面的标题),然后安装 如下图: ...
- consul简介
Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置. Consul是分布式的.高可用的.可横向扩展的.它具备以下特性 : 服务发现:consul通过DNS或者HTT ...
- DynamoDB-键值存储
什么是DynamoDB? DynamoDB 是一个你什么也不用管的 NoSql 数据库.记得给 AWS付账单就可以. 在2004年的时候, Amazon 发现 Oracle 数据库都不够用了.为了还能 ...
- 魔乐开发者社区正式上线,AI开发者快来撩!
近日,由天翼云与华为联合打造的魔乐(Modelers)开发者社区,在天翼云中国行·贵州站活动现场上线发布.依托全场景开源模型资源.易用的模型开发套件,以及强大的共建共享生态系统等优势,社区将使能开发者 ...
- .NET周刊【2月第1期 2025-02-02】
国内文章 dotnet 9 已知问题 默认开启 CET 导致进程崩溃 https://www.cnblogs.com/lindexi/p/18700406 本文记录 dotnet 9 的一个已知且当前 ...
- 在IDEA如何使用JProfiler性能分析
一.下载地址 https://www.ej-technologies.com/download/jprofiler/files 版本:11 激活码:L-J11-Everyone#speedzodiac ...
- [POI2014] HOT-Hotels 加强版题解
好好好,太好了这题,太好了. 首先有一点是很明显的: 对于一个合法的答案 \((i,j,k)\),必有一点 \(p\),使 \(dis(i,p)=dis(j,p)=dis(k,p)\) 且三点到 \( ...
- nginx: [error] open() “/usr/local/var/run/nginx.pid” failed (2: No such file or directory)
- thymeleaf的手动渲染HTML模板
thymeleaf的手动渲染HTML模板 长河 2018-11-14 11:18:10 6833 收藏 2 分类专栏: Springboot 版权 现在很多公司都在thymeleaf作为前端的显示 ...
- Java基础常见知识、面试题总结 (上)
Java语言有哪些特点 简单易学: 面向对象(封装,继承,多态): 平台无关性( Java 虚拟机实现平台无关性): 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来 ...