算法--数据结构基础

news2024/11/14 10:24:38

文章目录

  • 数据结构
    • 单链表
      • 表达式求值
        • 前缀表达式
        • 中缀表达式
        • 后缀表达式
    • 队列
    • 单调栈
    • 单调队列
    • KMP
    • Trie
    • 并查集
    • 哈希表
      • 字符串哈希

数据结构

单链表

用数组模拟(静态链表)效率比定义Node类(动态链表)效率高些
在这里插入图片描述

使用数组模拟单链表,e [ ] 数组中存值,ne [ ] 数组中存下个元素位置下标,定义头指针head,初始时指向-1,定义idx表示用到了哪个下标

定义数组 stk[ ] tt指向栈顶初始为-1,插入时tt++,弹出时tt- - ,查看栈是否为空,只用看tt是否大于0即可,栈顶元素即stk[tt]

表达式求值

前缀表达式

运算符位于操作数之前,求值过程:

  1. 从右向左读取表达式
  2. 将遇到的数字压入栈中,读到运算符是弹出栈顶操作数并进行计算
中缀表达式

常见的数学表达式,运算符位于操作数之间

后缀表达式

也被称为逆波兰表达式,运算符位于操作数之后,后缀表达式不需要括号,不存在优先级问题

求值过程与前缀表达式相同,不过是从左向右读取

后缀表达式在计算机科学中有广泛的应用,特别是在编译器设计、计算器实现和栈的应用中。它可以方便地用于计算复杂的算术表达式,并且可以通过简单的迭代和栈操作来实现。可以将中缀表达式转换为后缀表达式,使其更适合计算机程序中的求值过程。

队列

定义数组q[ ] ,hh为头下标初始为0,tt为尾,初始为-1,尾部添加元素,添加时,q[++tt] = x,头部删除,删除hh++即可

查看队列是否为空,只用看hh<=tt,如果是,不为空,不是就为空,查看队头元素只用看q[hh]

单调栈

情景:给一个序列,找到一个数左边(右边)满足xx条件,且离他最近的一个数

例如:给一个序列,找到每个数左边离他最近的且比他小的数,不存在的话返回-1

  • 暴力:双层for循环,一层逐个遍历,一层从遍历的位置的前一个开始倒着遍历,直到找到比他小的

  • 单调栈思路:
    在读数据的同时维护一个栈,如果栈不为空,就比较栈顶元素和当前要加入的元素的大小,如果大于或等于当前元素,就将栈顶元素弹出,直到新的栈顶元素比当前元素小,就停止循环弹出栈顶元素,如果此时栈不为空,那么栈顶元素即答案,栈为空答案为-1,最后将当前元素入栈

    这样维护的栈一定是单调的

单调队列

情景:求滑动窗口中的最大值和最小值

例如:给定一串数字,有个大小为k的滑动窗口,从左边移到右边,求出每个位置的滑动窗口的最大值和最小值

  • 普通队列:维护一个队列,当窗口向右走一步,就将新的元素添加进队尾并删掉队头,暴力求窗口中的最大和最小值即可
  • 单调队列:
    队列中存元素索引,遍历整个数组,先判断队头元素是否已经被移除窗口,如果是,将队头元素从队列中移除,
    获取窗口最小值:判断队尾元素与当前元素大小,若队尾元素大于当前元素,删除队尾元素,直到队尾元素小于当前元素,再将当前元素添加到队尾,然后判断当前遍历的元素是否达到窗口大小,达到就输出队头(窗口最小值)即可
    获取窗口最大值:和上面一样,只是判断队尾元素与当前元素大小相反

队尾添加元素,添加的过程中保证目前的结果一定在队头,队头取结果即可

KMP

习惯下标从1开始

对模板串处理:对每个点预处理以某点为终点的后缀和前缀相等,相等的长度最大为多少

next[ i ] = j 以 i 为终点的后缀,和从一开始的前缀相等,而且后缀长度最长 ,记录的是最长公共前后缀长度

next[i] = j;
p[1,j] = p[i - j + 1];

Trie

用于高效存储查找字符串

模版:

解释:[0] [1] = 3 表示根节点有个儿子 b ,这个儿子在数组中的下标是3

​ [3] [4] = 7 [3] 表示当前字符的下标,[4] 表示当前字符有个儿子e,下标为7

public class Main{
    final static int N = 100010;
    static int[][]son = new int[N][26]; //这里总共有26个字符
    static int[]cnt = new int[N]; //以某个下标的字符为结尾的字符串个数
    static int idx = 0; //表示下标,自增来生成下标
    
    public static void insert(char[]str) {
        int p = 0;
        for(int i = 0;i < str.length;i++) {
            int u = str[i] - 'a';
            if(son[p][u] == 0) son[p][u] = ++idx;
            p = son[p][u];
        }
        cnt[p]++;
    }
    
    public static int query(char[]str) {
        int p = 0;
        for(int i = 0;i < str.length;i++) {
            int u = str[i] - 'a';
            if(son[p][u] == 0) return 0;
            p = son[p][u];
        }
        return cnt[p];
    }
}

并查集

用来快速处理 近乎O1

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

实现方式:每个集合用一棵树来表示,树根的编号就是整个集合的编号,每个节点存储他的父节点,p[x] 表示x的父节点

a1:如何判断树根 :p[x] == x

a2:如何求x的集合编号:while(p[x] != x) x = p[x] (一直向上找他的父节点,直到找到了根)

a3:如何合并两集合:把其中一个集合的集合编号等于另一棵树的集合编号

求集合编号时时间复杂度和树高是成正比的,可能会出现树高过高问题,需要优化
**优化:**路径压缩

在从一个节点不断向上找到根节点时,将走过的所有节点直接指向根节点

模版:

public class Main{
    final static int N = 100010;
    static int[]p = new int[N];
    //找x的根节点 + 路径压缩
    static int find(int x) {
        if(x != p[x]) p[x] = find(p[x]);
        return p[x];
    }
    
    public static void main(String[]args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        for(int i = 0;i < n;i++) p[i] = i;//初始化
        int m = sc.nextInt();
        for(int i = 0;i < m;i++) {
            String l = sc.next();
            int a = sc.nextInt();
            int b = sc.nextInt();
            if("M".equals(l)) p[find(a)] = find(b);//合并两集合
            else {
                if(find(a) == find(b)) System.out.println("Yes");
                else System.out.println("No");
            }
        }
    }
}

有些题往往还需要维护别的变量

完全二叉树,最后一层节点从左到右依次排列

用数组存储堆

这里索引从1开始,左儿子为 2x 右儿子为2x + 1 父节点为 x / 2

以最小堆为例,修改根节点元素,需要将新的值下沉,就让根元素和左右孩子比较,与最小的那个交换,一直到没法移了

上浮:和父节点比较,如果小于父节点,和父节点交换位置

插入:在heap[++size] = x,再将这个数不断上浮

删除根节点 :用堆的最后一个元素覆盖堆的根节点,在将其不断下沉

删除任意一个元素:heap[k] = heap[size] ; size - - ; up(k) || down(k);

修改任意一个元素:heap[k] = x; down(k) || up(k);

将数组转化成堆:

  • 可以一个一个往堆里add,复杂度为nlogn
  • 也可以从n/2 个元素开始倒着到1不断下沉操作

要修改第k个插入的元素,还需要存个映射关系:

  • 第k个插入的元素的索引 ph[ ]
  • 索引为x的元素是第几个插入的 hp[ ]

交换堆中元素时,也需要考虑到映射关系的改变:

public static void heapSwap(int x,int y) {
    swap(ph,hp[x],hp[y]);
    swap(hp,x,y);
    swap(a,x,y);
}

交换数组中的元素:

public static void swap(int[]q,int a,int b) {
    int t = q[a];
    q[a] = q[b];
    q[b] = t;
}

哈希表

离散化是一种保序的hash方式(只是其中一种)

情景:把0~10^9映射到 0 ~ 10^5 这些数

  • 存储结构
    • 开放寻址法
    • 拉链法
  • 字符串哈希方式

a1:hash函数一般怎么写

x mod 10^5(取模的这个数尽可能是质数,且离2整次幂尽可能远)

a2:处理冲突

  • 开放寻址法
  • 拉链法:将发生冲突的直接接在要插入的位置

在算法中,对哈希表一般只有添加和查找两个操作

算法中,对哈希表就算要删除,往往不会真的删,会再开一个数组,对每个位置打一个标记,标记一下被删除

拉链法:

import java.util.Scanner;
import java.util.Arrays;
public class Main{
    final static int N = 100003;
    static int[]h = new int[N];//哈希表的槽
    static int[]e = new int[N];//链表存值
    static int[]ne = new int[N];//链表存下一个元素位置
    static int idx;//链表当前用到的索引
    static int hash(int x) {
        return (x%N+N) % N;//计算hash值,即该存入位置索引,这样写目的是防止负数出现
    }
    //头插
    static void insert(int x) {
         int k = hash(x);
         e[idx] = x;
         ne[idx] = h[k]; //h[k]就是每个链表的头指针
         h[k] = idx++;
    }
    static boolean find(int x) {
        int k = hash(x);
        for(int i = h[k];i != -1;i = ne[i]) {
            if(e[i] == x) return true;
        }
        return false;
    }
    public static void main(String[]args) {
        Scanner sc = new Scanner(System.in);
        Arrays.fill(h,-1);//相当于初始化每条链表头结点为-1
        int n = sc.nextInt();
        for(int i = 0;i < n;i++) {
            String l = sc.next();
            int x = sc.nextInt();
            if(l.equals("I")) insert(x);
            else {
                if(find(x)) System.out.println("Yes");
                else System.out.println("No");
            }
        }
    }
}

开放寻址法:

只用一个一维数组,数组长度一般是题目要求的2~3倍(经验值)

添加:

先用hash得到该存入的索引,若该索引已有元素,依次找下一个位置,直到找到空的位置,将元素插入

查找:

用hash得到对应索引,若对应索引元素不是要查找的元素,依次往后找,直到找到空的位置,那么这个元素不存在

删除:

先查找x,然后对x打一个标记,表示他被删除了

0x3f3f3f3f的十进制为1061109567,和INT_MAX一个数量级,即10^9 数量级,而一般场合下的数据都是小于10^9的。
0x3f3f3f3f * 2 = 2122219134,无穷大相加依然不会溢出。

public class Main{
    static final int nem = 0x3f3f3f3f;//定义一个数据范围之外的数,表示当前位置为空
    static final int N = 200003;
    static int[]h = new int[N];
    static int hash(int x) {
        return (x%N+N) % N;
    }
    //核心
    //如果是添加,返回的就是该添加的位置,如果是查找,返回位置要么就是这个元素的位置,要么为空位置
    static int find(int x) {
        int k = hash(x);
        while(h[k] != nem && h[k] != x) {
            k++;
            if(k == N) k = 0;
        }
        return k;
    }
    public static void main(String[]args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine());
        Arrays.fill(h,nem);
        while(n --> 0) {
            String[]rr = br.readLine().split(" ");
            int x = Integer.parseInt(rr[1]);
            int we = find(x);
            if(rr[0].equals("I")) {
                h[we] = x;
            }else {
                if(h[we] == x) System.out.println("Yes");
                else System.out.println("No");
            }
        }
    }
}

字符串哈希

当我们需要快速判断两个字符串是否相等时,可以使用

字符串前缀哈希法:先预处理出字符串每个前缀的hash值

在这里插入图片描述

如何求字符串的hash值:

  • p进制法:
    对于“ABCD”,使用p进制表示,可以表示成,(A * p^3 + B * p^2 + C * p^1 + D * p^0)mod Q,其中,将A映射成1,B - - > 2,C - - > 3,D - - > 4
    结果可能比较大,故给他模上一个Q,使结果在 0 ~ Q - 1的范围内
    • 不能映射成0,如果A映射成0,那么AA也是0,AAA也是0……,可以将他们映射成对应的ASII值
    • 当p取131或13331,Q取2^64(经验值),在这种情况下,我们可以不考虑冲突

我们可以利用预先求得的hash值,可以根据公式求得所有子串的hash值。

例如:

aabbaabb,要求3 ~ 7(L ~ R)位置的子串和hash值,即bbaab,需要知道hash[2] (aa) 和 hash[7] (aabbaab),转化为p进制就是 (11)p和(1122112)p,要求bbaab的hash值,就是求(22112)p

可以将(11)p左移成(1100000)p,即左移R - L + 1位(位运算理解),即乘以 p^R - L + 1

要求子串的hash值就可以表示为 hash[R] - hash[L - 1] * p^R - L + 1

示例:

public class Main{
    static final int N = 100010;
    static int[]h = new int[N];//预处理的前缀子串hash值
    static int[]p = new int[N];//p[i]表示p的i次方,将p的i次方预先算出来存到数组中
    static final int P = 131;
    public static void main(String[]args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[]ro1 = br.readLine().split(" ");
        int n = Integer.parseInt(ro1[0]);
        int m = Integer.parseInt(ro1[1]);
        String s = br.readLine();
        p[0] = 1;
        for(int i = 1;i <= n;i++) {
            h[i] = h[i - 1] * P + s.charAt(i - 1);//求前缀子串hash值
            p[i] = p[i - 1] * P;
        }
        while(m --> 0) {
            String[]ro2 = br.readLine().split(" ");
            int l1 = Integer.parseInt(ro2[0]);
            int r1 = Integer.parseInt(ro2[1]);
            int l2 = Integer.parseInt(ro2[2]);
            int r2 = Integer.parseInt(ro2[3]);
            if(h[r1] - h[l1 - 1] * p[r1 - l1 + 1] == h[r2] - h[l2 - 1] * p[r2 - l2 + 1]) System.out.println("Yes");
            else System.out.println("No");
        }
    }
}

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

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

相关文章

DBNet文本检测网络 (FPN、batch normalization、Transpose conv)

DB Net文本检测网络概述 DBNet论文地址&#xff1a;https://arxiv.org/pdf/1911.08947.pdf DBNet是一种基于分割的文本检测网络&#xff0c;使用分割网络提供自适应的thresh用于二值化。 原始二值化方法和DBNet中的动态阈值 传统的基于分割的检测方法&#xff0c;对于分割后的…

C++第一讲之初入C++

注&#xff1a;本文是对于学完C语言再学C同学的讲解&#xff0c;主要补充C与C语言不同之处&#xff0c;如果你没学过C语言&#xff0c;不建议观看本文。 一.C简介 我们都知道C语言是过程性语言&#xff08;强调的是实现过程&#xff09;&#xff0c;即对计算机语言要处理的两…

【持续更新】汇总了一份前端领域必看面试题

文章目录 1. 写在前面2. 前端面试汇总2.0.1. 如何提⾼webpack的打包速度2.0.2. 数组去重2.0.3. 前端有几种缓存方式&#xff1f;2.0.4. nextTick描述一下&#xff1f;2.0.5. Webpack层面的优化&#xff1f;2.0.6. 代码层面的优化&#xff1f;2.0.7. Web 技术的优化&#xff1f;…

ESP32 - Thonny+MicroPython+ESP32 继电器的使用

ESP32 - ThonnyMicroPythonESP32 继电器的使用 认真理解&#xff0c;能看懂 继电器默认为断开 from machine import Pin p13 Pin(13, Pin.OUT) p13.value(1) # 吸合 #p13.value(0) # 断开

专攻代码型闪存芯片赛道,芯天下授权世强硬创代理全线产品

近年来受下游应用需求增长的驱动&#xff0c;代码型闪存芯片市场空间持续扩张&#xff0c;在后疫情之下NOR Flash及SLC NAND Flash市场规模整体仍保持逐步增长的趋势。 为了迎合市场需求&#xff0c;世强先进&#xff08;深圳&#xff09;科技股份有限公司&#xff08;下称“世…

【离散数学】——期末刷题题库(树其二)

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

vue2的小练习——实现自定义指令v-nodata的商品列表

实现&#xff1a;一个商品列表&#xff0c;每个商品包含商品名称、商品价格&#xff0c;我们可以对每个商品进行删除操作&#xff0c;要求如下。 实现一个自定义指令v-nodata&#xff0c;指令的功能是当商品列表中没有商品数据时会显示一个div&#xff0c;div中有“暂无数据”…

node.js mongoose middleware

目录 官方文档 简介 定义模型 注册中间件 创建doc实例&#xff0c;并进行增删改查 方法名和注册的中间件名相匹配 执行结果 分析 错误处理中间件 手动抛出错误 注意点 官方文档 Mongoose v8.0.3: Middleware 简介 在mongoose中&#xff0c;中间件是一种允许在执…

智能停车场系统施工布线方案

一、停车场系统所用的控制主板的信号线是不可以与交流电源线平行铺设的&#xff0c;所以应该尽量避免穿在同一PVC管内。 二、停车场系统在布线的时候需要避免导线产生接头。如果必须有接头&#xff0c;那么接头也应该使用压线或焊接并作防水处理。 三、停车场布线所有外部设备…

玩转大数据19:数据治理与元数据管理策略

随着大数据时代的到来&#xff0c;数据已经成为企业的重要资产。然而&#xff0c;如何有效地管理和利用这些数据&#xff0c;成为了一个亟待解决的问题。数据治理和元数据管理是解决这个问题的关键。 1.数据治理的概念和重要性 数据治理是指对数据进行全面、系统、规范的管理…

netty线程调度定制

1、netty的线程调度问题 在netty的TCP调度中&#xff0c;线程的调度封装在NioEventLoopGroup中&#xff0c;线程执行则封装在NioEventLoop中。 线程调度规则封装在MultithreadEventExecutorGroup的next方法中&#xff0c;这个方法又封装了EventExecutorChooserFactory&#xf…

低代码核心能力详解:简化应用开发的新思路

低代码平台作为一种快速地应用开发解决方法&#xff0c;为中小企业实现数字化转型提供了机会。但是&#xff0c;对于一些刚开始触碰低代码平台的企业来说&#xff0c;了解其核心能力是很重要的。本文将详细分析低代码平台的核心能力&#xff0c;并在挑选低代码平台以前为中小企…

Jmeter接口程序项目实战教程

1.什么是jmeter&#xff1f; JMeter是100%完全由Java语言编写的&#xff0c;免费的开源软件&#xff0c;是非常优秀的性能测试和接口测试工具&#xff0c;支持主流协议的测试 2.jmeter能做什么&#xff1f; JMeter是100%完全由Java语言编写的软件性能测试的GUI的测试工具&am…

六、W5100S/W5500+RP2040之MicroPython开发<UDP示例>

文章目录 1. 前言2. 相关网络信息2.1 简介2.2 UDP通讯过程2.3 优点2.4 应用 3. WIZnet以太网芯片4. UDP通信示例讲解以及使用4.1 程序流程图4.2 测试准备4.3 连接方式4.4 相关代码4.5 烧录验证 5. 注意事项6. 相关链接 1. 前言 在这个智能硬件和物联网时代&#xff0c;MicroPyt…

drf知识--01

前后端开发模式 在开发Web应用中&#xff0c;有两种应用模式&#xff1a; 前后端混合开发: bbs 项目--renderajax 1、全栈开发--前端html后端都是一个人写 2、前端人员&#xff1a;写空页面&#xff0c;没有模板语法&#xff0c;只要html&#xff0c;c…

【Spring】14 ApplicationEventPublisherAware 接口

文章目录 1. 简介2. 作用3. 使用3.1 创建并实现接口3.2 配置 Bean 信息3.3 创建启动类3.4 启动3.5 工作流程图 4. 应用场景总结 Spring 框架为开发者提供了丰富的扩展点&#xff0c;其中之一是 Bean 生命周期中的回调接口。本文将专注介绍一个与事件发布相关的接口 Applicatio…

Opencv实验合集——实验四:图片融合

1.概念 图像融合是将两个或多个图像结合在一起&#xff0c;创建一个新的图像的过程。这个过程的目标通常是通过合并图像的信息来获得比单个图像更全面、更有信息量的结果。图像融合可以在许多领域中应用&#xff0c;包括计算机视觉、遥感、医学图像处理等。 融合的方法有很多…

同义词替换器降低论文重复率的最新技术动态

大家好&#xff0c;今天来聊聊同义词替换器降低论文重复率的最新技术动态&#xff0c;希望能给大家提供一点参考。 以下是针对论文重复率高的情况&#xff0c;提供一些修改建议和技巧&#xff0c;可以借助此类工具&#xff1a; 标题&#xff1a;同义词替换器降低论文重复率的最…

Jmeter的接口测试详细步骤并实现业务闭环

一、首先是了解Jmeter接口测试用到的组件 1、测试计划&#xff1a;Jmeter的起点和容器2、线程组&#xff1a;代表一定的虚拟用户3、取样器&#xff1a;发送请求的最小单元4、逻辑控制器&#xff1a;控制组件的执行顺序5、前置处理器&#xff1a;在请求之前的操作6、后置处理器…