算法基础 第3章 数据结构

1.单调栈

1.什么是单调栈
单调栈,即具有单调性的栈。
实现

#include <iostream>
#include <stack>
using namespace std;
const int N = 3e6 + 10;
int a[N], n;
void test1()
{stack<int> st; // 维护⼀个单调递增的栈for(int i = 1; i <= n; i++){// 栈⾥⾯⼤于等于 a[i] 的元素全部出栈while(st.size() && st.top() >= a[i]) st.pop();st.push(a[i]);}
}

2.单调栈解决的问题

  • 寻找当前元素左侧,离它最近,且比它大的元素在哪
  • 寻找当前元素左侧,离它最近,且比它小的元素在哪
  • 寻找当前元素右侧,离它最近,且比它大的元素在哪
  • 寻找当前元素右侧,离它最近,且比它小的元素在哪

虽然是四个问题,但是原理是⼀致的。因此,只要解决⼀个,举⼀反三就可以解决剩下的几个。

3.寻找当前元素左侧,离它最近,并且比它大的元素在哪
从左往右遍历元素,构造⼀个单调递减的栈。插入当前位置的元素的时:
• 如果栈为空,则左侧不存在比当前元素大的元素;
• 如果栈非空,若栈顶元素比当前位置大则栈顶元素为目标元素;若栈顶元素比当前元素小,出栈维护单调递减栈。
注意,因为我们要找的是最终结果的位置。因此,栈里面存的是每个元素的下标。

1.1【模板】单调栈

P5788 【模板】单调栈

2.单调队列

1.什么是单调队列?
单调队列,即存储的元素要么单调递增要么单调递减的队列,这里的队列是个双端队列。
2.单调队列解决的问题
一般用于解决滑动窗口内最大值最小值问题,以及优化动态规划。

2.1 滑动窗口 /【模板】单调队列

P1886 滑动窗口 /【模板】单调队列

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<deque>
//https://www.luogu.com.cn/problem/P1886
using namespace std;
const int N = 1e6 + 10;
int n, k;
int a[N];
//单调队列
int main()
{cin >> n >> k;for (int i = 1; i <= n; ++i)cin >> a[i];//存储下标的双端队列deque<int> q;//找窗口内最小值for (int i = 1; i <= n; ++i){//单调递增的队列while (q.size() && a[i] <= a[q.back()])q.pop_back();q.push_back(i);//判断是否在窗口内,并更新队列if (q.size() && i - q.front() >= k)q.pop_front();//当窗口大小达到k时,取出窗口内的最值if (i >= k)cout << a[q.front()] << ' ';}cout << endl;q.clear();//找窗口内最大值for (int i = 1; i <= n; ++i){//单调递减的队列while (q.size() && a[i] >= a[q.back()])q.pop_back();q.push_back(i);//判断是否在窗口内,并更新队列if (q.size() && i - q.front() >= k)q.pop_front();//当窗口大小达到k时,取出窗口内的最值if (i >= k)cout << a[q.front()] << ' ';}cout << endl;return 0;
}

3.并查集

3.1并查集的概念及实现

3.1.2并查集的概念

并查集(Union Find):是一种用于维护元素所属集合的数据结构,实现为一个森林,其中每棵树表示一个集合,树中的节点表示对应集合中的元素,根节点来代表整个集合。

实现并查集的时,我们⼀般让根节点自己指向自己。
在这里插入图片描述

我们需要维护若干个集合

  • 查询操作:查找x属于哪一个集合
  • 合并操作:将x所在集合和y所在集合合并成一个集合
  • 判断操作:判断xy是否在同一个集合

3.1.2并查集的实现

1.初始化:让元素自己指向自己

for(int i=1;i<=n;i++)fa[i]=i;

2.查询

// 返回父亲节点
int find(int x)
{if(fa[x] == x) return x;//     路径压缩			把被查询的节点到根节点的路径上的所有节点的⽗节点设置为根节点return fa[x] = find(fa[x]);// ⼀⾏实现return fa[x] == x ? x : fa[x] = find(fa[x]);
}

3.合并
将元素 x 所在的集合与元素 y 所在的集合合并成⼀个集合:

  • 让元素 x 所在树的根节点指向元素 y 所在树的根节点。
// 合并操作
void un(int x, int y) // 注意,函数名字不能⽤ union,因为它是 C++ 的关键字
{int fx = find(x);int fy = find(y);// 让元素 x 所在树的根节点指向元素 y 所在树的根节点。fa[fx] = fy;
}

4.判断
判断元素 x 和元素 y 是否在同⼀集合:

  • 看看两者所在树的根节点是否相同。
// 合并操作
// 判断是否在同⼀集合
bool issame(int x, int y)
{return find(x) == find(y);
}

3.2普通并查集

P3367 【模板】并查集

3.3拓展并查集

普通的并查集只能解决各元素之间仅存在⼀种相互关系,比如《亲戚》题目中:
• a 和 b 是亲戚关系, b 和 c 是亲戚关系,这时就可以查找出 a 和 c 也存在亲戚关系。

但如果存在各元素之间存在多种相互关系,普通并查集就无法解决。比如下面的案例:
• a和 b是敌⼈关系, b和 c是敌人关系,但是 a和 c其实不是敌人关系,而是另⼀种朋友关系。
此时,就不仅仅是简单的敌⼈关系,还是出现⼀种朋友关系。
解决这类问题就需要对并查集进行扩展:将每个元素拆分成多个域,每个域代表⼀种状态或者关系。
通过维护这些域之间的关系,来处理复杂的约束条件。

下例中,若1与2是敌人2与3是敌人那么1和3就在同一个集合中,则1与3是朋友
在这里插入图片描述
以下题为例实现:关键在于将敌人的敌人合并
P1892 [BalticOI 2003] 团伙

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
//https://www.luogu.com.cn/problem/P1892
using namespace std;
const int N = 1e3 + 10;
// 拓展并查集int n, m, p, q;
char opt;
int fa[N*2];int find(int x)
{if (fa[x] == x)return x;return fa[x] = find(fa[x]);
}
// 使朋友域元素为父节点
void un(int x, int y)
{int fx = find(x), fy = find(y);fa[fy] = fx;
}
int main()
{cin >> n >> m;for (int i = 1; i <= 2 * n; ++i)fa[i] = i;while (m--){cin >> opt >> p >> q;if (opt == 'F'){un(p, q);}else{//敌人的敌人是朋友un(p, q + n);un(q, p + n);}}int res = 0;for (int i = 1; i <=  n; ++i){if (fa[i] == i)res++;}cout << res << endl;return 0;
}

3.4带权并查集

1.带权并查集的概念
带权并查集在普通并查集的基础上,为每个结点增加了⼀个权值。这个权值可以表示当前结点与父结点之间的关系、距离或其他信息(注意,由于我们有路径压缩操作,所以最终这个权值表示的是当前结点相对于根结点的信息)。有了这样⼀个权值,就可以推断出集合中各个元素之间的相互关系
2.带权并查集的实现
实现⼀个能够查询任意两点之间距离的并查集。实现带权并查集的核心是在进行 FindUnion 操作时,不仅要维护集合的结构,还要维护结点的权值

基本操作:

const int N = 1e5 + 10, INF = 0x3f3f3f3f;
int n;
int fa[N], d[N];
// 初始化
void init()
{for(int i = 1; i <= n; i++){fa[i] = i;d[i] = 0; // 根据题⽬要求来初始化,这里表示到父节点距离}
}// 查询操作
int find(int x)
{if(fa[x] == x) return x;int t = find(fa[x]); // 这句代码⼀定要先执⾏,先缩短路径// 更新到根节点的距离d[x] += d[fa[x]]; // 会根据权值的意义有所改变return fa[x] = t;
}// 合并操作
// x 所在集合与 y 所在集合合并,x 与 y 之间的权值是 w
void un(int x, int y, int w)
{int fx = find(x), fy = find(y);if(fx != fy) // 不在同⼀个集合中{fa[fx] = fy;// 更新到根节点的距离d[fx] = d[y] + w - d[x]; // 会根据权值的意义有所改变}
}

4.字符串哈希

  • 什么是字符串哈希:
    定义⼀个把字符串映射到整数的函数 ,这就是字符串哈希。即将⼀个字符串用一个整数表示。

  • 字符串哈希中的哈希函数:
    在字符串哈希中,有⼀种冲突概率较小的哈希函数,将字符串映射成 p 进制数字:
    hash(s)=∑i=0n−1s[i]×pn−i−1(modM)hash(s) = \sum_{i=0}^{n-1} s[i] \times p^{n-i-1} \pmod{M}hash(s)=i=0n1s[i]×pni1(modM)

  • 前缀哈希数组:
    一般使用前缀和存储每个前缀的哈希值,这样的话每次就能快速求出子串的哈希了。

// 处理前缀哈希数组以及 p 的 i 次⽅数组
void init_hash()
{f[0] = 0; p[0] = 1;for(int i = 1; i <= len; i++){f[i] = f[i - 1] * P + s[i];p[i] = p[i - 1] * P;}
}// 快速求得任意区间的哈希值
ULL get_hash(int l, int r)
{return f[r] - f[l - 1] * p[r - l + 1];
}

将f[i]映射成p进制数,则f[i] = f[i - 1] * P + s[i];
若要求出中间某一段的哈希值,将目标字符串、f[r]、f[l-1]表示出来找出他们之间的关系,表示如下
在这里插入图片描述

例子:
P3370 【模板】字符串哈希

5.Trie树

  1. Trie 树又叫字典树或前缀树,是⼀种能够快速插入查询字符串的数据结构。它利用字符串的公共前缀,将字符串组织成⼀棵树形结构。
    我们可以把字典树想象成⼀棵多叉树,每一条边代表⼀个字符,从根节点到某个节点的路径就代表了一个字符串。例如,要存储 “abc” 、 “abd” 、 “acde” 以及 “cd” 时,构建的字典树如下:
    在这里插入图片描述
  2. 字典树的作用
    查询某个单词是否出现过,并且出现几次;
    查询有多少个单词是以某个字符串为前缀
    查询所有以某个前缀开头的单词;(这个作用可以用到输入法中,输入拼音的时候,可以提示可能的单词)
  3. 字典树的实现
    实现一个能够查询单词出现次数以及查询有多少个单词是以某个字符串为前缀的字典树,默认全是小写字母。
// 字典树        经过这个节点的字符串
int tr[N][62], p[N];
// 新插入节点的编号
int idx;
int t, n, q;
// 查询是哪个字符
int get_num(char ch)
{if (ch >= 'a' && ch <= 'z')return ch - 'a';else if (ch >= 'A' && ch <= 'Z')return ch - 'A' + 26;else return ch - '0' + 52;
}// 插入字典树
void insert(string& s)
{int cur = 0;// 从根结点开始p[cur]++;// 这个格⼦经过⼀次for (auto ch : s){int path = get_num(ch);if (tr[cur][path] == 0)tr[cur][path] = ++idx;// 迭代到下一个路径cur = tr[cur][path];p[cur]++;}e[cur]++;
}
// 查询特定前缀字符串个数
int find_pre(string& s)
{int cur = 0;for (auto ch : s){int path = get_num(ch);if (tr[cur][path] == 0)return 0;cur = tr[cur][path];}// 查询从这个格子经过的所有字符串return p[cur];
}// 查询字符串出现次数
int find(string& s)
{int cur = 0;for(auto ch : s){int path = ch - 'a';if(tree[cur][path] == 0) return 0;cur = tree[cur][path];}return e[cur];
}

例子:
P8306 【模板】字典树

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

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

相关文章

[机器学习]08-基于逻辑回归模型的鸢尾花数据集分类

使用sklearn的LogisticRegression多分类模型程序代码&#xff1a;import numpy as np from sklearn.linear_model import LogisticRegression import matplotlib.pyplot as plt import matplotlib as mpl from sklearn import datasets from sklearn import preprocessing impo…

【STM32入门教程】stm32简介

一、STM32简介二、ARM三、stm32f103c8t6四、命名规则五、系统结构六、引脚定义七、启动配置一般情况下&#xff0c;都是在flash开始程序&#xff0c;而启动程序也可以进行配置在其他地方启动程序&#xff0c;通过配置boot0和boot1来进行配置八、最小系统电路

SAE J2716多协议网关的硬件架构与实时协议转换机制解析

本文解析符合SAE J2716标准的工业级协议转换设备技术架构&#xff0c;通过拆解其四路双向SENT通道与多总线&#xff08;CANFD/Ethernet/USB&#xff09;的实时交互机制、MicroSD独立日志系统设计及模拟量动态映射方案&#xff0c;为汽车电子与工业通信开发者提供可复用的技术参…

VS2022+QT5.15.2+OCCT7.9.1的开发环境搭建流程

以下是VS2022 QT5.15.2 OCCT7.9.1开发环境搭建的完整流程&#xff1a; 一、安装Visual Studio 2022 下载安装程序 访问VS官网下载Community版安装组件 选择"使用C的桌面开发"工作负载勾选&#xff1a; MSVC v143 - VS 2022 C x64/x86生成工具Windows 10 SDK (建议…

数据库访问模式详解

数据库访问模式详解数据库访问模式是软件架构中数据访问层&#xff08;Data Access Layer&#xff09;设计的核心&#xff0c;它定义了应用程序如何与数据库进行交互的策略和方法。选择合适的访问模式对于系统的性能、可维护性、可扩展性、事务一致性和开发效率至关重要。不同的…

BGE向量算法

一、是什么 什么是BGE向量算法&#xff1f;先说说网上的概念吧。本文不讲解太深的算法知识&#xff0c;主要讲解如何用&#xff01; BGE&#xff08;BAAI General Embedding&#xff09;是北京智源研究院开源的“通用语义向量模型”。一句话&#xff1a;把中文或英文句子变成…

AI数据仓库的核心优势解析

内容概要本文旨在全面解析AI数据仓库的核心优势&#xff0c;为读者提供清晰的框架。文章首先从基础定义出发&#xff0c;探讨其如何高效整合多源数据&#xff0c;并支持人工智能与机器学习应用。随后&#xff0c;将详细阐述处理TB级数据的能力&#xff0c;包括兼容结构化和非结…

具身智能Scaling Law缺失:机器人界的“摩尔定律“何时诞生?

8月9日&#xff0c;在世界机器人大会的演讲台上&#xff0c;宇树科技创始人王兴兴谈论到目前机器人运动控制领域存在的RL Scaling Law问题&#xff0c;他认为现在的机器人在学习一项新的技能时&#xff0c;往往都是需要从头开始研究以及教学。而在未来更加希望的是能够在原有的…

【跨越 6G 安全、防御与智能协作:从APT检测到多模态通信再到AI代理语言革命】

跨越 6G 安全、防御与智能协作&#xff1a;从APT检测到多模态通信再到AI代理语言革命引言单篇总结**2. Integrated Multimodal Sensing and Communication: Challenges, Technologies, and Architectures****3. Why do AI agents communicate in human language?**引言 在迈向…

微前端-解决MicroApp微前端内存泄露问题

前言 之前使用京东微前端框架MicroApp集成10个微前端的页面到AngularJs的后台管理系统中&#xff0c;每个微前端做成一个菜单&#xff0c;一共10个&#xff0c;每次打开都是一个新的微前端&#xff0c;但是发现打开的微前端越多&#xff0c;容易造成内存泄露&#xff0c;下面讲…

线性代数 · 向量运算 | 叉乘 / 几何意义 / 推导

注&#xff1a;本文为 “线性代数 向量运算” 相关合辑。 图片清晰度受引文原图所限。 略作重排&#xff0c;未整理去重。 如有内容异常&#xff0c;请看原文。 数学基础 —— 向量运算&#xff08;叉乘&#xff09; keng_s 于 2016-08-05 17:17:57 发布 1_ 向量的叉乘 向量…

方法中只包含查询操作需要添加事务吗?

方法中只包含查询操作需要添加事务吗?绝大部分情况都不需要 是否需要为包含数据库查询操作的方法添加 @Transactional 注解,取决于业务需求和查询操作的特性,不能一概而论。以下是具体分析: 一、不需要添加 @Transactional 的常见场景 如果查询操作满足以下条件,通常不需…

MTK平台Wi-Fi学习--wifi channel 通过国家码进行功率限制和wifi eFEM 基本配置和wifi Tx SEM问题

一. 国家码可以用来限制功率上限,可以针对各国家实现By channel降功率的能力 可以通过country code来设置不同channel的power limit,操作方法如下: 在rlm_txpwr_init.h文件中g_rRlmPowerLimitConfiguration[]下添加需要限制功率的channel, 例如:国家码CN,信道:CH1,po…

MedGemma: 多模态医学文本与图像处理的创新模型

MedGemma: 多模态医学文本与图像处理的创新模型 今天&#xff0c;我有幸参加了在上海举行的Google 2025 I/O大会&#xff0c;这是一场充满创新与突破的技术盛宴。作为全球最具影响力的科技大会之一&#xff0c;Google I/O每年都会吸引来自世界各地的开发者、企业领袖以及科技爱…

深入剖析 C++ STL 中的 std::list 容器

基本介绍在 C 标准库&#xff08;STL&#xff09;中&#xff0c;std::list 是一个基于双向链表实现的序列容器。它与 std::vector、std::deque 等连续存储容器不同&#xff0c;提供了在序列中高效插入和删除元素的能力&#xff0c;尤其是在序列中间位置操作时优势明显。1. std:…

大规模调用淘宝商品详情 API 的分布式请求调度实践

在电商数据分析、比价系统、选品工具等业务场景中&#xff0c;往往需要大规模调用淘宝商品详情 API 以获取商品标题、价格、销量、评价等核心数据。然而&#xff0c;面对淘宝开放平台的严格限流策略、海量商品 ID 的处理需求以及系统高可用要求&#xff0c;传统的单节点调用方式…

在 Windows 系统中解决 Git 推送时出现的 Permission denied (publickey) 错误,请按照以下详细步骤操作:

完整解决方案步骤&#xff1a; 1. 检查并生成 SSH 密钥 # 打开 Git Bash ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 全程按回车&#xff08;使用默认路径&#xff0c;不设密码&#xff09; 密钥将生成在&#xff1a;C:\Users\<用户名>\.ssh\ 目…

【入门级-算法-2、入门算法:枚举法】

枚举法&#xff08;Brute Force&#xff09;&#xff1a;是一种直接遍历所有可能情况的算法思想&#xff0c;适合解决数据范围较小的问题。它的核心是穷举所有可能性&#xff0c;并检查哪些情况符合要求。 枚举法的基本思想&#xff1a;计算机主要功能&#xff0c;或者说它的优…

Python/Node.js 调用taobao API:构建实时商品详情数据采集服务

在电商数据分析、价格监控、竞品分析等场景中&#xff0c;实时获取商品详情数据至关重要。淘宝提供了丰富的 API 接口&#xff0c;允许开发者合法合规地获取商品信息。本文将介绍如何使用 Python 和 Node.js 两种主流语言调用淘宝 API&#xff0c;构建一个实时商品详情数据采集…

【OpenCV】Mat详解

在OpenCV中&#xff0c;cv::Mat是用于存储图像、矩阵等多维数据的核心数据结构&#xff0c;替代了早期的IplImage&#xff08;需手动管理内存&#xff09;&#xff0c;其设计的核心目标是自动内存管理和高效数据操作。下面详细介绍其组成原理及使用方法。 一、cv::Mat的组成原理…