哈希表 | 哈希查找 | 哈希函数 | 数据结构 | 大话数据结构 | Java

news2024/11/19 13:43:24
🙋大家好!我是毛毛张!
🌈个人首页: 神马都会亿点点的毛毛张

📌毛毛张今天分享的内容🖆是数据结构中的哈希表,毛毛张主要是依据《大话数据结构📖》的内容来进行整理,不同之处是使用Java语言来介绍其中的代码实现,同时也为后面介绍Java中的集合做铺垫🛤️

文章目录

  • 1.散列表查找(哈希表)概述
    • 1.1 散列表查找定义
    • 1.2 散列表查找步骤
  • 2.散列函数的构造方法
    • 2.1 直接定址法(了解)
    • 2.2 数字分析法(了解)
    • 2.3 平方取中法(了解)
    • 2.4 折叠法(了解)
    • 2.5 除留余数法(掌握)
    • 2.6 随机数法(了解)
  • 3.处理散列冲突
    • 3.1 开放定址法
    • 3.2 链地址法
    • 3.3 公共溢出区法
  • 4.散列表查找实现
    • 4.1 散列表查找的算法Java实现
    • 4.2 性能分析
  • 参考文献

1.散列表查找(哈希表)概述

1.1 散列表查找定义

  • 散列技术: 是在记录的存储位置和它的关键字之间建立一个确定的对应关系 f f f,使得每个关键字key对应一个存储位置 f ( k e y ) f(key) f(key)。查找时,根据这个确定的对应关系找到给定值key的映射 f ( k e y ) f(key) f(key),若查找集合中存在这个记录,则必定在 f ( k e y ) f(key) f(key)的位置上。我们把这种对应关系 f f f称为散列函数,又称哈希(Hash)函数
    存储位置 = f ( 关键字 ) 存储位置 = f(关键字) 存储位置=f(关键字)
  • 散列表/哈希表(Hash table): 采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为散列表哈希表(Hash table)。而关键字对应的记录存储位置我们称为散列地址
  • 总结:
    • 散列表通过建立关键字和存储地址之间的一种直接映射关系,是一种根据关键字而直接进行访问的数据结构
    • 散列技术使得我们可以通过查找关键字不需要比较就可获得需要的记录的存储位置

1.2 散列表查找步骤

  • 散列过程步骤: 如下图
    • 第一步: 通过散列函数计算记录的散列地址,并按此散列地址存储记录
    • 第二步: 当查找记录时,我们通过同样的散列函数计算记录的散列地址,按此散列地址访问该记录,由于存取用的是同一个散列函数,因此结果当然也是相同的
      image-20240627154931051
  • 散列技术既是一种存储方法, 也是一种查找方法,而散列技术的记录之间不存在什么逻辑关系,它只与关键字有关联,因此散列主要是面向查找的存储结构
  • 散列技术最合适的求解问题是查找与给定值相等的记录
    • 不适合那种同样关键字,它能对应很多记录的情况,就不适合用散列技术
    • 散列表也不适合范围查找,比如查找一个班级18~22岁的同学,在散列表中没法进行
  • 冲突(collision): 散列函数可能会把两个或两个以上的不同关键字映射到同一地址,称这种情况为冲突,这些发生碰撞的不同关键字称为同义词
    • 一方面,设计得好的散列函数应尽量减少这样的冲突
    • 另一方面,由于这样的冲突总是不可避免的,所以还要设计好处理冲突的方法
  • 设计一个简单、均匀、存储利用率高的散列函数是散列技术中最关键的问题,一个好的散列表在理想情况下,对散列表进行查找的时间复杂度为$O(1) $,即与表中元素的个数无关

2.散列函数的构造方法

  • 好的散列函数设计原则:
    • 计算简单: 散列函数的计算应尽量简单,能够在较短的时间内计算出任一关键字对应的散列地址
    • 散列地址分布均匀: 散列函数计算出来的地址应该能等概率、均匀地分布在整个地址空间中,从而减少冲突的发生
    • 散列函数的定义域必须包含全部需要存储的关键字,而值域的范围则依赖于散列表的大小或地址范围
  • 下面介绍接种常用的散列函数构造方法

2.1 直接定址法(了解)

  • 直接取关键字的某个线性函数值为散列地址,散列函数为:其中a、b为常数
    f ( k e y ) = a × k e y + b f(key)=a×key + b f(key)=a×key+b
    image-20240627160121771
  • 这种方法计算最简单,且不会产生冲突,但并不常用。它适合关键字的分布基本连续的情况,若关键字分布不连续,空位较多,则会造成存储空间的浪费。

2.2 数字分析法(了解)

  • 例如当手机号码为关键字时,其11位数字是有规则的,此时是无需把11位数值全部当做散列地址,这时我们给关键词抽取, 抽取方法是使用关键字的一部分来计算散列存储位置的方法,这在散列函数中是常常用到的手段
  • 数字分析法通常适合处理关键字位数比较大的情况,如果事先知道关键字的分布且关键字的若干位分布较均匀,就可以考虑用这个方法。

2.3 平方取中法(了解)

  • 这个方法计算很简单,假设关键字是1234,那么它的平方就是1522756,再抽取中间的3位就是227,用做散列地址。
  • 再比如关键字是4321,那么它的平方就是18671041,抽取中间的3位就可以是671,也可以是710,用做散列地址。
  • 平方取中法比较适合于不知道关键字的分布,而位数又不是很大的情况

2.4 折叠法(了解)

  • 折叠法 是将关键字从左到右分割成位数相等的几部分(最后一部分位数可以短些),然后将这几部分叠加求和,并按散列表表长,取后几位作为散列地址。
  • 折叠法适合事先不需要知道关键字的分布,适合关键字位数比较多的情况

2.5 除留余数法(掌握)

  • 除留取余法对于散列表长为m的散列函数公式为:f(key)= key mod p (p ≤ m)mod是取模(求余数)的意思,这方法不仅可以对关键字直接取模,也可在折叠、平方取中后再取模。
  • 除留余数法的关键是就在于选择合适的 p p p p p p如果选择不好,就有可能产生同义词,根据前辈门的经验,若散列表表长为 m m m,通常 p p p为小于或等于表长(最好接近 m m m)的最小质数或不包含小于20质因子的合数
  • 图例:
    image-20240627161944061
  • 此方法是最常用的构造散列函数的方法

2.6 随机数法(了解)

  • 选择一个随机数,取关键字的随机函数值为它的散列地址。也就是 f ( k e y ) = r a n d o m ( k e y ) f(key)=random(key) f(key)=random(key)这里 r a n d o m random random是随机函数。
  • 当关键字的长度不等时,采用这个方法构造散列函数是比较合适的

3.处理散列冲突

  • 任何设计出来的散列函数都不可能绝对地避免冲突,因此,必须考虑在发生冲突时应该如何处理,即为产生冲突的关键字寻找下一个空的Hash地址
  • H i H_i Hi表示处理冲突中第 i i i次探测得到的散列地址,假设得到的另一个散列地址 H 1 H_1 H1仍然发生冲突,只得继续求下一个地址 H 2 H_2 H2,以此类推,直到 H k H_k Hk不发生冲突为止,则 H k H_k Hk为关键字在表中的地址

3.1 开放定址法

  • 开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入
  • 计算公式: H i ( k e y ) = ( f ( k e y ) + d i ) % m   ( d i = 1 , 2 , 3 , . . . , m − 1 ) H_i(key)=(f(key)+d_i) \% m\ (d_i=1,2,3,...,m-1) Hi(key)=(f(key)+di)%m (di=1,2,3,...,m1),式中, H ( k e y ) H(key) H(key)为散列函数 i = 0 , 1 , 2 , . . . , k   ( k < = m − 1 ) i=0,1,2,...,k\ (k<=m-1) i=0,1,2,...,k (k<=m1) m m m表示散列列表表长; d i d_i di​​为增量序列
  • 取定某一增量序列后,对应的处理方法就是确定的,通常有以下4种方法:
  • 线性探测法:当 d i = 0 , 1 , 2 , . . . , m − 1 d_i=0,1,2,..., m-1 di=0,1,2,...,m1时,称为线性探测法
    • 特点: 当冲突发生时,顺序查看表中下一个单元(探测到表尾地址 m − 1 m-1 m1时,下一个探测地址是表首地址 0 0 0),直到找出一个空闲单元(当表未填满时一定能找到一个空闲单元)或查遍全表
    • 缺点: 线性探测法可能使第 i i i个散列地址的同义词存入第 i + 1 i+1 i+1个散列地址,这使得本应存入第 i + 1 i+1 i+1个散列地址的元素就争夺第 i + 2 i + 2 i+2个散列地址的元素的地址,从而造成大量元素在相邻的散列地址上堆积,大大降低了查找效率。
  • 平方探测法: d i = 0 2 , 1 2 , − 1 2 , 2 2 , − 2 2 , . . , k 2 , − k 2 d_i=0^2,1^2,-1^2,2^2,-2^2,..,k^2, -k^2 di=02,12,12,22,22,..,k2,k2时,称为平方探测法,其中 k < m / 2 k<m/2 k<m/2,散列表长度 m m m必须是一个可以表示成 4 k + 3 4k+ 3 4k+3​的素数,又称二次探测法
    • 平方探测法是一种较好的处理冲突的方法,可以避免出现堆积问题
    • 缺点: 不能探测到散列表上的所有单元,但至少能探测到一半单元
  • 再散列法: d i = H a s h 2 ( k e y ) d_i= Hash_2(key) di=Hash2(key)时,称为再散列法,又称双散列法,需要使用两个或者多个散列函数,当通过第一个散列函数 H ( k e y ) H(key) H(key)得到的地址发生冲突时,则利用第二个散列函数 H a s h 2 ( k e y ) Hash_2(key) Hash2(key)计算该关键字的地址增量
    • 具体散列函数形式为: H i = ( H ( k e y ) + i ∗ H a s h 2 ( k e y ) ) % m H_i= (H(key) + i*Hash_2(key)) \% m Hi=(H(key)+iHash2(key))%m,初始探测位置 H 0 = H ( k e y ) H_0 = H(key)% m H0=H(key) i i i是冲突的次数,初始为 0 0 0
    • 使用该方法,最多经过 m − 1 m-1 m1次探测就会遍历表中所有位置,回到 H 0 H_0 H0​位置
    • 这种方法使得关键字不产生剧集,相应地也增加了计算的时间
  • 伪随机数探测法: 在发生冲突时,对于位移量 d i d_i di​采用随机函数计算得到,称为伪随机探测法
    • 伪随机数是说,如果我们设置随机种子相同,则不断调用随机函数可以生成不会重复的数列,我们在查找时,用同样的随机种子,它每次得到的数列是相同的,相同的 d i d_i di当然可以得到相同的散列地址

注意:

  • 在开放定址的情形下,不能随便物理删除表中的已有元素,因为若删除元素,则会截断其他具有相同散列地址的元素的查找地址。
  • 因此,要删除一个元素时,可给它做一个删除标记,进行逻辑删除。
  • 但这样做的副作用是:执行多次删除后,表面上看起来散列表很满,实际上有许多位置未利用,因此需要定期维护散列表,要把删除标记的元素物理删除。

3.2 链地址法

  • 将所有关键字为同义词的记录存储在一个单链表中,我们称这种表为同义词子表,在散列表中只存储所有同义词子表的头指针
  • 示例: 有关键字序列为 { 12 , 67 , 56 , 16 , 25 , 37 , 22 , 29 , 15 , 47 , 48 , 34 } \{12, 67, 56, 16, 25, 37, 22, 29,15, 47,48, 34\} {12,67,56,16,25,37,22,29,15,47,48,34},我们用除留余数法构造散列函数 H ( k e y ) = k e y % 12 H(key)=key\%12 H(key)=key%12,用链地址法处理冲突,建立的表如下图所示
    image-20240627173927852
  • 链地址法对于可能会造成很多冲突的散列函数来说,提供了绝不会出现找不到地址的保障。当然,这也就带来了查找时需要遍历单链表的性能损耗

3.3 公共溢出区法

  • 这个方法其实就更加好理解,就是把凡是冲突的家伙额外找个公共场所待着,我们为所有冲突的关键字建立了一个公共的溢出区来存放
  • 示例:我们共有三个关键字 37 , 48 , 34 {37,48,34} 37,48,34与之前的关键字位置有冲突,那么就将它们存储到溢出表中,如下图所示
    在这里插入图片描述
  • 查找步骤:
    • 在查找时,对给定值通过散列函数计算出散列地址后,先与基本表的相应位置进行对比,如果相等,则查找成功;
    • 如果不相等,则到溢出表去进行顺序查找
  • 如果相对于基本表而言,有冲突的数据很少的情况下,公共溢出区的结构对查找性能来说还是非常高的

4.散列表查找实现

4.1 散列表查找的算法Java实现

  • 首先是需要定义一个散列表的结构以及一些相关的常数,其中HashTable就是散列表类,结构当中的elem为一个动态数组
public class HashTable {
    public static final int SUCCESS = 1;
    public static final int UNSUCCESS = 0;
    public static final int HASHSIZE = 12;    // 定义散列表表长为数组的长度
    public static final int NULLKEY = -32768; // 代表空地址

    private int[] elem;  // 数组元素存储基址,动态分配数组
    private int count;   // 当前数据元素个数
    private static int m = HASHSIZE; // 散列表表长,全局变量
}
  • 利用构造函数对散列表进行初始化
 public HashTable() {
     m = HASHSIZE;
     this.count = m;
     this.elem = new int[m];
     for (int i = 0; i < m; i++) {
         this.elem[i] = NULLKEY; // 初始化为空地址
     }
}
  • 为了插入时计算地址,我们需要定义散列函数,散列函数可以根据不同情况更改算法
// 散列函数
public int hash(int key) {
    return key % m;  // 除留余数法
}
  • 初始化完成后,我们可以对散列表进行插入操作,假设我们插入的关键字集合为 12 , 67 , 56 , 16 , 25 , 37 , 22 , 29 , 15 , 47 , 48 , 34 {12,67,56,16,25,37,22,29,15,47, 48,34} 12,67,56,16,25,37,22,29,15,47,48,34
 // 插入关键字进散列表
public void insertHash(int key) {
    int addr = hash(key);  // 通过散列函数求散列地址
    // 如果不为空,则冲突
    while (this.elem[addr] != NULLKEY) {
        addr = (addr + 1) % m;  // 开放定址法的线性探测
    }
    this.elem[addr] = key;  // 直到有空位后插入关键字
}
  • 代码中插入关键字时,首先算出散列地址,如果当前地址不为空关键字,则说明有冲突。此时我们应用开放定址法的线性探测进行重新寻址,此处也可更改为链地址法等其他解决冲突的办法。
  • 散列表存在后,我们在需要时就可以通过散列表查找要的记录
// 散列表查找关键字,找到后用addr保存地址
public boolean searchHash(int key, int[] addr) {
    addr[0] = hash(key);  // 通过散列函数求得散列地址
    // 如果不为空,则有同义词冲突
    while (this.elem[addr[0]] != key) {
        addr[0] = (addr[0] + 1) % m;  // 开放地址法的线性探测
        if (this.elem[addr[0]] == NULLKEY || addr[0] == hash(key)) {
            // 如果循环到空址或回到原点
            return false;  // 则说明关键字不存在
        }
    }
    return true;
}
  • 查找的代码与插入的代码非常类似,只需做一个不存在关键字的判断而已
  • 完整的测试代码如下:
public class HashTable {
    public static final int SUCCESS = 1;
    public static final int UNSUCCESS = 0;
    public static final int HASHSIZE = 12;    // 定义散列表表长为数组的长度
    public static final int NULLKEY = -32768; // 代表空地址

    private int[] elem;  // 数组元素存储基址,动态分配数组
    private int count;   // 当前数据元素个数
    private static int m = HASHSIZE; // 散列表表长,全局变量

    public HashTable() {
        this.elem = new int[HASHSIZE];
        this.count = HASHSIZE;
        for (int i = 0; i < HASHSIZE; i++) {
            this.elem[i] = NULLKEY; // 初始化为空地址
        }
    }

    // 初始化散列表
    public boolean initHashTable() {
        m = HASHSIZE;
        this.count = m;
        this.elem = new int[m];
        for (int i = 0; i < m; i++) {
            this.elem[i] = NULLKEY; // 初始化为空地址
        }
        return true;
    }

    // 散列函数
    public int hash(int key) {
        return key % m;  // 除留余数法
    }

    // 插入关键字进散列表
    public void insertHash(int key) {
        int addr = hash(key);  // 通过散列函数求散列地址
        // 如果不为空,则冲突
        while (this.elem[addr] != NULLKEY) {
            addr = (addr + 1) % m;  // 开放定址法的线性探测
        }
        this.elem[addr] = key;  // 直到有空位后插入关键字
    }

    // 散列表查找关键字,找到后用addr保存地址
    public boolean searchHash(int key, int[] addr) {
        addr[0] = hash(key);  // 通过散列函数求得散列地址
        // 如果不为空,则有同义词冲突
        while (this.elem[addr[0]] != key) {
            addr[0] = (addr[0] + 1) % m;  // 开放地址法的线性探测
            if (this.elem[addr[0]] == NULLKEY || addr[0] == hash(key)) {
                // 如果循环到空址或回到原点
                return false;  // 则说明关键字不存在
            }
        }
        return true;
    }

    public static void main(String[] args) {
        HashTable hashTable = new HashTable();
        boolean isInitialized = hashTable.initHashTable();
        System.out.println("HashTable initialized: " + isInitialized);

        // 插入一些关键字
        hashTable.insertHash(10);
        hashTable.insertHash(22);
        hashTable.insertHash(31);
        hashTable.insertHash(4);

        // 查找关键字
        int[] addr = new int[1];
        boolean found = hashTable.searchHash(22, addr);
        System.out.println("Key 22 found: " + found + ", at address: " + addr[0]);

        found = hashTable.searchHash(15, addr);
        System.out.println("Key 15 found: " + found);
    }
}

4.2 性能分析

  • 虽然散列表在关键字与记录的存储位置之间建立了直接映像,但由于冲突的产生,使得散列表的查找过程仍然是一个给定值和关键字进行比较的过程,因此,仍需要以平均查找长度作为衡量散列表的查找效率的度量。如果没有冲突,散列查找的时间复杂度是最高的,时间复杂度为 O ( 1 ) O(1) O(1)
  • 若用 c i c_i ci表示每一个关键字查找的次数,则平均查找次数可表示为: A S L = ( ∑ i = 0 m c i ) / m ASL=(\displaystyle\sum_{i=0}^{m}c_i)/m ASL=(i=0mci)/m
  • 散列表的查找效率取决于三个因素:
    • 散列函数是否均匀:散列函数的好坏直接影响着出现冲突的频繁程度,不过,由于不同的散列函数对同一组随机的关键字,产生冲突的可能性是相同的,因此我们可以不考虑它对平均查找长度的影响
    • 处理冲突的方法:相同的关键字、相同的散列函数,但处理冲突的方法不同,会使得平均查找长度不同。比如线性探测处理冲突可能会产生堆积,显然就没有二次探测法好,而链地址法处理冲突不会产生任何堆积,因而具有更佳的平均查找性能
    • 散列表的装填因子
  • 装填因子: 散列表的装填因子一般记为 α \alpha α,它用来表征散列表的装满程度,其计算公式为: α = n ( 表中填入的记录个数 ) / m ( 散列表长度 ) \alpha=n_{(表中填入的记录个数)}/m_{(散列表长度)} α=n(表中填入的记录个数)/m(散列表长度)
  • 散列表的平均查找长度取决于散列表的装填因子 α \alpha α,而不直接依赖于 n n n m m m
    • 直观地看, α \alpha α越大,表示装填的记录越,发生冲突的可能性越大,反之发生冲突的可能性越小
  • 不管记录个数 n n n有多大,我们总可以选择一个合适的装填因子以便将平均查找长度限定在一个范围之内,此时我们散列查找的时间复杂度就真的是 O ( 1 ) O(1) O(1)了。 为了做到这一点,通常我们都是将散列表的空间设置得比查找集合大,此时虽然是浪费了一定的空间,但换来的是查找效率的大大提升,总的来说,还是非常值得的。

参考文献

  • 《大话数据结构》
  • https://blog.csdn.net/Real_Fool_/article/details/114359564
  • https://blog.csdn.net/qq_56884023/article/details/123072671

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

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

相关文章

3D Gaussian Splatting代码中的train和render两个文件代码解读

现在来聊一聊训练和渲染是如何进行的 training train.py line 31 def training(dataset, opt, pipe, testing_iterations, saving_iterations, checkpoint_iterations, checkpoint, debug_from):# 初始化第一次迭代的索引为0first_iter 0# 准备输出和日志记录器tb_writer p…

0703_ARM7

练习&#xff1a; 封装exti&#xff0c;cic初始化函数 //EXTI初始化 void hal_key_exti_init(int id,int exticr,int mode){//获取偏移地址int address_offset (id%4)*8;//获取寄存器编号int re_ser (id/4)1;//printf("address_offset%d,re_ser%d\n",address_o…

苹果手机怎么刷机?适合小白的刷机办法!

自己的苹果手机用时间长了&#xff0c;有些人想要为自己的手机重新刷新一下&#xff0c;但又不知道怎么刷机。不要慌现在就来给大家详细介绍一下苹果手机怎么刷机&#xff0c;希望可以帮助到大家。 iPhone常见的刷机方式&#xff0c;分为iTunes官方和第三方软件两种刷机方式。 …

基于Web技术的教育辅助系统设计与实现(SpringBoot MySQL)+文档

&#x1f497;博主介绍&#x1f497;&#xff1a;✌在职Java研发工程师、专注于程序设计、源码分享、技术交流、专注于Java技术领域和毕业设计✌ 温馨提示&#xff1a;文末有 CSDN 平台官方提供的老师 Wechat / QQ 名片 :) Java精品实战案例《700套》 2025最新毕业设计选题推荐…

强行仅用time.localtime制作“日历牌”——全程记录“顶牛”“调戏”我的AI学习搭子

强行只用time.localtime制作“日历牌”&#xff0c;码好代码试炼通过&#xff0c;想榨取ai智能优化算法&#xff0c;结果失败。本文详细记录“顶牛”全过程。 (笔记模板由python脚本于2024年07月01日 19:16:26创建&#xff0c;本篇笔记适合喜欢python&#xff0c;喜欢搞“事儿”…

p2p、分布式,区块链笔记: 通过libp2p的Kademlia网络协议实现kv-store

Kademlia 网络协议 Kademlia 是一种分布式哈希表协议和算法&#xff0c;用于构建去中心化的对等网络&#xff0c;核心思想是通过分布式的网络结构来实现高效的数据查找和存储。在这个学习项目里&#xff0c;Kademlia 作为 libp2p 中的 NetworkBehaviour的组成。 以下这些函数或…

controller不同的后端路径对应vue前端传递数据发送请求的方式,vue请求参数 param 与data 如何对应后端参数

目录 案例一&#xff1a; 为什么使用post发送请求&#xff0c;参数依旧会被拼接带url上呢&#xff1f;这应该就是param 与data传参的区别。即param传参数参数会被拼接到url后&#xff0c;data会以请求体传递 补充&#xff1a;后端controller 参数上如果没写任何注解&#xff0c…

Redis中hash类型的操作命令(命令的语法、返回值、时间复杂度、注意事项、操作演示)

文章目录 字符串和哈希类型相比hset 命令hget 命令hexistshdelhkeyshvalshgetallhmgethlenhsetnxhincrbyhincrbyfloat 字符串和哈希类型相比 假设有以下一种场景&#xff1a;现在要在 Redis 中存储一个用户的基本信息(id1、namezhangsan、age17)&#xff0c;下图表示使用字符串…

Vue3轻松创建交互式仪表盘

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 基于 Plotly.js 的 Vue 仪表盘组件 应用场景介绍 仪表盘是一种交互式可视化工具&#xff0c;用于监控和分析关键指标。它广泛应用于各种行业&#xff0c;例如金融、医疗保健和制造业。 代码基本功能介绍 本…

Linux源码阅读笔记12-RCU案例分析

在之前的文章中我们已经了解了RCU机制的原理和Linux的内核源码&#xff0c;这里我们要根据RCU机制写一个demo来展示他应该如何使用。 RCU机制的原理 RCU&#xff08;全称为Read-Copy-Update&#xff09;,它记录所有指向共享数据的指针的使用者&#xff0c;当要修改构想数据时&…

搭建论坛和mysql数据库安装和php安装

目录 概念 步骤 安装mysql8.0.30 安装php 安装Discuz 概念 搭建论坛的架构&#xff1a; lnmpDISCUZ l 表示linux操作系统 n 表示nginx前端页面的web服务 m 表示 mysql 数据库 用来保存用户和密码以及论坛的相关内容 p 表示php 动态请求转发的中间件 步骤 &#xff…

基于Cardinal的AWD攻防平台搭建与使用以及基于docker的题目环境部署

关于 CTF 靶场的搭建与完善勇师傅前面已经总结过了&#xff0c;参考&#xff1a; CTF靶场搭建及Web赛题制作与终端docker环境部署_ctfoj搭建-CSDN博客 基于H1ve一分钟搭好CTF靶场-CSDN博客 Nginx首页修改及使用Nginx实现端口转发_nginx 修改欢迎首页-CSDN博客 关于H1ve导…

《IT 领域准新生暑期预习指南:开启未来科技之旅》

IT专业入门&#xff0c;高考假期预习指南 高考的落幕&#xff0c;只是人生长途中的一个逗号&#xff0c;对于心怀 IT 梦想的少年们&#xff0c;新的征程已然在脚下铺展。这个七月&#xff0c;当分数尘埃落定&#xff0c;你们即将迈向新的知识殿堂&#xff0c;而这个假期&#…

235、二叉搜索树的最近公共祖先

给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个结点 p、q&#xff0c;最近公共祖先表示为一个结点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是它自…

代码随想录第42天|动态规划

198.打家劫舍 参考 dp[j] 表示偷盗的总金额, j 表示前 j 间房(包括j)的总偷盗金额初始化: dp[0] 一定要偷, dp[1] 则取房间0,1的最大值遍历顺序: 从小到大 class Solution { public:int rob(vector<int>& nums) {if (nums.size() < 2) {return nums[0];}vector&…

Docker安装PostgreSQL详细教程

本章教程,使用Docker安装PostgreSQL具体步骤。 一、拉取镜像 docker pull postgres二、启动容器 docker run -it --name postgres --restart always -e POSTGRES_PASSWORD=123456 -e

VideoPrism——探索视频分析领域模型的算法与应用

概述 论文地址:https://arxiv.org/pdf/2402.13217.pdf 视频是我们观察世界的生动窗口&#xff0c;记录了从日常瞬间到科学探索的各种体验。在这个数字时代&#xff0c;视频基础模型&#xff08;ViFM&#xff09;有可能分析如此海量的信息并提取新的见解。迄今为止&#xff0c;…

全国数学建模大赛(一)

全国数学建模大赛 &#x1f388;1.数学模型是什么&#xff1f;&#x1f52d;1.1原型与模型&#x1f52d;1.2模型的分类&#x1f52d;1.3数学模型的分类&#x1f52d;1.4数学模型的全过程&#x1f52d;1.5论文写作基本流程&#x1f52d;1.6数学建模的六个步骤&#x1f52d;1.7小…

【SpringBoot配置文件读取】无法读取yaml文件中文字符

1. yaml配置文件 注意要将该文件编码格式改为UTF-8 spring:application:name: 好好学习admin:name: 李斯age: 24books:- name: 数据结构desc: 数据书- name: 编译原理desc: 编译书2.配置实体类 Data设置get&#xff0c;set方法Component注册为BeanConfigurationProperties(p…