常用算法代码模板 (2) :数据结构

news2024/11/23 8:59:30

AcWing算法基础课笔记与常用算法模板 (2) ——数据结构

常用算法代码模板 (1) :基础算法
常用算法代码模板 (2) :数据结构
常用算法代码模板 (3) :搜索与图论
常用算法代码模板 (4) :数学知识
算法基础课 动态规划模板题笔记
算法基础课 贪心算法模板题笔记
由数据范围反推算法时间复杂度

文章目录

  • 1 链表
    • 1.1 单链表
    • 1.2 双链表
  • 2 栈
  • 3 队列
    • 3.1 非循环队列
    • 3.2 循环队列
  • 4 单调队列
    • 4.1 单调栈
    • 4.2 单调队列
  • 5 KMP
  • 6 Trie树
  • 7 并查集
    • 7.1 朴素并查集
    • 7.2 维护size(集合大小)的并查集
    • 7.3 维护到祖宗节点距离的并查集
  • 8 堆
  • 9 哈希
    • 9.1 一般哈希
      • (1) 拉链法
      • (2) 开放寻址法
    • 9.2 字符串哈希


1 链表

1.1 单链表

int head, e[N], ne[N], idx;
// head为无头单链表的头指针
// e[i]存储结点i的值
// ne[i]指向结点i的后继
// idx为分配给结点的"地址"

/* 初始化 */
head = -1;	// 头指针初始为-1
idx = 0;	// 这里设定第1个插入的结点在0号下标

/* 头插一个数x */
void insert_head(int x) {
    e[idx] = x, ne[idx] = head, head = idx++;	// 后继为开始结点
}

/* 在结点k之后插入一个数x */
void insert(int k, int x) {
    e[idx] = x, ne[idx] = ne[k], ne[k] = idx++;	// 后继为k的后继
}

/* 删除头结点(需保证链表非空) */
void remove_head() {
    head = ne[head];
}

/* 删除结点k之后的结点 */
void remove(int k) {
    ne[k] = ne[ne[k]]
}

/* 遍历整条链表 */
for (int i = head; ~i; i = ne[i]) {
    int u = e[i];
    ...
}

1.2 双链表

int e[N], l[N], r[N], idx;
// l[i]、r[i]分别指向结点i的前驱、后继
// 特殊规定:0是头结点/左端点,只有后继;1是尾结点/右端点,只有前驱

/* 初始化 */
r[0] = 1, l[1] = 0;	// 左右端点分别指向对方
idx = 2;			// 第1个结点从下标2开始存储

/* 在结点k的右边插入一个数x */
void insert_r(int k, int x) {
    e[idx] = x;
    l[idx] = k, r[idx] = r[k];		// 前驱为k,后继为k的后继
    l[r[k]] = idx, r[k] = idx++;	// k后继的前驱、k的后继(须最后修改)即为该结点
}

/* 在结点k的左边插入一个数x */
void insert_l(int k, int x) {
    insert_r(l[k], x);	// 等价于在结点k的前驱(l[k])的右边插入
}

/* 删除结点k */
void remove(int k) {
    l[r[k]] = l[k];
    r[l[k]] = r[k];
}

/* 遍历整条链表 */
for (int i = r[0]; i != 1; i = r[i]) {
    int u = e[i];
    ...
}

2 栈

int stk[N], tt = -1;
// stk[0 ... N-1]
// 栈顶指针tt初始化为-1

/* 栈顶入栈一个数 */
stk[++tt] = x;

/* 栈顶出栈一个数 */
tt--;

/* 获取栈顶的值 */
stk[tt];

/* 判断栈是否为空 */
if (tt != -1) ...

3 队列

3.1 非循环队列

int q[N], hh = 0, tt = -1;
// q[0 ... N-1]
// 队头初始为0, 队尾初始和栈顶一样为-1

/* 队尾入队一个数 */
q[++tt] = x;

/* 队头出队一个数 */
hh++;

/* 队头、队尾的值 */
q[hh];
q[tt];

/* 判断队列是否为空 */
if (hh <= tt) ...

3.2 循环队列

int q[N], hh = 0, tt = 0;
// q[0 ... N-1]
// 队头和队尾指针初始均为0

/* 队尾入队一个数 */
q[tt++] = x;
if (tt == N) tt = 0;

/* 队头出队一个数 */
hh++;
if (hh == N) hh = 0;

/* 队头、队尾的值 */
q[hh];
q[tt];

/* 判断队列是否为空 */
if (hh != tt) ...

4 单调队列

核心思想:及时弹出必不会作为答案的数,使得容器内各所指元素始终单调

4.1 单调栈

常见模型:找出数列中每个数左边离它最近的比它大/小的数

/* 示例:输出数组a[n]中每个数左边离它最近的比它小的数,不存在则输出-1 */
int n, a[N];			// a[1 ... n]
int stk[N], tt = -1;	// 栈中存储数组元素下标

// 思想:若a[x] >= a[y] (x < y),则a[x]必不会是任何一数的答案,可直接剔除
for (int i = 0; i < n; i++) {	// 双指针算法,i是子区间右端点
    while (tt != -1 && a[stk[tt]] >= a[i]) tt--;	// 弹出既大又"远"的数
  
    if (tt != -1) printf("%d ", a[stk[tt]]);
    else printf("-1 ");
  
    stk[++tt] = i;
}

4.2 单调队列

常见模型:找出滑动窗口中的最大值/最小值

/* 示例:输出滑动窗口每次前移时窗口内的最小值 */
int n, a[N];				// a[1 ... n]
int k;						// 滑动窗口的长度
int q[N], hh = 0, tt = -1;	// 队列(双端队列)中存储数组元素下标

// 思想:同单调栈,且应输出的最值在队头(单调)
for (int i = 0; i < n; i++) {	// 双指针算法,i是子区间右端点
    while (hh <= tt && i - k + 1 > q[hh]) hh++;	// 判断队头是否滑出窗口
    while (hh <= tt && a[q[tt]] >= a[i]) tt--;	// 同单调栈,队尾弹出既大又"远"的数
  
    q[++tt] = i;	// 先将i从队尾入队
  
    if (i + 1 >= k) printf("%d ", a[q[hh]]);	// 当窗口长度达到要求时才输出
}

5 KMP

next数组 next [ i ] = i \text{next}[i]=i next[i]=i为终点的最大公共前后缀(此处规定包含 i i i )的长度

char s[N], p[N];	// 主串s[1 ... n]与模式串p[1 ... m](必须从下标1开始存储字符)
int n, m;			// 主串长度为n,模式串长度为m
int ne[N];			// next数组

/* 从下标1读取串至字符数组 */
scanf("%s", s + 1);

/* 求模式串p的next数组:p对p自己作KMP匹配 */
for (int i = 2, j = 0; i <= m; i++) {	// ne[1]=0,故i从2开始遍历
    while (j && p[i] != p[j + 1]) j = ne[j];
    if (p[i] == p[j + 1]) j++;
  
    ne[i] = j;	// 匹配成功则表明得到了以当前i为终点的最大公共前后缀的长度
}

/* KMP匹配 */
for (int i = 1, j = 0; i <= n; i++) {	// 始终与j的下一位(j+1)作匹配
    while (j && s[i] != p[j + 1]) j = ne[j];	// 若j未退回起点且i与j的下一位不匹配,则j回溯
    if (s[i] == p[j + 1]) j++;	// 若i与j的下一位匹配,则j走至下一位
  
    if (j == m) {   // 匹配成功的操作
        ...
        j = ne[j];	// 若要求找到所有匹配点,则j继续回溯、匹配
    }
}

6 Trie树

字典树(Retrieval Tree, Trie Tree)用于高效存储和查找字符串集合。

AC自动机为追加了fail指针的Trie树,算法思想参考KMP。

在这里插入图片描述

int son[N][26], cnt[N], idx;
// son[p][u]记录结点p的第u个子结点(26表示26个字母)
// cnt[p]存储以结点p结尾的单词数量
// idx初始为0(0号点既是根结点,又是空结点,故创建结点时为++idx)

/* 插入一个字符串 */
void insert(char str[]) {
    int p = 0;	// 从根结点0开始遍历Trie的指针
    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]++;	// p最终指向字符串末尾字母,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;	// 不存在则直接返回0
        p = son[p][u];
    }
    return cnt[p];	// p最终指向字符串末尾字母,返回数量
}

7 并查集

7.1 朴素并查集

int n;		// [1 ... n]
int p[N];	// p[i]存储结点i的父结点(路径压缩后则为祖宗结点),祖宗结点的父结点为其自身

/* 初始化 */
for (int i = 1; i <= n; i++) p[i] = i;

/* 并查集核心操作:返回结点x的祖宗结点,路径压缩 */
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

/* 判断结点a和b是否在同一集合 */
if (find(a) == find(b)) ...

/* 合并结点a和b所在集合:将a并至b */
p[find(a)] = find(b);	// 将a祖宗作为b祖宗的儿子

7.2 维护size(集合大小)的并查集

"size"在iostream头文件已被占用,故集合大小变量名改用cnt

int n;
int p[N], cnt[N];	// cnt[i]存储祖宗结点i的集合的大小(仅祖宗结点的cnt有意义)

/* 初始化 */
for (int i = 1; i <= n; i++) {
    p[i] = i;
    cnt[i] = 1;	// 初始化各结点所属集合大小为1
}

/* 并查集核心操作:返回结点x的祖宗结点,路径压缩 */
int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

/* 判断结点a和b是否在同一集合 */
if (find(a) == find(b)) ...

/* 结点a所属集合的大小 */
cnt[find(a)];

/* 合并结点a和b所在集合:将a并至b */
if (find(a) == find(b)) continue;	// 若已在同一集合内了则跳过
cnt[find(b)] += cnt[find(a)];	// 需将a所属集合的大小加至b
p[find(a)] = find(b);

7.3 维护到祖宗节点距离的并查集

int n;
int p[N], d[N];		// d[i]存储结点i到其祖宗p[i]的距离

/* 初始化 */
for (int i = 1; i <= n; i++) {
    p[i] = i;
    d[i] = 0;	// 初始化全为0
}

/* 并查集核心操作:返回结点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];
}

/* 判断结点a和b是否在同一集合 */
if (find(a) == find(b)) ...
  
/* 合并结点a和b所在集合:将a并至b */
p[find(a)] = find(b);
d[find(a)] = distance;	// 根据具体问题,初始化find(a)的偏移量

8 堆

int h[N], cnt;
int ph[N], hp[N], idx;
// h[1 ... cnt]为小根堆,h[1]为堆顶/最小值,结点u的左右儿子(若存在)分别为2u、2u+1
// ph[k]映射插入序列中第k个点到堆中的下标u (Position-Heap)
// hp[u]映射堆中结点u到插入序列中的序号k (Heap-Position)

/* 交换堆中两个结点a和b的所有信息 */
void heap_swap(int a, int b) {
    swap(ph[hp[a]], ph[hp[b]]);
    swap(hp[a], hp[b]);
    swap(h[a], h[b]);
}

/* 向下调整:一路向下交换结点u与其较小的儿子 */
void down(int u) {
    int t = u;	// 记录u、2u、2u+1三个结点中的最小值结点编号
    if (2 * u <= cnt && h[2 * u] < h[t]) t = 2 * u;
    if (2 * u + 1 <= cnt && h[2 * u + 1] < h[t]) t = 2 * u + 1;
  
    if (t != u) {
        heap_swap(u, t);
        down(t);
    }
}

/* 向上调整:一路向上交换结点u与其父结点 */
void up(int u) {
    while (u / 2 && h[u / 2] > h[u]) {
        heap_swap(u / 2, u);
        u >>= 1;
    }
}

/* 插入一个数x */
void insert(int x) {
    cnt++, idx++;
    ph[idx] = cnt, hp[cnt] = idx;
    h[cnt] = x;
    up(cnt);
}

/* 删除最小值/堆顶元素 */
void remove_top() {
    heap_swap(1, cnt);
    cnt--;
    down(1);
}

/* 删除第k个插入的元素 */
void remove(int k) {
    int u = ph[k];
    heap_swap(u, cnt);
    cnt--;
    up(u), down(u);	// 只会执行其中一个
}

/* 修改第k个插入的元素为x */
void change(int k, int x) {
    int u= ph[k];
    h[u] = x;
    up(u), down(u);	// 只会执行其中一个
}

9 哈希

9.1 一般哈希

N最好取质数,使得冲突概率尽可能低

若要删除,则可额外开bool数组标记各地址元素状态来表示是否被删

(1) 拉链法

int h[N], e[N], ne[N], idx;

/* 链表初始化 */
memset(h, -1, sizeof h);

/* 向哈希表中插入一个数 */
void insert(int x) {
    int t = (x % N + N) % N;	// C++的负数取余运算:(-n) mod k = -(n mod k)
    e[idx] = x, ne[idx] = h[t], h[t] = idx++;	// 将x头插在链表h[t]
}

/* 在哈希表中查询某个数是否存在 */
bool find(int x) {
    int t = (x % N + N) % N;
    for (int i = h[t]; ~i; i = ne[i])	// 遍历整条链表h[t]
        if (e[i] == x) return true;
  
    return false;
}

(2) 开放寻址法

数组长度应开到最大数据量的2~3倍

const int INF = 0x3f3f3f3f;		// 表示该哈希值的元素不在哈希表内

int h[N];

/* 哈希表初始化 */
memset(h, 0x3f, sizeof h);		// 初始化为无穷

/* 若x在哈希表中,返回x的下标;否则返回x应该插入的位置*/
int find(int x) {
    int t = (x % N + N) % N;
    while (h[t] != INF && h[t] != x) {	// 若已存在该哈希值的元素且该元素不等于x
        t++;
        if (t == N) t = 0;
    }
    return t;
}

9.2 字符串哈希

字符串前缀哈希法:快速判断两段字符串是否相等(不考虑冲突)

  • 核心思想:将字符串看成 P P P 进制数, P P P 的经验值是 131 131 131 13331 13331 13331 ,取这两个值的冲突概率极低
  • 小技巧:取模的数用 2 64 2^{64} 264,这样直接用unsigned long long存储,溢出的结果就是取模的结果
typedef unsigned long long ULL;

const int 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;	// 溢出则相当于对2^64取模
}

/* 计算子串str[l...r]的哈希值 */
ULL get(int l, int r) {
    return h[r] - h[l - 1] * p[r - l + 1];
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1638404.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

857.雇佣K名工人的最低成本

题目说的其实是有点乱的,所以我们可能抓不住重点,甚至都不太清楚规则,比如 eg. quality[3,1,10,10,1] wage[4,8,200,200,7] 这里是选下标0,1,4 ->单价为8 但是想清楚其实就很easy. 就是 贪心(sort) 优先队列 梳理下我们发现其实要让每个人得到最低期望,就要按照当前最贵…

项目管理-高级项目管理

1.高级项目管理--主要内容 高级项目管理&#xff0c;以下主要从5方面介绍&#xff1a;项目集管理、项目组合管理、组织级项目管理OPM、量化组织管理、项目管理实践模型。 2.具体内容 2.1项目集管理 项目管理绩效域&#xff1a; 包括项目集战略一致性、项目集效益管理、项目集干…

ip地址与硬件地址的区别是什么

在数字世界的浩瀚海洋中&#xff0c;每一台联网的设备都需要一个独特的标识来确保信息的准确传输。这些标识&#xff0c;我们通常称之为IP地址和硬件地址。虽然它们都是用来识别网络设备的&#xff0c;但各自扮演的角色和所处的层次却大相径庭。虎观代理小二将带您深入了解IP地…

【记录】Springboot项目集成docker实现一键部署

公司管理平台完成后&#xff0c;为了方便其他不懂开发的同事部署和测试&#xff0c;集成docker进行一键部署&#xff0c;也为后面自动化部署做准备。本文做个简单记录。 1、安装docker yum install https://download.docker.com/linux/fedora/30/x86_64/stable/Packages/cont…

剃齿和磨齿工艺比较

众所周知&#xff0c;剃齿加工和磨削加工是两种不同的齿轮精加工方法。剃齿是在热处理前进行的&#xff08;这也是剃齿加工受限的原因&#xff09;&#xff0c;而磨齿是在热处理之后进行的。近几年来&#xff0c;随着机械加工精度的不断提高、数控机床的不断完善以及加工软件的…

软件项目总体测试方案

测试目标&#xff1a;确保项目的需求分析说明书中的所有功能需求都已实现&#xff0c;且能正常运行&#xff1b;确保项目的业务流程符合用户和产品设计要求&#xff1b;确保项目的界面美观、风格一致、易学习、易操作、易理解。 软件全套文档过去进主页。 一、 前言 &#x…

AngularJS 的生命周期和基础语法

AngularJS 的生命周期和基础语法 文章目录 AngularJS 的生命周期和基础语法1. 使用步骤2. 生命周期钩子函数3. 点击事件4. if 语句1. if 形式2. if else 形式 5. for 语句6. switch 语句7. 双向数据绑定 1. 使用步骤 // 1. 要使用哪个钩子函数&#xff0c;就先引入 import { O…

【跟我学RISC-V】(一)认识RISC-V指令集并搭建实验环境

写在前面 现在计算机的体系架构正是发展得如火如荼的时候&#xff0c;占领桌面端市场的x86架构、占领移动端市场的arm架构、在服务器市场仍有一定地位的mips架构、国产自研的指令集loongarch架构、还有我现在要讲到的新型开源开放的RISC-V指令集架构。 我先说一说我的学习经历…

区块链 | IPFS 工作原理入门

&#x1f98a;原文&#xff1a;What is the InterPlanetary File System (IPFS), and how does it work? &#x1f98a;写在前面&#xff1a;本文属于搬运博客&#xff0c;自己留存学习。 1 去中心化互联网 尽管万维网是一个全球性的网络&#xff0c;但在数据存储方面&#…

LeetCode热题100:双指针

283.移动零 题目链接&#xff1a;移动零 题目描述&#xff1a;给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 解题思路&#xff1a; 创建两…

三种滤波(EKF、UKF、CKF)的对比,含MATLAB源代码

使用MATLAB模拟三维的滤波,包含扩展卡尔曼滤波EKF、无迹卡尔曼滤波UKF、容积卡尔曼滤波CKF。 状态更新和观测更新均为非线性的,模拟一定强度的机动性,可用于卡尔曼滤波方法的对比学习,自己修改成需要的运动模型后,可以用于组合导航(GPS+DVL形式)。 运行结果 真值的三轴…

c#word文档:3.向Word文档中插入表格/4.读取Word文档中表格

--向Word文档中插入表格-- &#xff08;1&#xff09;在OfficeOperator项目的WordOperator类中定义向Word文档插入换页的函数NewPage &#xff08;2&#xff09;在WordOperator类中定义向Word文档插入表格的函数InsertTable using Microsoft.Office.Interop.Word;// 引入Mic…

不小心格式化固态硬盘之后,数据能恢复吗?小白实测

前言 这段时间突然想到一件事情&#xff1a;固态硬盘一旦坏了&#xff0c;数据恢复的可能性基本上是0。 这件事情是小白自己亲身经历的&#xff0c;所以使用固态硬盘的小伙伴一定要注意数据备份&#xff0c;至少备一份到机械硬盘里&#xff0c;还会稍微可靠一些。 这几天小白…

node.js的常用内置库(1)共128节

我说假如node.js 一路杀出&#xff0c;成为一种后端主要选型的时候&#xff0c;你再次去了解晚么&#xff0c;不晚&#xff0c;但给你的时间肯定不多&#xff5e;&#x1f604; 跟着我一起开始认识node吧&#xff0c;今日份不多3个API &#xff0c;加油 1.assert 在 Node.js 环…

水稻病害检测(YOLO数据集,多分类,稻瘟病、纹枯病、褐斑病、枯心病、霜霉病、水稻细菌性条纹斑病、稻苞虫)

是自己利用LabelImg工具进行手工标注&#xff0c;数据集制作不易&#xff0c;请尊重版权&#xff08;稻瘟病、纹枯病、褐斑病、枯心病、霜霉病、水稻细菌性条纹斑病、稻苞虫&#xff09; 如果需要yolv8检测模型和数据集放在一起的压缩包&#xff0c;可以关注&#xff1a;最新最…

Pandas入门篇(三)-------数据可视化篇3(seaborn篇)(pandas完结撒花!!!)

目录 概述一、语法二、常用单变量绘图1. 直方图&#xff08;histplot&#xff09;2. 核密度预估图&#xff08;kdeplot&#xff09;3. 计数柱状图&#xff08;countplot&#xff09; 三、常用多变量绘图1.散点图(1) scatterplot(2)regplot 散点图拟合回归线(3)jointplot 散点图…

DVWA 靶场命令注入通关解析

介绍 命令注入&#xff08;Command Injection&#xff09;是一种常见的安全漏洞&#xff0c;它允许攻击者通过在应用程序中执行恶意命令来获取系统权限或执行非授权操作。 命令注入通常发生在需要将用户输入作为命令执行的地方&#xff0c;例如Web应用程序的输入框、参数传递…

【C++容器map】map的相关用法

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; C进阶 &#x1f389;其它专栏&#xff1a; C初阶 | 初阶数据结构 | Linux 本篇文章主要讲解 C容器之map相关用法 的相关内容 文章目录 1. map的介绍2. map的使用<font size5 color #…

Word域代码学习(简单使用)-【SEQ】

Word域代码学习(简单使用)-【SEQ】 快捷键 序号快捷键操作1 Ctrl F9 插入域代码花括号2 F9 显示域代码结果3 Shift F9 切换为域代码4 Windows Alt F9 切换全部域代码 域代码说明 域代码不区分大小写在word中&#xff0c;依次选择插入➡文档部件➡域即可选择插入…

WordPress Automatic插件 SQL注入漏洞复现(CVE-2024-27956)

0x01 产品简介 WordPress Automatic(又称为WP Automatic)是一款流行的WordPress插件,旨在帮助网站管理员自动化内容创建和发布。该插件可以从各种来源(如RSS Feeds、社交媒体、视频网站、新闻网站等)获取内容,并将其自动发布到WordPress网站。 0x02 漏洞概述 WordPres…