数据结构

链表与邻接表

单链表

\(e[i]:\)某点的值,\(ne[i]:\)某点的next指针

flowchart LR
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]];
}

邻接表:存储图和数

双链表

flowchart LR
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];
}

栈与队列

  1. 栈:先进后出

    • 定义:int stk[N],tt=0;
    • 插入:stk[++tt]=x;
    • 弹出:tt--;
    • 判断栈是否为空:if(tt>0)
    • 栈顶:stk[tt]
  2. 队列:先进后出

    • 初始化:int q[N],hh=0,tt=-1;
    • 插入:q[++tt]=x;
    • 弹出:hh++;
    • 判断是否为空:if(hh<=tt)
    • 取出队头:q[hh]

单调栈与队列

  1. 单调栈

    应用:给定序列,求每个数左边最近的比它大/小的数

    for(int i=0;i<n;i++)//生成单调增的栈
    {//tt 为栈顶
    cin>>x;
    while(tt&&stk[tt]>=x) tt--;//出栈 从栈顶弹出
    stk[++tt]=x;//入栈
    }
  2. 单调队列

    应用:求滑动窗口最大值最小值

    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字典树

graph TB
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)\):

  1. 将两个集合合并
  2. 询问两个元素是否在一个集合当中

基本原理:每个集合用一棵树来表示,树根的编号就是整个集合的编号,每个节点存储它的父节点,p[x]表示x的父节点

  1. 判断树根:if(p[x]==x)
  2. x的集合编号:while(p[x]!=x) x=p[x];
  3. 合并集合: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)\)

小根堆:父节点的值都小于等于其子节点的值,大根堆:父节点的值都大于等于其子节点的值

graph TB
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))
  1. 插入一个数:heap[++size]=x;up(size);
  2. 求集合中的最小值:heap[1]
  3. 删除最小值:heap[1]=heap[size--];down(1);
  4. 删除任意元素(STL不能):heap[k]=heap[size--];down(k);up(k);
  5. 修改任意元素(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)是一种基于哈希函数实现的快速查找数据结构。通过哈希函数将复杂数据结构映射,实现快速查找和插入

哈希冲突:多个不同元素通过哈希函数计算映射到哈希表中同一个槽

graph LR
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 数据结构的更多相关文章

  1. [从源码学设计]蚂蚁金服SOFARegistry 之 ChangeNotifier

    [从源码学设计]蚂蚁金服SOFARegistry 之 ChangeNotifier 目录 [从源码学设计]蚂蚁金服SOFARegistry 之 ChangeNotifier 0x00 摘要 0x01 ...

  2. [从源码学设计]蚂蚁金服SOFARegistry之配置信息

    [从源码学设计]蚂蚁金服SOFARegistry之配置信息 目录 [从源码学设计]蚂蚁金服SOFARegistry之配置信息 0x00 摘要 0x01 业务范畴 1.1 配置作用 1.2 学习方向 0 ...

  3. Python2 基本数据结构源码解析

    Python2 基本数据结构源码解析 Contents 0x00. Preface 0x01. PyObject 0x01. PyIntObject 0x02. PyFloatObject 0x04. ...

  4. linux内核数据结构学习总结

    目录 . 进程相关数据结构 ) struct task_struct ) struct cred ) struct pid_link ) struct pid ) struct signal_stru ...

  5. MPEG2_TS流基本概念和数据结构

    时对应network_PID,program_number等于其它值时对应program_map_PID. (3)PMT   PMT数据结构如下: TS_program_map_section(){ ...

  6. Java数据结构和算法 - 递归

    三角数字 Q: 什么是三角数字? A: 据说一群在毕达哥拉斯领导下工作的古希腊的数学家,发现了在数学序列1,3,6,10,15,21,……中有一种奇特的联系.这个数列中的第N项是由第N-1项加N得到的 ...

  7. [转]Redis内部数据结构详解-sds

    本文是<Redis内部数据结构详解>系列的第二篇,讲述Redis中使用最多的一个基础数据结构:sds. 不管在哪门编程语言当中,字符串都几乎是使用最多的数据结构.sds正是在Redis中被 ...

  8. 20172302《程序设计与数据结构》实验三 敏捷开发与XP实践报告

    课程:<程序设计与数据结构> 班级: 1723 姓名: 侯泽洋 学号:20172302 实验教师:王志强老师 实验日期:2018年5月16日 必修/选修: 必修 1.实验内容 (1)代码规 ...

  9. Redis学习笔记之Redis中5种数据结构的使用场景介绍

    原来看过 redisbook 这本书,对 redis 的基本功能都已经熟悉了,从上周开始看 redis 的源码.目前目标是吃透 redis 的数据结构.我们都知道,在 redis 中一共有5种数据结构 ...

  10. 探索Redis设计与实现3:Redis内部数据结构详解——sds

    本文转自互联网 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial ...

随机推荐

  1. IDEA神器插件-40款

    IDEA 插件安装 步骤 IDEA里面,选择打开 File --> Settings --> Plugins 在Plugins里面, 可以搜索需要的插件 (下面的标题),然后安装 如下图: ...

  2. consul简介

    Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置. Consul是分布式的.高可用的.可横向扩展的.它具备以下特性 : 服务发现:consul通过DNS或者HTT ...

  3. DynamoDB-键值存储

    什么是DynamoDB? DynamoDB 是一个你什么也不用管的 NoSql 数据库.记得给 AWS付账单就可以. 在2004年的时候, Amazon 发现 Oracle 数据库都不够用了.为了还能 ...

  4. 魔乐开发者社区正式上线,AI开发者快来撩!

    近日,由天翼云与华为联合打造的魔乐(Modelers)开发者社区,在天翼云中国行·贵州站活动现场上线发布.依托全场景开源模型资源.易用的模型开发套件,以及强大的共建共享生态系统等优势,社区将使能开发者 ...

  5. .NET周刊【2月第1期 2025-02-02】

    国内文章 dotnet 9 已知问题 默认开启 CET 导致进程崩溃 https://www.cnblogs.com/lindexi/p/18700406 本文记录 dotnet 9 的一个已知且当前 ...

  6. 在IDEA如何使用JProfiler性能分析

    一.下载地址 https://www.ej-technologies.com/download/jprofiler/files 版本:11 激活码:L-J11-Everyone#speedzodiac ...

  7. [POI2014] HOT-Hotels 加强版题解

    好好好,太好了这题,太好了. 首先有一点是很明显的: 对于一个合法的答案 \((i,j,k)\),必有一点 \(p\),使 \(dis(i,p)=dis(j,p)=dis(k,p)\) 且三点到 \( ...

  8. nginx: [error] open() “/usr/local/var/run/nginx.pid” failed (2: No such file or directory)

  9. thymeleaf的手动渲染HTML模板

    thymeleaf的手动渲染HTML模板 长河 2018-11-14 11:18:10  6833  收藏 2 分类专栏: Springboot 版权 现在很多公司都在thymeleaf作为前端的显示 ...

  10. Java基础常见知识、面试题总结 (上)

    Java语言有哪些特点 简单易学: 面向对象(封装,继承,多态): 平台无关性( Java 虚拟机实现平台无关性): 支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来 ...