数据结构入门-9-线段树字典树并查集

news2024/10/7 6:46:43

文章目录

  • 一、线段数Segment Tree
    • 1.1 线段树的优势
      • 1.1.2 数组实现线段树
    • 1.2 线段树结构
      • 1.2.1 创建线段树
      • 1.2.2 线段树中的区间查询
      • 1.2.3 线段树的更新
  • 二、字典树 Trie
    • 1.2 字典树结构
      • 1.2.1 创建Trie
      • 1.2.2 Trie查询
  • 三、并查集
    • 3.1 并查集的实现
      • 3.1.1 QuickFind
      • 3.1.1 QuickUnion
        • 初始化
        • union示例

一、线段数Segment Tree

也叫 区间树

1.1 线段树的优势

一面墙,长度n,每次选择一段染色
在这里插入图片描述
关注的是一个个区间

染色操作:更新区间
查询

1.1.2 数组实现线段树

可以很快想到直接用 数组 实现
在这里插入图片描述
数组实现时间复杂度很高,需要优化

1.2 线段树结构

在这里插入图片描述

在这里插入图片描述
平衡二叉树:最大层 - 最小层 <= 1

1.2.1 创建线段树

在这里插入图片描述
区间有n个元素,数组要有多少?

在这里插入图片描述
最差的情况4n,有接近一半的空间是浪费:
在这里插入图片描述

public class SegmentTree<E> {

    private E[] tree;
    private E[] data;
    private Merger<E> merger;

    public SegmentTree(E[] arr, Merger<E> merger){

        this.merger = merger;

        data = (E[])new Object[arr.length];
        for(int i = 0 ; i < arr.length ; i ++)
            data[i] = arr[i];

        tree = (E[])new Object[4 * arr.length];
        buildSegmentTree(0, 0, arr.length - 1);
    }

    // 在treeIndex的位置创建表示区间[l...r]的线段树
    private void buildSegmentTree(int treeIndex, int l, int r){

        if(l == r){
            tree[treeIndex] = data[l];
            return;
        }

        int leftTreeIndex = leftChild(treeIndex);
        int rightTreeIndex = rightChild(treeIndex);

        // int mid = (l + r) / 2;边界问题,l r 特别大 溢出
        int mid = l + (r - l) / 2;
        buildSegmentTree(leftTreeIndex, l, mid);
        buildSegmentTree(rightTreeIndex, mid + 1, r);

		//组合 和 业务逻辑有关
        tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
    }

    public int getSize(){
        return data.length;
    }

    public E get(int index){
        if(index < 0 || index >= data.length)
            throw new IllegalArgumentException("Index is illegal.");
        return data[index];
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
    private int leftChild(int index){
        return 2*index + 1;
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
    private int rightChild(int index){
        return 2*index + 2;
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append('[');
        for(int i = 0 ; i < tree.length ; i ++){
            if(tree[i] != null)
                res.append(tree[i]);
            else
                res.append("null");

            if(i != tree.length - 1)
                res.append(", ");
        }
        res.append(']');
        return res.toString();
    }
}

public interface Merger<E> {
    E merge(E a, E b);
}

public class Main {

    public static void main(String[] args) {

        Integer[] nums = {-2, 0, 3, -5, 2, -1};
//        SegmentTree<Integer> segTree = new SegmentTree<>(nums,
//                new Merger<Integer>() {
//                    @Override
//                    public Integer merge(Integer a, Integer b) {
//                        return a + b;
//                    }
//                });

        SegmentTree<Integer> segTree = new SegmentTree<>(nums,
                (a, b) -> a + b);
        System.out.println(segTree);
    }
}


1.2.2 线段树中的区间查询

在这里插入图片描述

    public E get(int index){
        if(index < 0 || index >= data.length)
            throw new IllegalArgumentException("Index is illegal.");
        return data[index];
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引
    private int leftChild(int index){
        return 2*index + 1;
    }

    // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引
    private int rightChild(int index){
        return 2*index + 2;
    }

    // 返回区间[queryL, queryR]的值
    public E query(int queryL, int queryR){

        if(queryL < 0 || queryL >= data.length ||
                queryR < 0 || queryR >= data.length || queryL > queryR)
            throw new IllegalArgumentException("Index is illegal.");

        return query(0, 0, data.length - 1, queryL, queryR);
    }

    // 在以treeIndex为根的线段树中[l...r]的范围里,搜索区间[queryL...queryR]的值
    private E query(int treeIndex, int l, int r, int queryL, int queryR){

        if(l == queryL && r == queryR)
            return tree[treeIndex];

        int mid = l + (r - l) / 2;
        // treeIndex的节点分为[l...mid]和[mid+1...r]两部分

        int leftTreeIndex = leftChild(treeIndex);
        int rightTreeIndex = rightChild(treeIndex);
        if(queryL >= mid + 1)
            return query(rightTreeIndex, mid + 1, r, queryL, queryR);
        else if(queryR <= mid)
            return query(leftTreeIndex, l, mid, queryL, queryR);

        E leftResult = query(leftTreeIndex, l, mid, queryL, mid);
        E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR);
        return merger.merge(leftResult, rightResult);
    }

1.2.3 线段树的更新


    // 将index位置的值,更新为e
    public void set(int index, E e){

        if(index < 0 || index >= data.length)
            throw new IllegalArgumentException("Index is illegal");

        data[index] = e;
        set(0, 0, data.length - 1, index, e);
    }

    // 在以treeIndex为根的线段树中更新index的值为e
    private void set(int treeIndex, int l, int r, int index, E e){

        if(l == r){
            tree[treeIndex] = e;
            return;
        }

        int mid = l + (r - l) / 2;
        // treeIndex的节点分为[l...mid]和[mid+1...r]两部分

        int leftTreeIndex = leftChild(treeIndex);
        int rightTreeIndex = rightChild(treeIndex);
        if(index >= mid + 1)
            set(rightTreeIndex, mid + 1, r, index, e);
        else // index <= mid
            set(leftTreeIndex, l, mid, index, e);

        tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]);
    }

二、字典树 Trie

在这里插入图片描述
在这里插入图片描述

区分大小写、字符 ,可以设计的更灵活,用Map 灵活若干
在这里插入图片描述

查找之前就已经知道节点的内容,因此可以省略char c
在这里插入图片描述
有一些 单词 中间就可能是一个单词 pan(平底锅) panda(熊猫)
因此需要指定一个标记 isWord ,来标记是否是完整的单词
在这里插入图片描述

1.2 字典树结构

1.2.1 创建Trie

import java.util.TreeMap;

public class Trie {

    private class Node{

        public boolean isWord;
        public TreeMap<Character, Node> next;

        public Node(boolean isWord){
            this.isWord = isWord;
            next = new TreeMap<>();
        }

        public Node(){
            this(false);
        }
    }

    private Node root;
    private int size;

    public Trie(){
        root = new Node();
        size = 0;
    }

    // 获得Trie中存储的单词数量
    public int getSize(){
        return size;
    }

    // 向Trie中添加一个新的单词word
    public void add(String word){

        Node cur = root;
        for(int i = 0 ; i < word.length() ; i ++){
            char c = word.charAt(i);
            if(cur.next.get(c) == null)
                cur.next.put(c, new Node());
            cur = cur.next.get(c);
        }

        if(!cur.isWord){
            cur.isWord = true;
            size ++;
        }
    }
}


1.2.2 Trie查询

    // 查询单词word是否在Trie中
    public boolean contains(String word){

        Node cur = root;
        for(int i = 0 ; i < word.length() ; i ++){
            char c = word.charAt(i);
            if(cur.next.get(c) == null)
                return false;
            cur = cur.next.get(c);
        }
        //查询到了,但是有可能不是终止,pan  pandas
        //所以不能直接返回true
        return cur.isWord;
    }

三、并查集

子节点 —> 父节点 的数结构

并查集可以应用到 很多点 之间是否有连接的问题
(脉脉,各个用户一层层之间连接)
在这里插入图片描述
是否连接问题(A–>B : T/F) & 路径问题(A----->X----->Z----->E------>B)
减少多余的计算

public interface UF {

    int getSize();
    boolean isConnected(int p, int q); //不关心pq是谁,可以直接存索引
    void unionElements(int p, int q);
}

并不考虑add 操作,一般都是一次性初始化

3.1 并查集的实现

在这里插入图片描述

下边 0 1 代表在0 1 的集合中

在这里插入图片描述
一组集合,看是否在同一个下面的 0 1 中,

3.1.1 QuickFind

在这里插入图片描述


    // 查找元素p所对应的集合编号
    // O(1)复杂度
    private int find(int p) {
        if(p < 0 || p >= id.length)
            throw new IllegalArgumentException("p is out of bound.");

        return id[p];
    }

    // 查看元素p和元素q是否所属一个集合
    // O(1)复杂度
    @Override
    public boolean isConnected(int p, int q) {
        return find(p) == find(q);
    }

在这里插入图片描述
union(1,4):1 和 4 在同一个集合中,因此需要改变4组的 下面的值
时间复杂度O(n)

    // 合并元素p和元素q所属的集合
    // O(n) 复杂度
    @Override
    public void unionElements(int p, int q) {

        int pID = find(p);
        int qID = find(q);

        if (pID == qID)
            return;

        // 合并过程需要遍历一遍所有元素, 将两个元素的所属集合编号合并
        for (int i = 0; i < id.length; i++)
            if (id[i] == pID)
                id[i] = qID;
    }

3.1.1 QuickUnion

在这里插入图片描述
3 ----> 2 根节点
union(3,1)
1 ----> 2 根节点

在这里插入图片描述
union(7,3)
7的根节点5 -----> 3的根节点

初始化

在这里插入图片描述
每个节点父节点都是自己

union示例

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

// 我们的第二版Union-Find
public class UnionFind2 implements UF {

    // 我们的第二版Union-Find, 使用一个数组构建一棵指向父节点的树
    // parent[i]表示第一个元素所指向的父节点
    private int[] parent;

    // 构造函数
    public UnionFind2(int size){

        parent = new int[size];

        // 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合
        for( int i = 0 ; i < size ; i ++ )
            parent[i] = i;
    }

    @Override
    public int getSize(){
        return parent.length;
    }

    // 查找过程, 查找元素p所对应的集合编号
    // O(h)复杂度, h为树的高度
    private int find(int p){
        if(p < 0 || p >= parent.length)
            throw new IllegalArgumentException("p is out of bound.");

        // 不断去查询自己的父亲节点, 直到到达根节点
        // 根节点的特点: parent[p] == p
        while(p != parent[p])
            p = parent[p];
        return p;
    }

    // 查看元素p和元素q是否所属一个集合
    // O(h)复杂度, h为树的高度
    @Override
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合并元素p和元素q所属的集合
    // O(h)复杂度, h为树的高度
    @Override
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot )
            return;

        parent[pRoot] = qRoot;
    }
}

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

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

相关文章

事件触发模式 LT ET ?EPOLLIN EPOLLOUT 各种情况总结。【面试复盘】【学习笔记】

麻了&#xff0c;对 epoll 的触发机制理解不深刻…面试又被拷打了… 下面总结一下各种情况&#xff0c;并不涉及底层原理&#xff0c;底层原理看这里。 文章结构可以看左下角目录、 有什么理解的不对的&#xff0c;请大佬们指点。 先说结论&#xff0c;下面再验证&#xff…

WRF-UCM 高精度城市化气象动力模拟、WRF+WRF-UCM 模拟气象场

查看原文>>>&#xff08;WRF-UCM&#xff09;高精度城市化气象动力模拟技术与案例应用 目录 模型基础理论 模型平台从零安装讲解 城市模块在线耦合&#xff08;WRFWRF-UCM&#xff09;模拟案例讲解 WRFWRF-UCM如何模拟气象场 实际应用及案例分析 其他大气相关推…

PostgreSQL插件—数据恢复工具pg_recovery使用详解

说明 pg_recovery 是一款基于PostgreSQL的数据恢复工具。针对表做了 update/delete/rollback/dropcolumn 后的数据恢复。 版本支持 pg_revovery当前支持 PostgreSQL 12/13/14 。 安装 下载插件 墨天轮下载地址&#xff1a;https://www.modb.pro/download/434516github下载地…

吃鸡录屏怎么录到自己的声音 吃鸡录屏怎么隐藏按键

很多人在玩吃鸡游戏时喜欢将自己的游戏过程录制下来&#xff0c;特别是很多游戏主播会录制视频&#xff0c;录制后将视频分享到社交平台。但是在录制时经常会遇到很多问题&#xff0c;如声音、画面清晰度和完整性等。接下来就来分享一下吃鸡录屏怎么录到自己的声音&#xff0c;…

pytorch单机多卡训练

多卡训练的方式 以下内容来自知乎文章&#xff1a;当代研究生应当掌握的并行训练方法&#xff08;单机多卡&#xff09; pytorch上使用多卡训练&#xff0c;可以使用的方式包括&#xff1a; nn.DataParalleltorch.nn.parallel.DistributedDataParallel使用Apex加速。Apex 是 N…

嵌入式学习笔记汇总

本文整理STM32、STM8和uCOS-III的所有文章链接。 STM32学习笔记目录 源码&#xff1a;mySTM32-learn STM32学习笔记&#xff08;1&#xff09;——LED和蜂鸣器 STM32学习笔记&#xff08;2&#xff09;——按键输入实验 STM32学习笔记&#xff08;3&#xff09;——时钟系统 …

.NET System.Management 获取windows系统和硬件信息

ManagementObject用于创建WMI类的实例与WINDOWS系统进行交互&#xff0c;通过使用WMI我们可以获取服务器硬件信息、收集服务器性能数据、操作Windows服务&#xff0c;甚至可以远程关机或是重启服务器。 WMI 的全称 Windows Management Instrumentation&#xff0c;即 Windows …

音视频八股文(1)--音视频基础

1.1.音视频录制原理 1.2.音视频播放原理 1.3.图像表示RGB-YUV 1.3.1 图像基础概念 ◼ 像素&#xff1a;像素是一个图片的基本单位&#xff0c;pix是英语单词picture的简写&#xff0c;加上英 语单词“元素element”&#xff0c;就得到了“pixel”&#xff0c;简称px&#xff…

使用Mingw64在CLion中搭建Linux开发环境

1.前言&#xff1a; 博主本来一直是在Visual Studio 2017中使用C语言编写程序&#xff0c;但有个问题是Visual Studio 2017默认使用自带的Windows SDK和编译器&#xff0c;我想使用POSIX文件操作就不行&#xff08;因为Windows中没有Linux SDK&#xff09;&#xff0c;虽然Wind…

【Kafka-架构及基本原理】Kafka生产者、消费者、Broker原理解析 Kafka原理流程图

【Kafka-架构及基本原理】Kafka生产者、消费者、Broker原理解析 & Kafka原理流程图1&#xff09;Kafka原理1.1.生产者流程细节1.2.Broker 的存储流程细节1.3.消费者流程细节2&#xff09;Kafka读写流程图1&#xff09;Kafka原理 1.1.生产者流程细节 1、生产者发送消息到 …

计算机毕业设计源码整合大全_kaic

以下为具体单个列表&#xff08;单个下载在我主页搜索即可&#xff09;&#xff1a; 1&#xff1a;计算机专业-ASP(499套&#xff09; ASP学生公寓管理系统的设计与实现(源代码论文).rar 1&#xff1a;计算机专业-ASP(499套&#xff09; ASP学科建设设计(源代码论文).ra…

Clickhouse 引擎之MergeTree详解

分区详解 数据存储底层分布 # 数据在这个位置 rootfjj001:~# cd /var/lib/clickhouse/data rootfjj001:/var/lib/clickhouse/data# ls # 数据库 default system rootfjj001:/var/lib/clickhouse/data# cd default/ rootfjj001:/var/lib/clickhouse/data/default# ls #表 enu…

ASEMI代理AD8400ARZ10-REEL原装ADI车规级AD8226ARZ-R7

编辑&#xff1a;ll ASEMI代理AD8400ARZ10-REEL原装ADI车规级AD8226ARZ-R7 型号&#xff1a;AD8400ARZ10-REEL 品牌&#xff1a;ADI/亚德诺 封装&#xff1a;SOIC-8 批号&#xff1a;2023 引脚数量&#xff1a;8 安装类型&#xff1a;表面贴装型 AD8400ARZ10-REEL汽车芯…

Zabbix监控系统——附详细步骤和图解

文章目录一、Zabbix概述1、使用zabbix的原因2、zabbix的概念和构成3、zabbix 监控原理&#xff1a;4、zabbix的程序组件二、安装 zabbix 5.01、部署 zabbix 服务端的操作步骤2、实例操作&#xff1a;部署 zabbix 服务端3、部署 zabbix 客户端4、实例操作&#xff1a;部署 zabbi…

【Linux】揭开套接字编程的神秘面纱(下)

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;前言&…

(二十三)槽函数的书写规则导致槽函数触发2次的问题

在创建QT的信号和槽时&#xff0c;经常无意间保留着QT书写槽函数的习惯&#xff0c;或者在QT设计界面直接右键【转到槽】去创建槽函数&#xff0c;但是后期需要用到disconnect时&#xff0c;又重新写了一遍connect函数&#xff0c;那么你会发现实际槽函数执行了2遍。 首先来看…

要在Ubuntu中查找进程的PID,可以使用pgrep或pidof命令。

一 查找进程 1.pgrep命令 pgrep命令可以根据进程名或其他属性查找进程的PID。例如&#xff0c;要查找名为"firefox"的进程的PID&#xff0c;可以在终端中输入以下命令&#xff1a; pgrep firefox如果有多个名为"firefox"的进程&#xff0c;pgrep命令将返…

互联网一个赛道只剩下几家,真要爆品

互联网一个赛道剩下几家&#xff0c;真要爆品 2017年的书&#xff0c;案例基本上是马后炮总结 趣讲大白话&#xff1a;说起来容易&#xff0c;做起来难 【趣讲信息科技136期】 **************************** 书中讲的范冰冰翻车了 书中不看好的线下渠道&#xff0c;现在成香饽饽…

面试篇-Java并发之CAS:掌握原理、优缺点和应用场景分析,避免竞态问题

1、CAS介绍及原理 多线程中的CAS&#xff08;Compare-and-Swap&#xff09;操作是一种常见的并发控制方法&#xff0c;用于实现原子性更新共享变量的值。其核心思想是通过比较内存地址上的值和期望值是否相等来确定是否可以进行更新操作&#xff0c;从而避免多线程条件下的竞态…

HMI实时显示网络摄像机监控画面——以海康威视网络摄像机为例

随着IOT技术的快速发展&#xff0c;网络摄像机快速应用于工业领域&#xff0c;结合其他智能设备建立一个智能系统&#xff0c;提高用户与机器设备之间的交互体验&#xff0c;帮助企业优化人员配置。 作为重要的可视化设备&#xff0c;HMI不仅可以采集现场设备数据&#xff0c;…