【数据结构】Map和Set

Map和Set

1. 搜索树

1.1 概念

二叉搜索树是左子树比根节点小,右子树比根节点大的二叉树。(如果左右子树不为空的话是这样,但是左右子树也可以为空)

1.2 操作——查找

查找的思想与二分查找类似。

如果根节点的值和所要查找的值相同,那么就返回。

如果根节点的值比查找的值小,那么就往这个结点的右子树走。

如果根节点的值比查找的值大,那么就往这个结点的左子树走。

(每次都可以筛选掉一半的数据)

1.2.1 代码
/**
 * 操作——搜索
 * @param val 搜索的值
 * @return boolean
 */
public boolean search(int val) {
// 1. 定义一个cur进行遍历
TreeNode cur = root;
// 2. 比较
while (cur != null) {
    if (cur.val < val) {
        cur = cur.right;
    } else if (cur.val > val) {
        cur = cur.left;
    } else  {
        return true;
    }
}
return false;
}

1.3 操作——插入

插入和查找差不多。

先找到要插入数据应该存放的位置(一定会是叶子结点)

然后看是放在这个结点的左边还是右边。

/**
 * 操作——插入
 * @param val
 */
public boolean insert(int val) {
    // 1. 如果是空树
    if (root == null) {
        root = new TreeNode(val);
        return true;
    }
    // 2. 定义一个cur进行遍历,一个parent进行保存上一步cur的位置
    TreeNode cur = root;
    TreeNode parent = cur;

    while (cur != null) {
        // 保存这次的cur结点,方便后续的插入结点
        parent = cur;
        if (cur.val < val) {
            cur = cur.right;
        } else if (cur.val > val) {
            cur = cur.left;
        } else  {
            return false;
        }
    }

    if (parent.val < val) {
        parent.right = new TreeNode(val);
    } else {
        parent.left = new TreeNode(val);
    }
    return true;
}

1.4 操作——删除(难点)

删除的情况分为三种:

规定 node为将要删除的结点,parent 为要删除结点的父节点。

  1. node.left = null

    1.1 如果 node是根节点,root.right = node.right;

    1.2 如果node不是根节点,是parent 的右子树, parent.right = node.right

    1.3 如果node不是根节点,是parent 的左子树, parent.left= node.left

  2. node.right = null

    1.1 如果 node是根节点,root.left= node.left;

    1.2 如果node不是根节点,是parent 的右子树, parent.right = node.right

    1.3 如果node不是根节点,是parent 的左子树, parent.left= node.left

  3. node.right != null && node.left != null

    使用 “替罪羊” 方式进行删除:并灭有真正删除node结点,而是将node左子树的最大值进行替换上去,然后

/**
 * 删除的子函数
 * @param node
 * @param parent
 */
public void removeNode(TreeNode node, TreeNode parent) {
    // 叶子节点——直接删除
    if (node.left == null && node.right == null) {
        node = null;
    }
    // 1. node没有左子树
    if (node.left == null) {
        if (node == parent.left) {
            parent.left = node.right;
        } else {
            parent.right = node.left;
        }
    // 2. node 没有右子树
    } else if (node.right == null) {
        if (node == parent.left) {
            parent.left = node.right;
        } else {
        parent.right = node.left;
        }
    // 3. node 左右子树都有
        // “替罪羊” 删除方式
    } else {
        // 找到node左子树的最右边,即为左子树中最大的值,进行填补;也可以选择右子树的最大值进行填补(最左边
        // target即为替罪羊
        TreeNode target = node.left;
        TreeNode targetParent = target;
        while (target.right != null) {
            targetParent = target;
            target = target.right;
        }
        // 到达最右边
        node.val = target.val;// “虚假”的删除
        targetParent.right = target.left;// target没有右子树,直接接管左子树即可
    }
}

1.5 性能分析

最好的情况:为平衡二叉树的时候,即为log2N。

最坏的情况:为单分支树的时候,即为N(产生了AVL树来防止这种情况的发生,其可以左旋,右旋,左右双旋,右左双旋)

2. 搜索

2.1 场景

在以往学过的搜索思想中,我们只学过二分查找(log2N,必须是有序的数组)和直接遍历(n,对数据无要求,效率低下),这种思想对于静态的数据能够有效地检索,但是无法进行删除、添加操作,但是,Map和Set可以进行动态的操作。

2.2 模型

Map:存储的是key-value键值对,key就是所要存储的数据据,value就是这个数据所附带的值(可以是这个key出现的次数,也可以是这个key的关键字等信息)。

Set:存储的是key的集合,不能够有重复,底层仍然使用Map进行初始化。

3. Map的使用

3.1 关于Map的说明

Map 是单独的一个接口,没有继承自Collection接口,并且存储的类型是<key, value>,key不可以重复(搜索的时候就是按照key的值进行搜索,重复便不能够进行搜索)

3.2 关于Map.Entry<K, V>的说明

Entry<K, V> 是Map中的一个内部接口,其能够返回Map的key以及value,并且能够设置value(但是没有提供设置key的方法),Entry<K, V>返回的是一个包含<K, V>键值对的对象,能够用其初始化Iterator,可直接定义变量。

Map.Entry<K, V>能够表示Map的映射项,而Map又没有实现iterator,所以通常用来遍历Map。

Map中有**values()方法来获得value,有keySet()**方法来获得key的一个Set,但是都一次只能访问一个内容,Entry<K, V>很好地实现了这个一次访问两个的效果。

方法解释
K getKey()获得Map中的key
V getValue()获得Map中的value
V setValue(V value);设置Map中的value

使用方法:

Map<String, Integer> map = new TreeMap<>();
map.put("初中读了几年",3);
map.put("高中读了几年",3);
map.put("大学读了几年",2);

for (Map.Entry<String,Integer> entry1 : map.entrySet()) {
    System.out.println("String:" + entry1.getKey() + " Value:" + entry1.getValue());
}

// Map.Entry<String,Integer>得到的是一个<String,Integer>的变量,可直接看作<String,Integer>
Map.Entry<String,Integer> entry;
Iterator<Map.Entry<String,Integer>> it = map.entrySet().iterator();
it.next().setValue(99);

for (Map.Entry<String,Integer> entry2 : map.entrySet()) {
    System.out.println("String:" + entry2.getKey() + " Value:" + entry2.getValue());
}
3.3 Map的常用方法说明
方法解释
V put(K key, V value);将K,V的元素放进Map
V get(Object key);得到Map中某个key对应的value
Set keySet();返回一个全是key的Set
V remove(Object key);删除key结点
Set<Map.Entry<K, V>> entrySet();返回<K, V>键值对的Set集合(可用<K, V>键值对遍历)
boolean containsKey(Object key);返回Map中是否存在这个key
boolean containsValue(Object value);返回Map中是否存在这个value
default V getOrDefault(Object key, V defaultValue);返回Map中是否存在这个key,如果不存在,则返回defaultValue
  • 注意:
  1. 关于Set<Map.Entry<K, V>> entrySet()产生的代码如下,
Set<Map.Entry<String, Integer>> set = map.entrySet();
for(Map.Entry<String, Integer> e : set) {
    System.out.println("String:" + e.getKey() + " Value:" + e.getValue());
}
  1. Map有两种实现类,分别是HashMap和TreeMap。

    Map底层结构TreeMapHashMap
    底层结构红黑树哈希桶
    插入/删除/查找时间复杂度O(log2N)O(1)
    是否有序有序(插入的时候对于key进行了排序)无序(存储是按照HashCode进行存储)
    线程安全不安全不安全
    插入/查找/删除区别按照key值插入,进行了元素之间的比较通过哈希函数,按照哈希地址插入
    比较与覆写key值必须能够比较,否则抛出ClassCastException异常(需要实现Comparable/Comparator接口)自定义类型需要覆写equals和 hashCode方法(需要利用哈希函数进行插入)
    应用场景需要key有序的时候更需要效率的时候
  2. Map中不存在两个相同的key值,但是value可以重复,当多次put进同一个key时,会更新这个key的值。

  3. 在TreeMap中key值不能为空,但是HashMap的key可以为空

    主要是因为TreeMap中,put方法会调用比较器函数,如果传过来的是一个null,那么也就无法比较。

    但是HashMap中,虽然用的是哈希函数,但是在哈希函数的内部使用了hash()方法,里面对于key为null的情况作了处理:如果key为null,那么就将其置为0。(但是只能有一个key为0的结点,因为同样,一个0只能计算出一个hash地址,重复插入key为null的结点,只会更新其value值)

  4. Map中的key不能修改,要修改只能删除后再添加进行修改

4. Set的使用

Set与Map的不同点在于Set只存储key,并且Set继承自Collection接口。

4.1 常见方法说明

方法解释
boolean add(E e);向集合中添加元素e
Object[] toArray();将集合变为数组
boolean contains(Object o);查看集合中是否存在o元素
Iterator iterator();返回迭代器
boolean containsAll(Collection<?> c);如果调用方包含c中的所有元素则返回true
boolean addAll(Collection<? extends E> c);将集合c中的所有元素加到调用方中,可以达到去重的效果
boolean retainAll(Collection<?> c);使调用方成为与c的子集

注意:

  1. Set中的key不能修改,要修改只能删除后再添加进行修改

  2. Set的key是唯一值

  3. TreeSet的底层是使用TreeMap进行初始化的,仍然使用键值对进行初始化,但是只传入了Set中的K,另外一个使用了Object对象。

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
    
  4. 同Map,TreeSet不能插入null的key,但是HashSet可以。

  5. Set的实现类有HashSet、TreeSet、LinkedHashSet(能够记录插入次序、继承了HashMSet、HashSet中有HashMap、HashMap中有这个结点,所以能够记录次序)

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
    
  6. Set最大的功能就是对元素进行去重

    Set底层结构TreeSetHashSet
    底层结构红黑树哈希桶
    插入/删除/查找时间复杂度O(log2N)O(1)
    是否有序关于key有序b不一定有序
    插入/删除/查找区别利用红黑树进行操作计算出哈希地址后才会进行对应的操作
    比较与覆写key必须能够比较,否则抛异常:ClassCastException自定义类型需要覆写equals和HashCode方法
    应用场景需要key有序查找效率为先

5. 哈希表

在以往的搜索方法中,都是需要遍历(直接搜索、二分查找)进行,但是理想的搜索方式是不需要遍历,直接能够找到,就像抓中药的时候,药师会直接抽出一个箱子,取出对应的中药,而不是一个一个箱子拉开看是不是符合自己需要的中药。

所以,哈希表也应该像中药一样,能够建立起中药和中药箱上的关系,使得药师能够根据药箱的外表就能够找到中药。所以哈希表也需要有一个映射关系,使得数据的存储位置和它的关键码能够产生联系。

实现思路:

插入元素:

根据待插入元素的关键码,根据某个计算方法计算出一个地址,按此地址进行存放。

搜索元素:

对于待搜索元素的关键码进行同样的计算,根据得到的地址进行查找。

上述的计算方法即为哈希方法,地址即为哈希地址,数据按此方式进行存放组成的结构叫做散列表(HashTable)。

5.2 冲突——概念

当进行插入的时候,势必会对多个插入元素计算出相同的哈希地址,比如我们要存储int,采用的哈希函数是:hash(key) = key % array,length;那么当我们数组长度为10,插入一个4,再插入一个14的时候,他俩都需要往下标为4的地址上存放,这样的情况就叫做哈希冲突。

5.3 冲突——避免

我们可以采用更改哈希函数、增大容器容量等方法来进行避免冲突,但是这只是一时的避免,在日后的数据越来越多的情况下,势必会再次出现冲突,那时候就又需要进行处理新的冲突。

5.3.4 冲突——避免——哈希函数设计

引起冲突的一个原因可能就是哈希函数设置的不够合理,就比如上述:hash(key) = key % array,length这个函数简单易懂,但是去容易产生冲突,如果我们更改为:hash(key) = (key + i^2) % array,length(其中 i 为插入数据的个数)那就降低了冲突的可能性。

哈希函数的设计原则:

  1. 应该尽可能的简单

  2. 函数计算出来的地址应该能够使得数据能够均匀的分布在散列表当中

  3. 哈希函数的定义域必须包括预备存储的所有数据的关键码(这样才能对于所有的数据进行计算),

    而且值域(算出来的哈希地址)必须在散列表的容量之中。

常见的哈希函数:

  1. 直接定制法:(常用)

    使用线性函数:hash(key) = a * key +b

    优点:简单均匀

    缺点:需要事先知道数据的分布才能实现函数

  2. 除留余数法:(常用)

    hash(key) = key % array.length

    优点:简单

    缺点:如果数据分散,则空间利用率低,适合处理数据集中的情况

  3. 平方取中法(了解)

    对数据进行平方后,取中间的x位作为哈希地址。

    适合数据不是很大,又不知道其分布的情况

  4. 折叠法(了解)

    将数据折叠成几段长度相等的部分,然后叠加求和,对于后几位按照散列表的长度进行取余。

  5. 随机数法(了解)

    hash(key) = random(key)

  6. 数学分析法(了解)

    通过对于数据的分析,选择出不宜重复的几项,然后将其选做哈希地址。

    比如:如果要存储公司里员工及其手机号,那么就可以选择使用后四位作为散列表地址。

5.5 冲突——避免——负载因子调节(重点掌握)

负载因子:α = 已存储的数据个数 / 散列表的长度

一般负载因子不会超过0.8,超过0.8就会频繁出现冲突问题。

所以当负载因子超过0.8的时候,我们就需要对于散列表进行调整。

5.6 冲突——解决

常见的两种方法:开散列和闭散列。

5.7 闭散列

闭散列:也叫开放地址法,当发生冲突的时候,如果哈希表未被装满,那就可以把发生冲突的数据存放到下一个空位置

  1. 线性探测
    在这里插入图片描述

这种处理方式的坏处是不能够随便删除元素,比如下表存储了12在2的后面,如果把2删除,那么32就会找不到,因为这种存储方式是通过依次遍历空位置来查找元素的,当22删除后,那个位置就为空,32就被误认为是没有尾数为2的元素的。
在这里插入图片描述


  1. 二次探测(二次方)

    线性探测对于在哈希地址同一个位置的数据进行了简单的处理,但是这种方式过于粗暴(直接挨着往后找),所以二次探测找下一个空位置的方式:Hi = (H0 ± i2) % array.length,其中 i = 1,2,3…,H0 为发生冲突的为位置。

    当发生冲突时,先取 i=1 试试看,如果这个位置仍然有元素,那就取 i = 2 看。

    在这里插入图片描述

研究表明:当表的长度为质数且表装载因子a不超过0.5时,新的表项一定能够插入,而且任何一个位置都不 会被探查两次。因此只要表中有一半的空位置,就不会存在表满的问题。在搜索时可以不考虑表装满的情况,但在插入时必须确保表的装载因子a不超过0.5,如果超出必须考虑增容(增容后,全部元素需要重新哈希)

综上,闭散列的最大缺陷就是空间利用率低,这也是哈希的缺陷。

5.8 开散列/哈希桶

开散列法又叫链地址法,因为开散列是通过一个每个元素是一个链表结点的数组(哈希表)来存储数据的。

首先对关键码利用哈希函数进行计算散列地址,如果有相同的地址,那就归于一个下标,在这个下表下有一个单链表,多出来的元素往上串即可,各链表的头结点都在哈希表中。

在开散列中进行搜索是先找到下标,然后在这个下标存储的链表中进行遍历查找所需元素。

5.9 冲突严重时的解决办法

  1. 每个桶的背后是另一张哈希表
  2. 每个桶的背后是一棵搜索树

5.10 实现

public class HashBucket {
    // 一个结点就是一个桶,每个桶里装单链表的头结点
    static class Node {
        private int key;
        private int value;
        Node next;

        public Node() {
        }

        public Node(int key, int value, Node next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

    public Node[] array;
    public int size;
    public static final double LOAD_FACTOR = 0.75;

    /**
     * 构造函数
     */
    public HashBucket() {
        array = new Node[8];
        size = 0;
    }
    /**
     * 插入函数
     * @param key
     * @param value
     */
    public int put(int key, int value) {
        // 这里使用最简单粗暴的哈希函数
        int index = key % array.length;

        //先找是否已经存在key
        for (Node node = array[index]; node != null; node = node.next) {
            if (node.key == key) {
                int oldValue = node.value;
                node.value = value;
                // 返回更新前的值
                return oldValue;
            }
        }


        Node newNode = new Node(key, value, null);

        if (array[index] == null) {
            array[index] = newNode;
        } else {
          // 尾插
          Node tail = array[index];
          while (tail.next != null) {
              tail = tail.next;
          }
          tail.next = newNode;
        }
        size++;

        // 判断是否需要扩容
        if (loadFactor() > LOAD_FACTOR) resize();// 使用函数的目的是为了每次进行判断都能重新计算
        return value;
    }

    double loadFactor() {
        return size * 1.0 / array.length;
    }
    /**
     * 扩容函数
     */
    private void resize() {
        // 扩容需要将全部元素进行重新哈希
        Node[] newArray = new Node[array.length * 2];

        /*这种方式连着空位置一起进行了迁移
        for (int i = 0; i < array.length; i++) {
            int newIndex = array[i].key % newArray.length;
            newArray[newIndex] = array[i];
        }*/

        for (int i = 0; i < array.length; i++) {
            Node next = null;
            // 把array的每个结点都遍历一遍,如果为空就直接跳过,不为空则一直遍历进行重新哈希
            for (Node cur = array[i]; cur != null; cur = next){
                next = cur.next;

                int newIndex = cur.key % newArray.length;
                newArray[newIndex] = array[i];
            }
        }

        array = newArray;
    }

    /**
     * 得到key对应的value值
     * @param key
     * @return
     */
    public int get (int key) {
        int index = key % array.length;

        Node cur = array[index];
        while (cur.key != key) {
            cur = cur.next;
        }
        if (cur != null) return cur.value;
        else return -1;
    }
}

5.11 性能分析

因为哈希表构建了数据关键码和散列表地址的映射,能够根据数据直接取出对应的值,虽然总是存在冲突,但是这并不是不可解决的问题,所以时间复杂度一般认为是O(1)。

5.12 和Java类集的关系

  1. HashMap和HashSet就是利用了哈希表实现的Map和Set
  2. Java中使用哈希桶解决冲突
  3. Java中负载因子超过0.75后会转变为红黑树提高效率
  4. Java中计算哈希地址就是使用 hashcode()方法进行计算,如果要使用自定义的类进行插入,那么必须覆写 equals()hashcode()

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于GPIO子系统编写LED驱动

编写应用程序进行测试 设置定时器&#xff0c;每5秒打印一次hello world 驱动程序 #include <linux/init.h> #include <linux/module.h> #include<linux/of.h> #include<linux/of_gpio.h> #include<linux/fs.h> #include<linux/io.h> #i…

LeetCode——哈希表(Java)

哈希表 简介242. 有效的字母异位词349. 两个数组的交集202. 快乐数 简介 记录一下自己刷题的历程以及代码&#xff0c;会尽量把在本地测试包含main函数的完整代码贴上&#xff0c;以及一些注释掉的输出语句。写题过程中参考了 代码随想录。会附上一些个人的思路&#xff0c;如…

再畅通工程(最小生成树)

题目描述&#xff1a;还是畅通工程 某省调查乡村交通状况&#xff0c;得到的统计表中列出了任意两村庄间的距离。省政府“畅通工程”的目标是使全省任何两个村庄间都可以实现公路交通&#xff08;但不一定有直接的公路相连&#xff0c;只要能间接通过公路可达即可&#xff09;&…

NewStarCTF2023week4-Nmap

题目要我们找出Nmap扫描得到所有的开放端口 Nmap通常用于直接扫描目标主机&#xff0c;而不是直接扫描pcap文件。 那么这里我们还是使用wireshark来分析&#xff0c;使用过滤器&#xff1a; tcp.flags.syn 1 and tcp.flags.ack 1 这个过滤条件可以筛选出TCP端口开放的数据…

测开 (Junit 单元测试框架)

目录 了解 Junit 引入相关依赖 1、Junit注解 Test BeforeEach、BeforeAll AfterEach && AfterAll 2、断言 1、Assertions - assertEquals 方法 2、Assertions - assertNotEquals 方法 3、Assertions - assertTrue && assertFalse方法 4、Assertions…

DeOldify 接口化改造 集成 Flask

类似的图片修复项目 GFPGAN 的改造见我另一篇文 https://blog.csdn.net/weixin_43074462/article/details/132497146 DeOldify 是一款开源软件&#xff0c;用于给黑白照片或视频上色&#xff0c;效果还不错。 安装部署教程请参考别的文章&#xff0c;本文基于你给项目跑通&…

Chapter1:C++概述

此专栏为移动机器人知识体系的 C {\rm C} C基础&#xff0c;基于《深入浅出 C {\rm C} C》(马晓锐)的笔记&#xff0c; g i t e e {\rm gitee} gitee链接: 移动机器人知识体系. 1.C概述 1.1 C概述 计算机系统分为硬件系统和软件系统。 硬件系统&#xff1a;指组成计算机的电子…

【Matlab2016】Matlab中文版的下载、安装、激活(不建议安装过高版本!!)

这里写目录标题 首先双击R2016_win64.iso加载镜像文件双击setup.exe开始安装选择使用文件密钥安装填入密钥修改安装路径并记住此路径建议全部勾选等待安装完成 激活复制补丁到matlab路径下 创建快捷方式进入bin目录&#xff0c;找到matlab.exe 安装包 首先双击R2016_win64.iso加…

08.K8S高可用方案

K8S高可用方案 1、高可用部署方式 官方提供两种高可用实现方式: 堆叠etcd 拓扑,其中 etcd 节点与控制平面节点共存;外部 etcd 节点,其中 etcd 与控制平面在不同的节点上运行;1.1、堆叠 etcd 拓扑 主要特点: 每个 master 节点上运行一个 apiserver 和 etcd, etcd 只与本…

MySQL总结 (思维导图,常用)

一、常见的增删改查 二、约束&#xff08;五种&#xff09; 三、聚合查询 1、聚合函数 2、group by 和 having 3、联合查询 案例表&#xff1a; drop table if exists classes; create table classes (id int primary key auto_increment,name varchar(20) ); insert into …

高可用系统架构——关于语雀宕机的思考

语雀系统崩溃了&#xff0c;并且经过8个多小时才恢复&#xff0c;估计语雀的小伙伴们已经哭晕在厕所里了。 本次稳定性故障再次给架构师敲响警钟&#xff1a;系统高可用一直是架构的重点&#xff0c;它涉及到系统的方方面面&#xff0c;并且是一件持续性的长期工作。 故障起因…

Spring Security: 整体架构

Filter Spring Security 是基于 Sevlet Filter 实现的。下面是一次 Http 请求从 client 出发&#xff0c;与 Servlet 交互的图&#xff1a; 当客户端发送一个请求到应用&#xff0c;容器会创建一个 FilterChain&#xff0c;FilterChain 中包含多个 Filter 和 Servlet。这些 Fi…

代码训练营第53天:动态规划part12|leetcode309买卖股票的最佳时期含冷静期|leetcode714买卖股票的最佳时机含手续费

leetcode309&#xff1a;买卖股票的最佳时机含冷冻期 文章讲解&#xff1a;leletcode309 leetcode714&#xff1a;买卖股票的最佳时机含手续费 文章讲解&#xff1a;leetcode714 目录 1&#xff0c;leetcode309 买卖股票的最佳时机含冷冻期 2&#xff0c;leetcode714 买卖股票…

荣耀推送服务消息分类标准

前言 为了提升终端用户的推送体验、营造良好可持续的通知生态&#xff0c;荣耀推送服务将对推送消息进行分类管理。 消息分类 定义 荣耀推送服务将根据应用类型、消息内容和消息发送场景&#xff0c;将推送消息分成服务通讯和资讯营销两大类别。 服务通讯类&#xff0c;包…

Linux学习第26天:异步通知驱动开发: 主动

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 在正式开启今天的学习前&#xff0c;讲一讲为什么标题中加入了【主动】俩字。之前学习的阻塞和非阻塞IO&#xff0c;都是在被动的接受应用程序的操作。而今天的学…

深入浅出排序算法之计数排序

目录 1. 原理 2. 代码实现 3. 性能分析 1. 原理 首先看一个题目&#xff0c;有n个数&#xff0c;取值范围是 0~n&#xff0c;写出一个排序算法&#xff0c;要求时间复杂度和空间复杂度都是O(n)的。 为了达到这种效果&#xff0c;这一篇将会介绍一种不基于比较的排序方法。这…

python,pandas ,openpyxl提取excel特定数据,合并单元格合并列,设置表格格式,设置字体颜色,

python&#xff0c;pandas &#xff0c;openpyxl提取excel特定数据&#xff0c;合并单元格合并列&#xff0c;设置表格格式&#xff0c;设置字体颜色&#xff0c; 代码 import osimport numpy import pandas as pd import openpyxl from openpyxl.styles import Font from op…

【鸿蒙软件开发】ArkTS基础组件之TextClock(时间显示文本)、TextPicker(滑动选择文本)

文章目录 前言一、TextClock1.1 子组件1.2 接口参数TextClockController 1.3 属性1.4 事件1.5 示例代码 二、TextPicker2.1 子组件2.2 接口参数 2.3 属性2.4 事件2.5 示例代码 总结 前言 TextClock组件:通过文本将当前系统时间显示在设备上。支持不同时区的时间显示&#xff0…

uni-app中tab选项卡的实现效果 @click=“clickTab(‘sell‘)“事件可传参数

一、效果图 二、代码 <template><view><view class"choose-tab"><view class"choose-tab-item" :class"chooseTab 0 ? active : " data-choose"0" click"clickTab">选项1</view><view …

docker部署prometheus+grafana服务器监控(三) - 配置grafana

查看 prometheus 访问 http://ip:9090/targets&#xff0c;效果如下&#xff0c;上面我们通过 node_exporter 收集的节点状态是 up 状态。 配置 Grafana 访问 http://ip:3000&#xff0c;登录 Grafana&#xff0c;默认的账号密码是 admin:admin&#xff0c;首次登录需要修改…