【算法详解】数据结构:7种哈希散列算法,你知道几个?

news2024/10/7 8:22:17

一、前言

哈希表的历史

哈希散列的想法在不同的地方独立出现。1953 年 1 月,汉斯·彼得·卢恩 ( Hans Peter Luhn ) 编写了一份IBM内部备忘录,其中使用了散列和链接。开放寻址后来由 AD Linh 在 Luhn 的论文上提出。大约在同一时间,IBM Research的Gene Amdahl、Elaine M. McGraw、Nathaniel Rochester和Arthur Samuel为IBM 701汇编器实现了散列。 线性探测的开放寻址归功于 Amdahl,尽管Ershov独立地有相同的想法。“开放寻址”一词是由W. Wesley Peterson在他的文章中创造的,该文章讨论了大文件中的搜索问题。

二、哈希数据结构

哈希表的存在是为了解决能通过O(1)时间复杂度直接索引到指定元素。

这是什么意思呢?通过我们使用数组存放元素,都是按照顺序存放的,当需要获取某个元素的时候,则需要对数组进行遍历,获取到指定的值。如图所示;

而这样通过循环遍历比对获取指定元素的操作,时间复杂度是O(n),也就是说如果你的业务逻辑实现中存在这样的代码是非常拉胯的。那怎么办呢?这就引入了哈希散列表的设计。


在计算机科学中,一个哈希表(hash table、hash map)是一种实现关联数组的抽象数据结构,该结构将键通过哈希计算映射到值。

也就是说我们通过对一个 Key 值计算它的哈希并与长度为2的n次幂的数组减一做与运算,计算出槽位对应的索引,将数据存放到索引下。那么这样就解决了当获取指定数据时,只需要根据存放时计算索引ID的方式再计算一次,就可以把槽位上对应的数据获取处理,以此达到时间复杂度为O(1)的情况。如图所示;

哈希散列虽然解决了获取元素的时间复杂度问题,但大多数时候这只是理想情况。因为随着元素的增多,很可能发生哈希冲突,或者哈希值波动不大导致索引计算相同,也就是一个索引位置出现多个元素情况。如图所示;

李二狗拎瓢冲都有槽位的下标索引03的 叮裆猫 发生冲突时,情况就变得糟糕了,因为这样就不能满足O(1)时间复杂度获取元素的诉求了。

那么此时就出现了一系列解决方案,包括;HashMap 中的拉链寻址 + 红黑树、扰动函数、负载因子、ThreadLocal 的开放寻址、合并散列、杜鹃散列、跳房子哈希、罗宾汉哈希等各类数据结构设计。让元素在发生哈希冲突时,也可以存放到新的槽位,并尽可能保证索引的时间复杂度小于O(n)

三、实现哈希散列

哈希散列是一个非常常见的数据结构,无论是我们使用的 HashMap、ThreaLocal 还是你在刷题中位了提升索引效率,都会用到哈希散列。

只要哈希桶的长度由负载因子控制的合理,每次查找元素的平均时间复杂度与桶中存储的元素数量无关。另外许多哈希表设计还允许对键值对的任意插入和删除,每次操作的摊销固定平均成本。

好,那么介绍了这么多,小傅哥带着大家做几个关于哈希散列的数据结构,通过实践来了解会更加容易搞懂。

  • 对应源码:java-algorithms/data-structures/src/main/java/hash_table at main · fuzhengwei/java-algorithms · GitHub

1. 哈希碰撞

说明:通过模拟简单 HashMap 实现,去掉拉链寻址等设计,验证元素哈新索引位置碰撞。

public class HashMap01<K, V> implements Map<K, V> {

    private final Object[] tab = new Object[8];

    @Override
    public void put(K key, V value) {
        int idx = key.hashCode() & (tab.length - 1);
        tab[idx] = value;
    }

    @Override
    public V get(K key) {
        int idx = key.hashCode() & (tab.length - 1);
        return (V) tab[idx];
    }

}

  • HashMap01 的实现只是通过哈希计算出的下标,散列存放到固定的数组内。那么这样当发生元素下标碰撞时,原有的元素就会被新的元素替换掉。

测试

@Test
public void test_hashMap01() {
    Map<String, String> map = new HashMap01<>();
    map.put("01", "花花");
    map.put("02", "豆豆");
    logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));
    
    // 下标碰撞
    map.put("09", "蛋蛋");
    map.put("12", "苗苗");
    logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));
}

 

06:58:41.691 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:花花
06:58:41.696 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:苗苗

Process finished with exit code 0
  • 通过测试结果可以看到,碰撞前 map.get("01") 的值是花花,两次下标索引碰撞后存放的值则是苗苗
  • 这也就是使用哈希散列必须解决的一个问题,无论是在已知元素数量的情况下,通过扩容数组长度解决,还是把碰撞的元素通过链表存放,都是可以的。

2. 拉链寻址

说明:既然我们没法控制元素不碰撞,但我们可以对碰撞后的元素进行管理。比如像 HashMap 中拉链法一样,把碰撞的元素存放到链表上。这里我们就来简化实现一下。

public class HashMap02BySeparateChaining<K, V> implements Map<K, V> {

    private final LinkedList<Node<K, V>>[] tab = new LinkedList[8];

    @Override
    public void put(K key, V value) {
        int idx = key.hashCode() & (tab.length - 1);
        if (tab[idx] == null) {
            tab[idx] = new LinkedList<>();
            tab[idx].add(new Node<>(key, value));
        } else {
            tab[idx].add(new Node<>(key, value));
        }
    }

    @Override
    public V get(K key) {
        int idx = key.hashCode() & (tab.length - 1);
        for (Node<K, V> kvNode : tab[idx]) {
            if (key.equals(kvNode.getKey())) {
                return kvNode.value;
            }
        }
        return null;
    }

    static class Node<K, V> {
        final K key;
        V value;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value;
        }

    }

}

  • 因为元素在存放到哈希桶上时,可能发生下标索引膨胀,所以这里我们把每一个元素都设定成一个 Node 节点,这些节点通过 LinkedList 链表关联,当然你也可以通过 Node 节点构建出链表 next 元素即可。
  • 那么这时候在发生元素碰撞,相同位置的元素就都被存放到链表上了,获取的时候需要对存放多个元素的链表进行遍历获取。

测试

@Test
public void test_hashMap02() {
    Map<String, String> map = new HashMap02BySeparateChaining<>();
    map.put("01", "花花");
    map.put("05", "豆豆");
    logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));
    
    // 下标碰撞
    map.put("09", "蛋蛋");
    map.put("12", "苗苗");
    logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));
}

 

07:21:16.654 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:花花
07:22:44.651 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:花花

Process finished with exit code 0
  • 此时第一次和第二次获取01位置的元素就都是花花了,元素没有被替代。因为此时的元素是被存放到链表上了。

3. 开放寻址

说明:除了对哈希桶上碰撞的索引元素进行拉链存放,还有不引入新的额外的数据结构,只是在哈希桶上存放碰撞元素的方式。它叫开放寻址,也就是 ThreaLocal 中运用斐波那契散列+开放寻址的处理方式。

public class HashMap03ByOpenAddressing<K, V> implements Map<K, V> {

    private final Node<K, V>[] tab = new Node[8];

    @Override
    public void put(K key, V value) {
        int idx = key.hashCode() & (tab.length - 1);
        if (tab[idx] == null) {
            tab[idx] = new Node<>(key, value);
        } else {
            for (int i = idx; i < tab.length; i++) {
                if (tab[i] == null) {
                    tab[i] = new Node<>(key, value);
                    break;
                }
            }
        }
    }

    @Override
    public V get(K key) {
        int idx = key.hashCode() & (tab.length - 1);
        for (int i = idx; i < tab.length; i ++){
            if (tab[idx] != null && tab[idx].key == key) {
                return tab[idx].value;
            }
        }
        return null;
    }

    static class Node<K, V> {
        final K key;
        V value;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }

    }

}

  • 开放寻址的设计会对碰撞的元素,寻找哈希桶上新的位置,这个位置从当前碰撞位置开始向后寻找,直到找到空的位置存放。
  • 在 ThreadLocal 的实现中会使用斐波那契散列、索引计算累加、启发式清理、探测式清理等操作,以保证尽可能少的碰撞。

测试

@Test
public void test_hashMap03() {
    Map<String, String> map = new HashMap03ByOpenAddressing<>();
    map.put("01", "花花");
    map.put("05", "豆豆");
    logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));
    // 下标碰撞
    map.put("09", "蛋蛋");
    map.put("12", "苗苗");
    logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));
}

 

07:20:22.382 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:花花
07:20:22.387 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:花花
07:20:22.387 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 数据结构:HashMap{tab=[null,{"key":"01","value":"花花"},{"key":"09","value":"蛋蛋"},{"key":"12","value":"苗苗"},null,{"key":"05","value":"豆豆"},null,null]}

Process finished with exit code 0
  • 通过测试结果可以看到,开放寻址对碰撞元素的寻址存放,也是可用解决哈希索引冲突问题的。

4. 合并散列

说明:合并散列是开放寻址和单独链接的混合,碰撞的节点在哈希表中链接。此算法适合固定分配内存的哈希桶,通过存放元素时识别哈希桶上的最大空槽位来解决合并哈希中的冲突。

public class HashMap04ByCoalescedHashing<K, V> implements Map<K, V> {

    private final Node<K, V>[] tab = new Node[8];

    @Override
    public void put(K key, V value) {
        int idx = key.hashCode() & (tab.length - 1);
        if (tab[idx] == null) {
            tab[idx] = new Node<>(key, value);
            return;
        }

        int cursor = tab.length - 1;
        while (tab[cursor] != null && tab[cursor].key != key) {
            --cursor;
        }
        tab[cursor] = new Node<>(key, value);

        // 将碰撞节点指向这个新节点
        while (tab[idx].idxOfNext != 0){
            idx = tab[idx].idxOfNext;
        }

        tab[idx].idxOfNext = cursor;
    }

    @Override
    public V get(K key) {
        int idx = key.hashCode() & (tab.length - 1);
        while (tab[idx].key != key) {
            idx = tab[idx].idxOfNext;
        }
        return tab[idx].value;
    }

    static class Node<K, V> {
        final K key;
        V value;
        int idxOfNext;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }

    }

}

  • 合并散列的最大目的在于将碰撞元素链接起来,避免因为需要寻找碰撞元素所发生的循环遍历。也就是A、B元素存放时发生碰撞,那么在找到A元素的时候可以很快的索引到B元素所在的位置。

测试

07:18:43.613 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:花花
07:18:43.618 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:苗苗
07:18:43.619 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 数据结构:HashMap{tab=[null,{"idxOfNext":7,"key":"01","value":"花花"},null,null,null,{"idxOfNext":0,"key":"05","value":"豆豆"},{"idxOfNext":0,"key":"12","value":"苗苗"},{"idxOfNext":6,"key":"09","value":"蛋蛋"}]}

Process finished with exit code 0
  • 相对于直接使用开放寻址,这样的挂在链路指向的方式,可以提升索引的性能。因为在实际的数据存储上,元素的下一个位置不一定空元素,可能已经被其他元素占据,这样就增加了索引的次数。所以使用直接指向地址的方式,会更好的提高索引性能。

5. 杜鹃散列

说明:这个名字起的比较有意思,也代表着它的数据结构。杜鹃鸟在孵化🐣的时候,雏鸟会将其他蛋或幼崽推出巢穴;类似的这个数据结构会使用2组key哈希表,将冲突元素推到另外一个key哈希表中。

private V put(K key, V value, boolean isRehash) {
    Object k = maskNull(key);
    if (containsKey(k)) {
        return null;
    }
    if (insertEntry(new Entry<K, V>((K) k, value))) {
        if (!isRehash) {
            size++;
        }
        return null;
    }
    rehash(2 * table.length);
    return put((K) k, value);
}

private boolean insertEntry(Entry<K, V> e) {
    int count = 0;
    Entry<K, V> current = e;
    int index = hash(hash1, current.key);
    while (current != e || count < table.length) {
        Entry<K, V> temp = table[index];
        if (temp == null) {
            table[index] = current;
            return true;
        }
        table[index] = current;
        current = temp;
        if (index == hash(hash1, current.key)) {
            index = hash(hash2, current.key);
        } else {
            index = hash(hash1, current.key);
        }
        ++count;
    }
    return false;
}

 

  • 当多个键映射到同一个单元格时会发生这种情况。杜鹃散列的基本思想是通过使用两个散列函数而不是仅一个散列函数来解决冲突。
  • 这为每个键在哈希表中提供了两个可能的位置。在该算法的一种常用变体中,哈希表被分成两个大小相等的较小的表,每个哈希函数都为这两个表之一提供索引。两个散列函数也可以为单个表提供索引。
  • 在实践中,杜鹃哈希比线性探测慢约 20-30%,线性探测是常用方法中最快的。然而,由于它对搜索时间的最坏情况保证,当需要实时响应率时,杜鹃散列仍然很有价值。杜鹃散列的一个优点是它的无链接列表属性,非常适合 GPU 处理。

测试

07:52:04.010 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:花花
07:52:04.016 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:苗苗
07:52:04.016 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 数据结构:{01=花花, 12=苗苗, 05=豆豆, 09=蛋蛋}

Process finished with exit code 0
  • 从测试结果可以看到,杜鹃散列可以通过两个散列函数解决索引冲突问题。不过这个探测的过程比较耗时。

6. 跳房子散列

说明:跳房子散列是一种基于开放寻址的算法,它结合了杜鹃散列、线性探测和链接的元素,通过桶邻域的概念——任何给定占用桶周围的后续桶,也称为“虚拟”桶。 该算法旨在在哈希表的负载因子增长超过 90% 时提供更好的性能;它还在并发设置中提供了高吞吐量,因此非常适合实现可调整大小的并发哈希表。

public boolean insert(AnyType x) {
    if (!isEmpty()) {
        return false;
    }
    int currentPos = findPos(x);
    if (currentPos == -1) {
        return false;
    }
    if (array[currentPos] != null) {
        x = array[currentPos].element;
        array[currentPos].isActive = true;
    }
    String hope;
    if (array[currentPos] != null) {
        hope = array[currentPos].hope;
        x = array[currentPos].element;
    } else {
        hope = "10000000";
    }
    array[currentPos] = new HashEntry<>(x, hope, true);
    theSize++;
    return true;
}

  • 该算法使用一个包含n 个桶的数组。对于每个桶,它的邻域是H个连续桶的小集合(即索引接近原始散列桶的那些)。邻域的期望属性是在邻域的桶中找到一个项目的成本接近于在桶本身中找到它的成本(例如,通过使邻域中的桶落在同一缓存行中)。在最坏的情况下,邻域的大小必须足以容纳对数个项目(即它必须容纳 log( n ) 个项目),但平均只能是一个常数。如果某个桶的邻域被填满,则调整表的大小。

测试

@Test
public void test_hashMap06() {
    HashMap06ByHopscotchHashing<Integer> map = new HashMap06ByHopscotchHashing<>();
    map.insert(1);
    map.insert(5);
    map.insert(9);
    map.insert(12);
    logger.info("数据结构:{}", map);
}

 

17:10:10.363 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 数据结构:HashMap{tab=[null,{"element":1,"hope":"11000000","isActive":true},{"element":9,"hope":"00000000","isActive":true},null,{"element":12,"hope":"10000000","isActive":true},{"element":5,"hope":"10000000","isActive":true},null,null]}

Process finished with exit code 0
  • 通过测试可以看到,跳房子散列会在其原始散列数组条目中找到,或者在接下来的H-1个相邻条目之一找到对应的冲突元素。

7. 罗宾汉哈希

说明:罗宾汉哈希是一种基于开放寻址的冲突解决算法;冲突是通过偏向从其“原始位置”(即项目被散列到的存储桶)最远或最长探测序列长度(PSL)的元素的位移来解决的。

public void put(K key, V value) {
    Entry entry = new Entry(key, value);
    int idx = hash(key);
    // 元素碰撞检测
    while (table[idx] != null) {
        if (entry.offset > table[idx].offset) {
            // 当前偏移量不止一个,则查看条目交换位置,entry 是正在查看的条目,增加现在搜索的事物的偏移量和 idx
            Entry garbage = table[idx];
            table[idx] = entry;
            entry = garbage;
            idx = increment(idx);
            entry.offset++;
        } else if (entry.offset == table[idx].offset) {
            // 当前偏移量与正在查看的检查键是否相同,如果是则它们交换值,如果不是,则增加 idx 和偏移量并继续
            if (table[idx].key.equals(key)) {
                // 发现相同值
                V oldVal = table[idx].value;
                table[idx].value = value;
            } else {
                idx = increment(idx);
                entry.offset++;
            }
        } else {
            // 当前偏移量小于我们正在查看的我们增加 idx 和偏移量并继续
            idx = increment(idx);
            entry.offset++;
        }
    }
    // 已经到达了 null 所在的 idx,将新/移动的放在这里
    table[idx] = entry;
    size++;
    // 超过负载因子扩容
    if (size >= loadFactor * table.length) {
        rehash(table.length * 2);
    }
}

 

  • 09、12 和 01 发生哈希索引碰撞,进行偏移量计算调整。通过最长位置探测碰撞元素位移来处理。

测试

public void test_hashMap07() {
    Map<String, String> map = new HashMap07ByRobinHoodHashing<>();
    map.put("01", "花花");
    map.put("05", "豆豆");
    logger.info("碰撞前 key:{} value:{}", "01", map.get("01"));
    // 下标碰撞
    map.put("09", "蛋蛋");
    map.put("12", "苗苗");
    logger.info("碰撞前 key:{} value:{}", "01", map.get("12"));
    logger.info("数据结构:{}", map);
}

 

07:34:32.593 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:花花
09 1
12 1
01 1
09 9
12 1
05 5
07:35:07.419 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 碰撞前 key:01 value:苗苗
07:35:07.420 [main] INFO cn.bugstack.algorithms.test.AlgorithmsTest - 数据结构:HashMap{tab=[null,{"key":"01","offset":0,"value":"花花"},{"key":"12","offset":1,"value":"苗苗"},null,null,{"key":"05","offset":0,"value":"豆豆"},null,null,null,{"key":"09","offset":0,"value":"蛋蛋"},null,null,null,null,null,null]}

Process finished with exit code 0
  • 通过测试结果和调试的时候可以看到,哈希索引冲突是通过偏向从其“原始位置”(即项目被散列到的存储桶)最远或最长探测序列长度(PSL)的元素的位移来解决。这块可以添加断点调试验证。

四、常见面试问题

  • 介绍一下散列表
  • 为什么使用散列表
  • 拉链寻址和开放寻址的区别
  • 还有其他什么方式可以解决散列哈希索引冲突
  • 对应的Java源码中,对于哈希索引冲突提供了什么样的解决方案

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

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

相关文章

项目进度管理

第3 章 项目进度管理 3.1 概述 1.项目进度管理是指在项目实施过程中&#xff0c;对各阶段的进展程度和项目最终完成的期限所进行的管理&#xff0c;是在 规定的时间内&#xff0c;拟定出合理且经济的进度计划&#xff08;包括多级管理的子汁划)&#xff0c;在执行该计划的过程…

常见的限流算法的原理以及优缺点

原文网址&#xff1a;常见的限流算法的原理以及优缺点_IT利刃出鞘的博客-CSDN博客 简介 说明 本文介绍限流常用的算法及其优缺点。 常用的限流算法有&#xff1a; 计数器&#xff08;固定窗口&#xff09;算法滑动窗口算法漏桶算法令牌桶算法 下面将对这几种算法进行分别介绍…

tmux的简单使用

文章目录一、认识tmux1.1 会话1.2 tmux的作用1.3 tmux的安装二、tmux的使用2.1 会话管理2.1.1 创建会话2.1.2 退出会话2.1.3 从终端环境进入会话2.1.4 查看会话列表2.1.5 销毁会话2.1.6 重命名会话2.2 窗口管理2.3 窗格管理一、认识tmux 1.1 会话 命令行的典型使用方式是&…

rocketmq是如何消费

拉取消息的请求都在pullRequestQueue队列里&#xff0c; 拉取消息成功后设置下一次需要拉取的offset&#xff0c; boolean dispatchToConsume processQueue.putMessage(pullResult.getMsgFoundList()); 这个方法会把拉取回来的消息放进msgTreeMap里面 然后消费拉取回来的消…

MongoDB副本集成员如何复制新数据

复制是指在多台服务器上保持相同的数据副本。MongoDB 实现此功能的方式是保存操作日志&#xff08;oplog&#xff09;&#xff0c;其中包含了主节点执行的每一次写操作。oplog 是存在于主节点 local 数据库中的一个固定集合。从节点通过查询此集合以获取需要复制的操作。 每个…

Solving Inverse Problems With Deep_Neural Networks – Robustness Included_

作者&#xff1a;Martin Genzel, Jan Macdonald, and Maximilian Marz期刊&#xff1a;preprint arXiv时间&#xff1a;2020代码链接&#xff1a;代码论文链接&#xff1a;论文 1 动机与研究内容 最近工作发现深度神经网络对于图像重构的不稳定(instabilities)&#xff0c;以…

记一次漏洞挖掘【网络安全】

漏洞信息 从CVE-2019-10999查看该CVE的基础信息得知&#xff0c;这是一个栈溢出漏洞&#xff0c;攻击者在已登录的情况下可以通过向wireless.htm发送一个超长的WEPEncryption参数导致栈溢出&#xff0c;从而执行任意命令攻击. 现在我们利用Shambles Desktop工具确定这个漏洞的…

单商户商城系统功能拆解23—用户标签

单商户商城系统&#xff0c;也称为B2C自营电商模式单店商城系统。可以快速帮助个人、机构和企业搭建自己的私域交易线上商城。 单商户商城系统完美契合私域流量变现闭环交易使用。通常拥有丰富的营销玩法&#xff0c;例如拼团&#xff0c;秒杀&#xff0c;砍价&#xff0c;包邮…

在Windows使用VSCode搭建嵌入式Linux开发环境

在Windows使用VSCode搭建嵌入式Linux开发环境 百问网已经制作好了完备的Ubuntu镜像&#xff0c;可以从这里下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1vw4VUV_Mvt0HXz8IC66ACg 提取码&#xff1a;iftb 我们也正在(2022.10.17开始)使用纯粹的Ubuntu环境开始…

孙宇晨2022釜山区块链周演讲:区块链是未来数字城市发展基石

据韩国媒体全球经济新闻10月27日报道&#xff0c;波场TRON创始人孙宇晨线上出席了2022 釜山区块链周&#xff08;Blockchain Week in Busan 2022&#xff0c;以下简称BWB 2022&#xff09;&#xff0c;并发表《 The Cities of Tomorrow》主题演讲。孙宇晨表示&#xff0c;“区块…

生产环境数据库表迁移实践集锦

数据库表迁移是开发者必须要掌握的一种能力&#xff0c;对中高级开发者来说更是如此。工作中随着公司业务不断发展、系统架构的慢慢调整演化&#xff0c;迁移数据库表是不可避免的。由于数据是公司最最核心的资产&#xff0c;所以对生产环境数据库表的迁移并不是谁都可以去实施…

Web服务器、Ftp服务器、DNS服务器搭建【高级路由协议与实验04-2】

上一篇自学练习了如何搭建web服务器 文章目录网络设置1.修改配置文件2.改成桥接模式访问外网3.选择网卡4.重启网卡一、web服务器补充&#xff1a;步骤1.安装httpd软件包2.将httpd服务设为开始自启动&#xff0c;并启动该服务3.查看httpd服务是否启动4.在防火墙开放80端口并查看…

小白学习spring第一天

第二章&#xff1a;Spring 第1节&#xff1a;概述 1.1 介绍 heap stack Spring是一个分层的Java SE/EE full-stack&#xff08;一站式&#xff09;轻量级开源框架&#xff0c;以 IoC&#xff08;Inverse Of Control&#xff1a;控制反转&#xff09;和 AOP&#xff08;Aspec…

力扣刷题day32|738单调递增的数字、714买卖股票的最佳时机含手续费、968监控二叉树

文章目录738. 单调递增的数字思路难点&#xff1a;遍历顺序难点&#xff1a;设置flag714. 买卖股票的最佳时机含手续费贪心思路难点968. 监控二叉树思路难点&#xff1a;如何隔两个节点放一个摄像头738. 单调递增的数字 力扣题目链接 当且仅当每个相邻位数上的数字 x 和 y 满…

常用算法———P I D控制算法(P I D三个参数的作用和两种P I D算法的代码实现)

如果有错误请及时指出&#xff0c;大家一起学习交流。 目录 一、PID的概述 二、PID三个参数的控制原理 1、P控制器 2、I控制器 3、D控制器 4、PID控制器 5、PID的数学公式 三、位置式PID和增量式PID的差别和代码实现 1、位置式PID和增量式PID的差别 2、位置式PID …

(附源码)计算机毕业设计SSM竞赛报名管理系统

&#xff08;附源码&#xff09;计算机毕业设计SSM竞赛报名管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&…

mindspore实现自定义CNN图像分类模型

一、数据集定义 使用mindspore.dataset中的ImageFolderDataset接口加载图像分类数据集&#xff0c;ImageFolderDataset接口传入数据集文件上层目录&#xff0c;每个子目录分别放入不同类别的图像。使用python定义一个create_dataset函数用于创建数据集&#xff0c;在函数中使用…

[C++基础]-初识模板

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。 目录 一、泛型编…

【正点原子STM32连载】第五十五章 T9拼音输入法实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

1&#xff09;实验平台&#xff1a;正点原子MiniPro H750开发板 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id677017430560 3&#xff09;全套实验源码手册视频下载地址&#xff1a;http://www.openedv.com/thread-336836-1-1.html 4&#xff…

deepwalknode2vec 代码实战

提示&#xff1a;笔记内容来自于B站up主同济子豪兄 文章目录1. Embedding嵌入的艺术2. deepwalk2.1. 什么是图嵌入&#xff1f;2.2. deepwalk的步骤1、生成graph&#xff1b;2、利用random walk生成多个路径&#xff1b;3、训练表示向量的学习&#xff1b;4、为了解决分类个数过…