数据结构之优先级队列(堆)及top-k问题讲解

 💕"哪里会有人喜欢孤独,不过是不喜欢失望。"💕

作者:Mylvzi 

 文章主要内容:数据结构之优先级队列(堆) 

 

一.优先级队列

1.概念

  我们已经学习过队列,队列是一种先进先出(FIFO)的数据结构,但是在有些情况下,数据的进出是有优先级的,优先级高的往往需要先"出",优先级低的就需要后"出",此时普通的队列就无法完成这样的操作(也就是数据在插入到队列中后,还需要根据优先级进行位置调整),需要另外一种数据结构--优先级队列 PriorityQueue来实现

  在实际生活中PriorityQueue的场景经常存在,比如你在打游戏的时候,如果有来电,系统应该优先处理来电。

  在这种场景下,数据结构需要实现两个最基本的操作"返回优先级最高的对象"和"添加新的数据"

二.优先级队列的模拟实现

  jdk1.8中的PriorityQueue 底层就使用了"堆"这种数据结构,堆实际上就是经过优先级调整的完全二叉树

注:这里的优先级在堆中的体现一般是所存储数据的大小 

1.什么是堆?

  堆是一种把元素按大小排列的完全二叉树,堆就是进行数据高效组织的另一种形式

从小到大排列:小根堆(每棵树的根节点的值比当前树的所有孩子都小)

从大到小排列:大根堆(每棵树的根节点的值比当前树的所有孩子都大)

大根堆示例:

小根堆示例:
 

2.堆的存储方式

  堆其实是一颗完全二叉树,那么在存储的时候就可以使用顺序表进行数据的存储;而对于非完全二叉树来说,则不适合使用顺序表存储数据,因为会导致空间资源的浪费

  既然是一颗完全二叉树,那就具有完全二叉树的一些性质

  • 如果i为0,则i对应的结点是根节点;i的父节点的下标为(i-1)/2
  • 由父节点的下标i可得左孩子的下标:2*i+1,右孩子的下标:2*i+2

3.堆的创建

一般都是根据数组去创建堆  使数组中的元素在二叉树中排列时呈现某种顺序

对于集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成堆呢?

观察集合,此时是无序的,我们需要通过调整将其设置为大根堆/小根堆

1.堆的实现

1.大根堆的实现

图解:

可见实现大根堆的核心思路在于"向下调整",对已有的数据进行向下调整,使其符合规定的顺序

向下调整的步骤:

  1.  先获取到最后一棵子树的根节点的下标 parent = (usedSize - 1- 1)/2
  2. 获取到左右孩子结点的最大值(先获取左孩子,再判断左右孩子谁的值更大)
  3. 如果孩子节点的值>根节点的值,交换,交换之后需要堆parent重新赋值为child,继续向下调整
  4. 如果孩子节点的值<根节点的值,不交换,parent--;

对于2:为什么先获取左孩子的结点呢?因为对于一个完全二叉树来说,其最后一棵子树一定有左孩子,但是不一定有右孩子。

代码实现 

    // 根据传入的数组  将他们调整为大根堆

    public int[] elem;
    public int usedSize;

    public TestHeap(int size) {
        this.elem = new int[size];
    }

    // 初始化堆
    public void initHeap(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            this.elem[i] = arr[i];
            this.usedSize++;
        }


    }

    // 实现大根堆

    /**
     * 从最后一棵子树的根节点开始,进行向下调整,一直调整到root
     */
        // 向下调整
    public void createHeap() {
        // 从最后一棵子树的根节点开始
        for (int parents = (usedSize-1-1)/2; parents >= 0; parents--) {
            shiftDown(parents,usedSize);
        }
        
    }

    private void shiftDown(int parents, int usedSize) {
        // 先获取左孩子的下标
        int child = 2*parents + 1;
        
        // 进行向下调整
        while (child < usedSize) {
            // 先判断左右孩子的值是谁更大
            if(child+1 < usedSize && elem[child] < elem[child+1]) {
                // 存在右孩子  且右孩子的值比左孩子的大
                child++;
            }
            
            if(elem[child] > elem[parents]) {
                // 如果孩子结点的值比根节点大  交换
                swap(child,parents);
                parents = child;
                child = 2*parents + 1;
            }else {
                // 如果根节点就是最大值  直接翻一下
                break;
            }
        }
    }

    // 交换函数
    private void swap(int i, int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }
2.小根堆的实现

实现小根堆的逻辑是相同的,只需要在进行向下调整的时候改一下交换的逻辑

    // 实现小根堆
    public void createHeap2() {
        // 从最后一棵子树的根节点开始
        for (int parents = (usedSize-1-1)/2; parents >= 0; parents--) {
            shiftDown(parents,usedSize);
        }

    }

    private void shiftDown2(int parents, int usedSize) {
        // 先获取左孩子的下标
        int child = 2*parents + 1;

        // 进行向下调整
        while (child < usedSize) {
            // 先判断左右孩子的值是谁更大
            if(child+1 < usedSize && elem[child] > elem[child+1]) {
                // 存在右孩子  且右孩子的值比左孩子的小
                child++;
            }

            if(elem[child] < elem[parents]) {
                // 如果孩子结点的值比根节点小  交换
                swap(child,parents);

                // parent中大的元素向下移动  可能会造成子树不满足堆的性质  继续进行向下调整
                parents = child;
                child = 2*parents + 1;
            }else {
                // 如果根节点就是最大值  
                break;
            }
        }
    }

向下调整的时间复杂度:
   最坏的情况就是从根节点一直比到叶子节点,比较的次数就是完全二叉树的高度,O(logn)

其他情况比较的次数都是常数次,对于时间复杂度来说,一般只考虑最坏情况

2.堆的创建

  在向下调整的代码中我们已经实现了堆的创建

    // 堆的创建
    public void createHeap() {
        // 从最后一棵子树的根节点开始
        for (int parents = (usedSize-1-1)/2; parents >= 0; parents--) {
            shiftDown(parents,usedSize);
        }
    }

向下调整的时间复杂度是 O(logn),那"建堆"的时间复杂度是多少呢?请看下面的推导

 

注:
1.堆建立的时间复杂度需要考虑两方面,每层的结点数,以及每个结点向下调整的最坏情况

2.堆的时间复杂度的推导需要使用到数列中常见的一种方法"错位相减"

3.堆的时间复杂度一定要回手写推导,面试中有可能会考到!!!

3.在堆中添加元素

  要求:添加之后仍满足大根堆的形式

说明:

  1. 在堆中插入一个新的元素之后,需要仍保持堆的性质,此时需要进行向上调整,直到调整到合适的位置;
  2. 由于堆的存储结构是一个顺序表,所以当顺序表满时,需要进行扩容 ;同时,堆的插入对应的就是顺序表的尾插

代码实现:

// 在堆内添加元素
    /**
     * 插入到最后一个位置(保证是一个完全二叉树,且方便后续操作)
     * 向上调整
     */

    public void offer(int val) {
        if (isFull()) {
            this.elem = Arrays.copyOf(this.elem,2*this.elem.length);
        }

        this.elem[usedSize] = val;
        shiftUp(usedSize);
        this.usedSize++;
    }

    private void shiftUp(int child) {
        int parent = (child-1)/2;
        
        // == 0此时就是根节点了  不需要再去向上调整
        // 需要一直调整到根节点
        while(child != 0) {
            if(elem[child] > elem[parent]) {
                swap(child,parent);
                child = parent;
                parent = (child-1)/2;
            }else {
                break;
            }
        }
    }

    private boolean isFull() {
        return this.usedSize == this.elem.length;
    }

4.删除优先级最高的元素(数组的首元素)

  堆的删除对应队列的"出队"操作,只不过堆的删除出的是优先级最高的元素

  对于堆来说,优先级最高的元素位于堆顶,也就是顺序表的首元素,那如何进行堆的删除操作呢?

  最直观的想法就是先保存堆顶元素,然后挪动堆顶元素之后的所有元素,使其都向前挪动一个位置,再对挪动之后的结果重新进行向下调整。这种方法实际上是可行的,但是时间复杂度过高,效率较低,这种方法的时间复杂度是挪动数据的时间复杂度O(n)与向下调整的时间复杂度O(logN)之和

  当然还有另一种效率更高的实现方法,先交换堆顶和堆尾的元素,再进行向下调整,此方法的时间复杂度是交换的时间复杂度O(1)与向下调整的时间复杂度O(logN),效率明显比大量挪动数据更高

代码实现:

// 删除  一定是删除优先级最高的元素  最后返回优先级最高的那个元素
    public int poll() {
        // 优先级最高的元素就是顺序表elem 的首元素
        int tmp = elem[0];
        swap(0,usedSize-1);
        this.usedSize--;
        
        // 只需对堆顶元素进行向下调整
        shiftDown(0,usedSize);
        
        return tmp;
    }

注:

  交换堆顶和堆尾元素这一操作还有另一考量,因为堆尾元素一定是优先级最低的元素,出队一定是最后一个进行出队,交换之后,堆顶是优先级最低的元素,就保证了其余元素都是优先级比堆顶元素更高,此时只需对堆顶元素进行向下调整即可,而不是从最后一棵子树的根节点开始进行调整

总结:删除堆中元素的三步骤

  1. 交换堆顶和堆中最后一个元素
  2. 删除最后一个元素
  3. 将堆顶元素进行向下调整

三.常用接口介绍

1.PriorityQueue的特性

  Java的集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,此处主要介绍PriorityQueue

使用PriorityQueue的注意事项

  1. 需要导包,即:import java.util.PriorityQueue;
  2. PriorityQueue中存放的数据必须能够进行大小的比较,不能够插入无法比较的对象,否则会报错
            PriorityQueue<Integer> priorityQueue1 = new PriorityQueue<>();
            priorityQueue1.offer(1);
            priorityQueue1.offer(2);
    
            PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>();
            priorityQueue2.offer(new Student());
            priorityQueue2.offer(new Student());// 报错  

3.不能插入null,否则会报空指针异常

4.插入(向上调整)和删除的操作的时间复杂度都是O(logN)

5. PriorityQueue默认是小根堆,也就是根节点的值比子节点的值小

 

        PriorityQueue<Integer> priorityQueue1 = new PriorityQueue<>();
        priorityQueue1.offer(1);
        priorityQueue1.offer(2);
        priorityQueue1.offer(3);
        priorityQueue1.offer(4);
        priorityQueue1.offer(5);

        System.out.println(priorityQueue1.poll());// 输出1

2. PriorityQueue常用接口介绍

1.构造方法

1.1不含参的构造方法
    // 不含参的构造方法
    public PriorityQueue() {
        this(DEFAULT_INITIAL_CAPACITY, null);
    }
    // 默认容量是11
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
1.2 指定容量的构造方法
    // 指定容量的构造方法
    public PriorityQueue(int initialCapacity) {
        this(initialCapacity, null);
    }
1.3指定容量  并接受比较器的构造方法(最核心的一个构造方法)
    // 指定容量  并接受比较器的构造方法
    public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        // Note: This restriction of at least one is not actually needed,
        // but continues for 1.5 compatibility
        
        // 如果初始容量<1  就抛出异常  但实际上源码中也说了  <1这个条件并不是必需的  只不过是为了和1.5保持一致设置的条件
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
    }
 1.4利用其他集合创建一个优先级队列

只要实现了Collection的集合都能作为参数参与创建一个优先级队列

PriorityQueue(Collection<? extends E> c)  用一个集合来创建优先级队列

        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(12);
        list.add(13);

        PriorityQueue<Integer> priorityQueue =
                new PriorityQueue<>(list);
        System.out.println(priorityQueue.toString());// 输出1 12 13

2.PriorityQueue的扩容机制

PriorityQueue的存储结构是顺序表,在不断添加数据的时候涉及到扩容问题,下面来研究一下PriorityQueue中是如何进行扩容的

先来看offer对应的源码

    public boolean offer(E e) {
        
        // 为空 直接抛出异常  不能插入空指针
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        
        // 进行扩容
        if (i >= queue.length)
            // grow方法内部是扩容的机制
            grow(i + 1);
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else  // 向上调整
            siftUp(i, e);
        return true;
    }

接下来看grow方法的实现 



3.如何实现大根堆

PriorityQueue默认是小根堆,想要实现大根堆则需要重新构建比较逻辑,使用Comparator接口,下面以整数的比较为例实现大根堆

1.方法一:直接构造一个比较器
        // 构造比较器 实现大根堆
        class IntCmp implements Comparator<Integer> {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        }

        PriorityQueue<Integer> priorityQueue =
                new PriorityQueue<>(new IntCmp());
        priorityQueue.offer(1);
        priorityQueue.offer(2);
        priorityQueue.offer(33);
        priorityQueue.offer(44);
        priorityQueue.offer(55);

        System.out.println(priorityQueue.poll());// 输出55
2.方法二:使用匿名内部类
        // 使用匿名内部类
        PriorityQueue<Integer> priorityQueue =
                new PriorityQueue<>(new Comparator<Integer>() {
                    @Override
                    public int compare(Integer o1, Integer o2) {
                        return o2.compareTo(o1);
                    }
                });

        priorityQueue.offer(1);
        priorityQueue.offer(2);
        priorityQueue.offer(33);
        priorityQueue.offer(44);
        priorityQueue.offer(55);

        System.out.println(priorityQueue.poll());// 输出55
3.方法三:使用lambda表达式(推荐) 
        // 使用lambda表达式
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(
                (o1,o2) ->{return o2.compareTo(o1);}
        );

        priorityQueue.offer(1);
        priorityQueue.offer(2);
        priorityQueue.offer(33);
        priorityQueue.offer(44);
        priorityQueue.offer(55);

        System.out.println(priorityQueue.poll());// 输出55

四.重点:top-k问题的三种解决方法

https://leetcode.cn/problems/smallest-k-lcci/description/

1.最简单的思路:对数组进行排序

// 解法1
    public int[] smallestK1(int[] arr, int k) {
        int[] ret = new int[k];
        if(arr == null || k <= 0) return ret;

        Arrays.sort(arr);

        // 排序之后  arr从小到大排列完毕
        for (int i = 0; i < k; i++) {
            ret[i] = arr[i];
        }

        return ret;
    }

 

2.直接使用堆(优先级队列)的特性  创建小根堆  poll k次即可

// 解法2
    public int[] smallestK2(int[] arr, int k) {
        int[] ret = new int[k];
        if(arr == null || k <= 0) return ret;

        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();

        for (int i = 0; i < arr.length; i++) {
            priorityQueue.offer(arr[i]);
        }

        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }


        return ret;
    }

3.建立一个k个结点的大根堆  再与数组剩余元素进行比较(重点掌握)

步骤:

  1. 先将前k个元素创建为大根堆(k 是因为我最后要返回k个元素)
  2. 将数组中剩余的元素依次去和堆顶元素进行比较,如果小于堆顶元素,插入堆中
  3. 返回容量为k的大根堆

// 解法3  效率最高的一种方法  创建一个具有k个结点的大根堆
    
    // 注意  优先级队列默认是小根堆  要实现大根堆  需要先创建一个实现了Comparator接口的对象
    class IntCmp implements Comparator<Integer> {

        @Override
        public int compare(Integer o1, Integer o2) {
            return o2.compareTo(o1);
        }
    }
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];
        if(arr == null || k <= 0) return ret;

        // 创建一个具有k个结点的大根堆
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new IntCmp());

        for (int i = 0; i < k; i++) {
            // 将前k个元素做成大根堆
            priorityQueue.offer(arr[i]);
        }

        for (int i = k; i < arr.length; i++) {
            // 去和栈顶元素比较
            if (arr[i] < priorityQueue.peek()) {
                // 证明栈顶元素不是前k个最小的元素  要删除
                priorityQueue.poll();
                priorityQueue.offer(arr[i]);
            }
        }

        for (int i = 0; i < k; i++) {
            ret[i] = priorityQueue.poll();
        }

        return ret;
    }

 时间复杂度分析:

前两种方法对于数据量特别大的情况十分不友好!占用的内存太大 

变式:

求数组中第k大/小元素
// 求数组中第k大/小元素

    //  前k个元素存储到小根堆中  堆里存放最大的k个元素 以小根堆的形式存储  则堆头一定是第k大的元素
    public int maxKestK(int[] arr, int k) {
        if(arr == null || k <= 0) {
            throw new ArrayEmptyException("不含有元素或k不合法");
        }

        // 创建一个具有k个结点的大根堆
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>();

        for (int i = 0; i < k; i++) {
            // 将前k个元素做成小根堆
            priorityQueue.offer(arr[i]);
        }

        for (int i = k; i < arr.length; i++) {
            if (arr[i] > priorityQueue.peek()) {
                priorityQueue.poll();
                priorityQueue.offer(arr[i]);
            }
        }

        return priorityQueue.poll();

    }

五.堆排

  堆排序即利用堆的思想进行排序,堆排序的过程可以分为两步

  1. 确定排序方式:升序--建立大根堆  降序--建立小根堆
  2. 利用堆删除的思想(向下调整进行堆排)

图解:

代码实现

    // 堆排序
    // 升序  从小到大  创建大根堆
    // 降序  从大到小  创建小根堆

    /**
     * 升序  调整为大根堆  堆首元素一定是最大的
     * 交换堆首和堆尾元素  向下调整(不包含被调下去的最大元素)  使第二大的元素位于堆顶
     * 重复上述操作  每次都是堆首元素和堆尾元素进行交换
     */

    public void headSort() {
        int end = usedSize-1;
        while(end > 0) {
            swap(0,end);
            shiftDown(0,end);
            end--;
        }
    }

    private void shiftDown(int parent, int usedSize) {
        int child = 2 * parent+1;// 得到左孩子的下标
        while (child < usedSize) {
            // 首先要保证child是左右孩子最大元素的下标
            if(child + 1 < usedSize && elem[child] < elem[child+1]) {
                // 有右孩子  且右孩子的值比左孩子大
                child++;
            }

            // 此时child就是值最大孩子的下标
            if(elem[child] > elem[parent]) {
                swap(child,parent);
                parent = child;
                child = 2 * parent+1;
            }else {
                break;
            }
        }
    }

总结:

堆排时要选择好排序的顺序,如果是升序排序就创建大根堆,如果是降序排序就是用小根堆

以升序排序为例,创建一个大根堆存储数据,再不断交换堆顶和堆尾元素(此时一定是最大元素放到堆尾,最小元素放到堆顶),再进行向下调整,向下调整的数据范围并不报错刚刚被挪动到堆尾的元素,使整个堆仍然保持大根堆的性质,这样最大的元素就被移动到最后,依次操作,每次都可以把当前数据范围内的最大元素移动到最后,最后创建出的堆中存储的数据就是升序排序的

这种"逆向思维"在堆中很常见,想让堆顶是最小的元素,就先创建大根堆,再不断地挪动数据,向下调整

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

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

相关文章

单线圈无刷直流电机驱动芯片选型分析,可应用于笔记本,显卡风散热风扇,变频冷却风扇,打印机风扇等产品上

单线圈无刷直流电机的电机驱动器。 GC1298R/S&#xff0c;GC1262E/S&#xff0c;GC1298R/S&#xff0c;GC1262R/S具有高效的直接PWM控制方式&#xff0c;它可以控制无刷直流电机转速。它集成了最低速度限制模式、可调速度斜率控制模式、软启动模式、风扇转速计、锁保护、自动重…

PGSQL 设置autovacuum

VACUUM和ANALYZE是PostgreSQL 数据库维护最重要的两个操作。 vacuum用于恢复表中“死元组”占用的空间。删除或更新&#xff08;删除后插入&#xff09;记录时&#xff0c;将产生死元组。PostgreSQL不会从表中物理删除旧行&#xff0c;而是在其上放置一个“标记”&#xff0c;以…

java定位系统源码,UWB技术的无线定位系统源码

UWB技术是一种传输速率高&#xff0c;发射功率较低&#xff0c;穿透能力较强并且是基于极窄脉冲的无线技术。UWB最优的应用环境是室内或者相对密闭的空间&#xff0c;有着厘米级的定位精度&#xff0c;不仅可以非常精准地进行位置跟踪&#xff0c;还可以快速地进行数据传输。 智…

DNF 单机联网 搭建教程(附视频)

更多游戏搭建&pvf修改教程请见: DNF教程 注意&#xff1a;请不要将游戏进行商业化&#xff0c;一切后果概不负责。仅供单机&#xff0c;好友之间进行娱乐&#xff01;&#xff01; 注意&#xff1a;请不要将游戏进行商业化&#xff0c;一切后果概不负责。仅供单机&#…

Vue笔记-在axios中的than函数中使用this需要注意的地方

在Vue中&#xff0c;可以使用this关键字来访问到组件中定义的变量。然而&#xff0c;在axios的then函数中&#xff0c;this关键字的作用域会改变&#xff0c;会指向axios对象本身而不是Vue组件实例。因此&#xff0c;不能直接访问到Vue组件中定义的变量。 解决这个问题的一种方…

git入门教程+常用命令

Git入门教程 本文章主要参照视频教程&#xff1a;https://www.bilibili.com/video/BV1FE411P7B3/?spm_id_from333.337.search-card.all.click&vd_source06caf161b187fb3f4c039bc15e238fea 为什么要使用GIT 版本控制是项目、文档迭代的必然要求&#xff0c;所以需要使用…

如何在 IDEA 中设置远程连接服务器开发环境并实现固定地址远程 Linux 环境

文章目录 1. 检查Linux SSH服务2. 本地连接测试3. Linux 安装Cpolar4. 创建远程连接公网地址5. 公网远程连接测试6. 固定连接公网地址7. 固定地址连接测试 本文主要介绍如何在IDEA中设置远程连接服务器开发环境&#xff0c;并结合Cpolar内网穿透工具实现无公网远程连接&#xf…

肥猫游戏报价器|计价器|王者荣耀代练陪练等游戏报价器软件介绍说明

目录 1. 前言2. 软件著作权3. 软件使用说明3.1 进入软件3.2 用户登录3.3 首页3.4 报价器3.4.1 总体介绍3.4.2 王者报价器3.4.3 LOL手游报价器3.4.4 英雄联盟报价器3.4.5 云顶之弈报价器3.4.7 王者水晶报价器3.4.8 和平精英报价器3.4.9 蛋仔派对报价器3.4.10 穿越火线报价器3.4.…

西工大网络空间安全学院计算机系统基础实验二(phase_2下——漫漫深夜过后的黎明!!!)

内存地址内存地址中的数注释指向这块内存的寄存器0xffffd0e8函数phase_2的栈帧0xffffd0e40xffffd0f4函数phase_2的栈帧0xffffd0e00x5655b7b0函数phase_2的栈帧0xffffd0dc0x565566ca函数read_six_numbers的返回地址&#xff0c;函数phase_2的栈帧0xffffd0d80x5655af64旧%ebx的值…

使用加密软件Enigma Protector,可以轻松获得软件许可!

软件许可是在互联网上销售商业软件以及将程序的使用锁定到特定用户或计算机的最常用方式。在这篇文章中&#xff0c;我将解释什么是软件许可证以及如何使用Enigma Protector将其应用到现有软件。 The Enigma Protector 是一款专门设计用来为应用程序添加高强度保护的强大工具。…

【VTK】VTK中的宏定义

很高兴在雪易的CSDN遇见你 【vtkWidgetRepresentation】第九期 vtk中的仿射变换 前言 本文分享VTK中的宏定义&#xff0c;希望对各位小伙伴有所帮助&#xff01; 感谢各位小伙伴的点赞关注&#xff0c;小易会继续努力分享&#xff0c;一起进步&#xff01; 你的点赞就是我的…

chrome浏览器使用flash player

今天用chrome打开学校校园网&#xff0c;显示不出来成绩单提示如下&#xff1a; 结果下了也没用。 Chrome浏览器在2020年12月已经停止支持Flash Player插件&#xff0c;所以无法在Chrome浏览器上使用Flash Player。 使用其他浏览器 如果之前安装了Flash Player插件的小伙伴&…

关于引用unpkg.com的mars3d相关依赖文件报错无法请求的说明

问题来源&#xff1a; 1.关于引用unpkg.com的mars3d相关依赖文件报错无法请求的说明 说明&#xff1a; 1.最近npm、unpkeg都访问异常&#xff0c;可能是unpkg.com等国外的服务器不稳定导致的请求未响应。 解决方案&#xff1a; 1.请切换静态文件引入的方式请求相关资源。参…

QEMU源码全解析 —— virtio(1)

接前一篇文章&#xff1a; 本文内容参考&#xff1a; 《趣谈Linux操作系统》 —— 刘超&#xff0c;极客时间 《QEMU/KVM》源码解析与应用 —— 李强&#xff0c;机械工业出版社 特此致谢&#xff01; virtio简介 对于一台虚拟机而言&#xff0c;除了要虚拟化CPU和内存&…

Linux:符号和符号表

文章目录 什么是符号&#xff1f;什么是符号表&#xff1f;全局符号和本地符号1. 全局符号&#xff1a;symtab符号表 2. 本地符号&#xff1a; 符号在汇编阶段符号在链接阶段1.由模块 m 定义并能被其他模块引用的全局符号。2.由其他模块定义并被模块 m 引用的全局符号。3.只被模…

电脑长截图、滚动截图软件!早用早享受!

FastStone Capture软件在屏幕捕捉、滚动截屏、屏幕录制、图片处理方面能力十分突出&#xff0c; 帮助用户以专业的方式来创建所需图片或视频。 最新版本 : FastStone Capture 10.3 软件大小 : 7.75MB 适配系统 : Windows Vista/7/8/10/11&#xff0c;WinXP 软件名称&#xff1a…

VCSA7.0加入Windows AD域失败的处理过程

一、问题现象 vcsa7.0加入AD域时提示“Idm client exception: Error trying to join AD, error code 2453” 二、处理过程 &#xff08;一&#xff09;检查是否DNS问题 查看dns是否设置正确&#xff0c;如下&#xff1a; 1. Enable SSH on VCSA. 2. Command> shell 3…

微信小程序---wxs脚本

WXS&#xff08;WeiXin Script&#xff09;是小程序的一套脚本语言&#xff0c;结合 WXML&#xff0c;可以构建出页面的结构。 WXS 与 JavaScript 是不同的语言&#xff0c;有自己的语法&#xff0c;并不和 JavaScript 一致。 1.wxs和JavaScript的区别 2.wxs脚本基础语法 &a…

短视频引流获客系统:引领未来营销的新潮流

在这个信息爆炸的时代&#xff0c;短视频已经成为了人们获取信息的主要渠道之一。而随着短视频的火爆&#xff0c;引流获客系统也逐渐成为了营销领域的新宠。本文将详细介绍短视频引流获客系统的开发流程以及涉及到的技术&#xff0c;让我们一起来看看这个引领未来营销的新潮流…

CRAMdb:动物微生物组的组成和功能综合数据库

中国中医科学院陈同、石河子大学胡圣伟、倪伟及团队在Nucleic Acids Research发表与动物相关的微生物数据CRAMdb&#xff0c;数据库中包含9430种细菌、278种古细菌、2216种真菌和458种病毒等超1.2万种微生物。与同类型其他数据库对比&#xff0c;CRAMdb囊括微生物更加广泛&…