JavaDS —— 优先级队列(堆) PriorityQueue

news2024/9/24 11:24:41

优先级队列的概念

我们在前面就已经学习过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,那么在该场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话;初中那会班主任排座位时可能会让成绩好的同学先挑座位。
在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)

JDK1.8中的PriorityQueue底层使用了堆这种数据结构,而堆实际就是在完全二叉树的基础上进行了一些调整

堆的概念

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

大根堆实例:任意根节点的数值均大于左右孩子的数值
在这里插入图片描述

小根堆实例:任意根节点的数值均小于左右孩子的数值
在这里插入图片描述

堆的存储方式

堆本质是一颗完全二叉树,因此可以按照层序遍历的顺序采用顺序存储的方式来进行存储,即由一个一维数组进行存储的。

对于非完全二叉树是不适合使用顺序存储的方式进行存储的,因为如果非完全二叉树使用顺序存储的话要想还原回原来的二叉树形态就需要知道空节点的位置,也就意味着需要在一维数组里存放空节点,这就浪费空间,所以采用链式存储会更好一点。

堆的性质

我们要想从一维数组还原二叉树,就必须知道双亲结点与孩子结点的存储关系,不知道大家还是否记得二叉树的第五条性质:
对于具有 n 个结点的完全二叉树,如果按照从上到下从左到右的顺序对所有的结点从0开始编号,则对于序号为 i 的结点有:
若 i = 0,i 为根节点的编号,没有双亲结点
若 i > 0 , 双亲结点的序号为 (i - 1) / 2
若 2 * i + 1 < n , 左孩子序号为 2 * 1 + 1,否则没有左孩子
若 2 * i + 2 < n , 右孩子序号为 2 * 1 + 2,否则没有右孩子

这里也是一样的,根节点就是数组的首元素,即下标为 0 .其他结点按层序序列进行存储 :
在这里插入图片描述
这里我们要稍微拓展一下性质五,现在来介绍一些堆的性质:

1.根节点从 0 开始编号
2. 假设双亲结点为 i ,那么左孩子结点为 2 * i + 1,右孩子结点为 2 * i + 2
3. 假设孩子结点为 i,那么其双亲结点为 ( i - 1 ) / 2
4. 假设数组一共有 n 个元素,那么完全二叉树最后一颗子树的双亲结点为 (n - 1 - 1 ) / 2 【要想找到最后一颗子树的双亲结点,我们可以从最后一个结点来推导其双亲结点,最后一个结点为 n - 1,则双亲结点为 (n - 1 - 1 ) / 2】

堆的模拟实现

这里我设计的是小根堆,因为 PriorityQueue 底层就是小根堆

准备阶段

我们需要一个数组来存储数据,还要一个 useSized 来记录使用了多少的空间。

public class MyHeap {
    int[] elem;
    int useSized;

    public MyHeap() {
        elem = new int[10];
    }

    public void init(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            this.elem[i] = arr[i];
        }
        useSized = arr.length;
    }
}

堆的创建

堆的创建我们一般使用向下调整,什么是向下调整呢?就是从根节点向孩子结点走,也是远离根节点的过程,从代码的角度看就是 parent 赋值为 child,child 向下走, 一直到堆调整好:

为什么我们要从最后一颗子树入手?
我们从最后一颗子树开始构建小堆的话,就能保证在循环的过程中,下面的小堆是已经建好的,因此在向下调整的时候,当 parent 本身就 小于 child 就不用交换直接停止循环即可,直接停止循环是因为下面的小堆早已构建好了。

在这里插入图片描述
在这里插入图片描述
当 parent 赋值为 child 的时候,说明了这是根节点向下调整的思路,然后 child 一直往下走,直到 child 越界就停止循环。
在这里插入图片描述
在这里插入图片描述


当孩子结点小于双亲结点的时候就交换,如果不满足交换条件,那就应该退出循环,因为说明这已经是小根堆。

注意这里的向下调整需要加多一个参数,这样使用者就可以直接在自己的数组构建堆的排列顺序,而不仅仅局限于在类中构建堆。

    public void creatHeap() {
        for (int parent = (useSized - 1 - 1) / 2; parent >= 0; parent--) {
            shiftDown(elem,parent,this.useSized);
        }
    }
    
    public void shiftDown(int[] elem,int parent,int useSized) {
        int child = 2 * parent + 1;  // 获取左孩子
        while(child < useSized) {
            if(child+1 < useSized && elem[child+1] < elem[child]) {  //先判断有没有右孩子,再判断左右孩子谁小
                child++;
            }
            if(elem[parent] > elem[child]) {
                //交换
                swap(elem,parent,child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break; // 说明本身就是小根堆,直接退出循环
            }
        }
    }

这里再加一个交换代码,因为后面的操作还要继续使用。

为了使用者能在外部将自己的数组变成堆,所以这个交换方法应该有一个数组的形参

    private void swap(int[] elem,int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }

堆的插入

我们将新的数据放在 useSized 下标处,然后开始进行向上调整,构建好小根堆
在这里插入图片描述
向上调整其实是双亲结点和孩子结点向根结点靠近的过程,从代码的角度看就是 child 赋值为 parent ,parent 一直向上走的过程。
在这里插入图片描述


这里要注意数组如果满了就要进行扩容操作,还有一点如果不满足交换条件就直接终止循环,说明其本身就是一个小根堆。

    public void offer(int val) {
        if(isFull()) {
            grow();
        }
        elem[useSized] = val;
        //向上调整
        shirtUp(useSized);
        useSized++;
    }

    public void shirtUp(int child) {
        int parent = (child - 1) / 2;
        while(parent >= 0) {
            if(elem[child] < elem[parent]) {
                swap(eles,child,parent);
                child = parent;
                parent = (child - 1) / 2;
            } else {
                break;
            }
        }
    }

构建堆的时间复杂度分析

现在计算构建分别在向下调整和向上调整两种情况下的时间复杂度,这里考虑的是从零开始搭建堆,也就是一个初始状态下的数组等待建堆,而不是仅仅插入一个元素的插入操作。

向下调整时间复杂度分析

在这里插入图片描述
我们知道每一层的结点个数为 2 ^ (h - 1),h 为高度(根节点所在高度为 1 )。
在向下调整的时候,我们是从最后一颗子树出发,由于时间复杂度采用大O渐进法,所以我们将倒数第二层当作是最后一颗子树的根节点所在处,也就是说倒数第二层我们需要调整的双亲结点有 2 ^ (h - 2) 个。

每一层需要调整的双亲结点个数:
第一层 : 2 ^ 0
第二层: 2 ^ 1
第三层: 2 ^ 2

倒数第二层: 2 ^ (h - 2)
最后一层: 0

时间复杂度按最坏的情况计算,就是每一个双亲结点的向下调整,直到 child 越界调整完毕为止,也就是每一个双亲结点 需要向下 交换 的次数 等于 高度,即 ( h - 层数 )

每一层交换次数:
第一层 : 2 ^ 0 * (h - 1)
第二层: 2 ^ 1 * (h - 2)
第三层: 2 ^ 2 * (h - 3)

倒数第二层: 2 ^ (h - 2) * 1
最后一层: 0

总次数为 T(n) = 2 ^ 0 * (h - 1) + 2 ^ 1 * (h - 2) + 2 ^ 2 * (h - 3) + … + 2 ^ (h - 2) * 1
使用错位相减法: 2 T(n) = 2 ^ 1 * (h - 1) + 2 ^ 2 * (h - 2) + 2 ^ 3 * (h - 3) + … + 2 ^ (h - 1) * 1
T(n) = 2T(n) - T(n)
= 2 ^ 1 * (h - 1) + 2 ^ 2 * (h - 2) + 2 ^ 3 * (h - 3) + … + 2 ^ (h - 1) * 1 - [ 2 ^ 0 * (h - 1) + 2 ^ 1 * (h - 2) + 2 ^ 2 * (h - 3) + … + 2 ^ (h - 2) * 1 ]
= h - 1 - [ 2^ 1 + 2^ 2 + 2^ 3+… +2 ^ (h-1) ]
= 2^ h - 1 - h

因为 2 ^ h - 1 = n(总结点数) 即 h = log2(n + 1)
T(n) = n - log2(n + 1)
一次函数的增长速率高于对数函数,所以 T(n) ≈ n

向下调整建堆的时间复杂度为O(N)

向上调整时间复杂度分析

向上调整是从最后一层开始进行的,也就意味着每一个结点都参加了这次向上调整

每一层参加向上调整的节点个数:
第一层:2 ^ 0
第二层:2 ^ 1
第三层:2 ^ 2

倒数第二层:2 ^ (h - 2)
最后一层:2 ^ (h - 1)

由于是向上调整,最坏情况下,需要交换上面的高度值的次数:
第一层:2 ^ 0 * 0
第二层:2 ^ 1 * 1
第三层:2 ^ 2 * 2

倒数第二层:2 ^ (h - 2) * (h - 2)
最后一层:2 ^ (h - 1) * (h - 1)

T(n) = 2 ^ 0 * 0 + 2 ^ 1 * 1 + 2 ^ 2 * 2 + … + 2 ^ (h - 2) * (h - 2) + 2 ^ (h - 1) * (h - 1)
使用错位相减法可得 T(n) = 2 + 2^ h * (h - 2)
因为 h = log2(n + 1)
T(n) = (n + 1)( log2(n + 1) - 2) + 2

向上调整建堆的时间复杂度为O(N * log2(N) )

由于 N 取值为 0,1,2,…
当 N 大于 2 时,log2(N) 恒大于 1
所以从整体趋势来看 N 是小于 N * log2(N)
向下调整的效率更优于向上调整,所以在构建堆的时候,我们通常采用 向下调整

堆的删除

堆的删除是指将堆顶的元素删除,但我们不是真删除,而是和堆的最后一个元素进行交换,然后向下调整堆的序列,因为后序的堆排序中我们需要使用到这个删除的思想,所以不能真删除。

只有当堆不为空的时候才可以进行删除操作,所以这里在写一个判空操作。

    public int poll() {
        if(isEmpty()) {
            return -1;
        }
        
        int ret = elem[0];
        swap(elem,0,useSized-1);
        useSized--;
        shiftDown(elem,0,useSized);
        return ret;
    }

    public boolean isEmpty() {
        return useSized == 0;
    }

获取堆顶元素

    public int peek() {
        if(isEmpty()) {
            return -1;
        }
        return elem[0];
    }

堆的模拟实现完整代码

public class MyHeap {
    int[] elem;
    int useSized;

    public MyHeap() {
        elem = new int[10];
    }

    public void init(int[] arr) {
        for (int i = 0; i < arr.length; i++) {
            this.elem[i] = arr[i];
        }
        useSized = arr.length;
    }

    public void creatHeap() {
        for (int parent = (useSized - 1 - 1) / 2; parent >= 0; parent--) {
            shiftDown(elem,parent,this.useSized);
        }
    }

    private void swap(int[] elem,int i,int j) {
        int tmp = elem[i];
        elem[i] = elem[j];
        elem[j] = tmp;
    }
    public void shiftDown(int[] elem,int parent,int useSized) {
        int child = 2 * parent + 1;  // 获取左孩子
        while(child < useSized) {
            if(child+1 < useSized && elem[child+1] < elem[child]) {  //先判断有没有右孩子,再判断左右孩子谁小
                child++;
            }
            if(elem[parent] > elem[child]) {
                //交换
                swap(elem,parent,child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break; // 说明本身就是小根堆,直接退出循环
            }
        }
    }

    public void offer(int val) {
        if(isFull()) {
            grow();
        }
        elem[useSized] = val;
        //向上调整
        shirtUp(useSized);
        useSized++;
    }

    public void shirtUp(int child) {
        int parent = (child - 1) / 2;
        while(parent >= 0) {
            if(elem[child] < elem[parent]) {
                swap(elem,child,parent);
                child = parent;
                parent = (child - 1) / 2;
            } else {
                break;
            }
        }
    }

    private void grow() {
        elem = Arrays.copyOf(elem,2 * elem.length);
    }

    public boolean isFull() {
        return useSized == elem.length;
    }

    public int poll() {
        if(isEmpty()) {
            return -1;
        }

        int ret = elem[0];
        swap(elem,0,useSized-1);
        useSized--;
        shiftDown(elem,0,useSized);
        return ret;
    }

    public boolean isEmpty() {
        return useSized == 0;
    }

    public int peek() {
        if(isEmpty()) {
            return -1;
        }
        return elem[0];
    }
}

PriorityQueue 的使用

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

PriorityQueue 底层是小根堆实现的,要想创建大根堆可以自行添加一个比较器。

在这里插入图片描述

使用注意

  1. 使用时必须导入PriorityQueue所在的包,即:
import java.util.PriorityQueue;
  1. PriorityQueue中放置的元素必须要能够比较大小不能插入无法比较大小的对象,否则会抛出
    ClassCastException异常
  2. 不能插入null对象,否则会抛出NullPointerException
  3. 没有容量限制,可以插入任意多个元素,其内部可以自动扩容
  4. 插入和删除元素的时间复杂度为 O(log2(N) )
  5. PriorityQueue底层使用了数据结构
  6. PriorityQueue默认情况下是小堆,即每次获取到的元素都是最小的元素

构造方法和 offer 方法 源码的介绍

这里介绍常见的构造方法:

下面是空的构造方法,可见它调用了另一个包含两个参数的构造方法:
在这里插入图片描述

下面是带一个初始容量的方法,方法内部调用了和上面一样的带两个参数的构造方法:
在这里插入图片描述

这一个是传入一个比较器的构造方法,内部还是一样调用了带两个参数的构造方法:
在这里插入图片描述


下面就是我们提及到的带两个参数的构造方法,这里要重点介绍这个方法:
在这里插入图片描述
initialCapacity 是初始容量,comparator 是比较器
当初始容量小于 1 ,说明容量值非法,抛出异常
接下来就是创建一个数组,并且将比较器装好。


现在我们来看一下 offer 方法:
在这里插入图片描述
当传入的数值为 null 的时候,就会抛出异常

modCount 是指此优先级队列已被执行的次数
在这里插入图片描述

size 是目前优先级队列已经存放多少元素。
当容量已满时就会进行扩容,grow 就是扩容方法,下面是扩容方法的源码:
在这里插入图片描述

当容量足够的时候就会进行向上调整,然后 size++

我们来看一下向上调整的源码:

在这里插入图片描述
当比较器为空的时候,会调用siftUpUsingComparator 方法:
在这里插入图片描述

看到上面的 if 语句,也就是说如果没有提供比较的器的话,传入的参数必须是可比较的对象,也就是没有比较器那就使用自己的 compareTo 方法,如果新加入的对象大于其双亲结点的数值就不需要进行向上调整,也证实了Java集合类提供的优先级队列是 以小根堆为底层的。


当你传入了比较器的时候,优先级队列就会优先使用你的比较器进行比较进而创建你需要的堆,这也说明了使用者可以自己实现大根堆 ~ ~

在这里插入图片描述

常见的方法

在这里插入图片描述

应用与实践

TopK 问题

题目链接:
https://leetcode.cn/problems/smallest-k-lcci/description/

在这里插入图片描述


思路一:先构建 k 个数据的大根堆,然后从 k + 1 开始遍历数据,当遇到比堆顶还小的元素的时候,堆就删除堆顶元素,然后插入新数据构建新堆。

原因:由于我们建立的是大根堆,所以堆顶元素是这 k 个元素的最大值,当从 k +1 开始遍历数组的时候,当找到比堆顶元素小的元素的时候,我们先进行删除操作,然后再进行插入操作,这样等到遍历完成,这 k个元素就是数组 最小的 k 个数。

class IntCmparator implements Comparator<Integer> {
    public int compare(Integer o1, Integer o2) {
        return o2.compareTo(o1);
    }
}

class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];
        if(arr == null || k == 0) {
            return ret;
        }

        PriorityQueue<Integer> queue = new PriorityQueue<>(k,new IntCmparator());
        for(int i=0;i<k;i++){
            queue.offer(arr[i]);
        }

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

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

思路二:将所有数据堆排,建立小根堆,然后一直进行出堆操作,一共需要出 k 次堆,这 k 个元素就是最小的k 个数了。

原因:小根堆的堆顶元素是所有堆中元素最小的,每次出堆的时候,都会进行向下调整建立新的小堆。删除之后堆顶元素还是堆中所有元素的最小值。

class Solution {
    public int[] smallestK(int[] arr, int k) {
        int[] ret = new int[k];
        if(arr == null || k == 0) {
            return ret;
        }

        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for(int i=0;i<arr.length;i++) {
            queue.offer(arr[i]);
        }

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

        return ret;
    }
}

我们来分析一下两个算法的时间复杂度,思路一采用 k 个元素建堆,建堆采用向下调整,所以建堆的时间复杂度为 O(k),然后遍历剩下的所有元素进行删除,插入等操作,这个时间复杂度为 (n - k)* log2(k),那么整体的时间复杂度为 O(k + (n - k)* log2(k) )
思路二采用的是所有元素参与建堆,时间复杂度为O(n),然后进行 k 次删除操作,时间复杂度为 O(k * log2(n) ),那么总体的时间复杂度为 O(n + k * log2(n) )

由此可见采用思路一的算法时间效率更高~~

堆排序

步骤:

1.建堆
升序: 建立大根堆
降序: 建立小根堆

2.利用堆删除思想来进行排序
建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

解释:这里没有额外开辟空间,而是直接在原数组直接进行排序。
首先删除的操作是堆顶元素和最后一个元素进行交换,然后重新建堆,这样就保证了最后的元素一定是最大的,依次类推,把每次的堆顶元素放到后面,这样就获得了升序的序列;同理可得降序原因。

升序代码:

    private void swap(int[] arr,int i,int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    private void creatHeap(int[] arr) {
        for (int parent = (arr.length - 1 - 1) / 2 ; parent >= 0 ; parent--) {
            siftDown(arr,parent,arr.length);
        }
    }

    private void siftDown(int[] arr, int parent, int size) {
        int child = 2 * parent + 1;
        while(child < size - 1) {
            if(child + 1 < size - 1 && arr[child+1] > arr[child]) {
                child++;
            }
            if(arr[parent] < arr[child]) {
                swap(arr,parent,child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }

    public void heapSort(int[] arr) {
        if(arr == null || arr.length == 0) {
            return;
        }
        creatHeap(arr); // 建立大堆
        int end = arr.length - 1; //获取最后一个元素的下标
        //删除
        while(end > 0) {
            swap(arr,0,end);
            siftDown(arr,0,end + 1);
            end--;
        }
    }

降序代码:

    private void swap(int[] arr,int i,int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    private void creatHeap(int[] arr) {
        for (int parent = (arr.length - 1 - 1) / 2 ; parent >= 0 ; parent--) {
            siftDown(arr,parent,arr.length);
        }
    }

    private void siftDown(int[] arr, int parent, int size) {
        int child = 2 * parent + 1;
        while(child < size - 1) {
            if(child + 1 < size - 1 && arr[child+1] < arr[child]) {
                child++;
            }
            if(arr[parent] > arr[child]) {
                swap(arr,parent,child);
                parent = child;
                child = 2 * parent + 1;
            } else {
                break;
            }
        }
    }

    public void heapSort(int[] arr) {
        if(arr == null || arr.length == 0) {
            return;
        }
        creatHeap(arr); // 建立小堆
        int end = arr.length - 1; //获取最后一个元素的下标
        //删除
        while(end > 0) {
            swap(arr,0,end);
            siftDown(arr,0,end + 1);
            end--;
        }
    }

时间复杂度分析:
向下调整建堆时间复杂度为 O(n),一次删除操作时间复杂度为 O( log2(n) ),删除循环时间复杂度为 (n - 1) * log2(n),整体时间复杂度为 n + (n - 1) * log2(n) 即为 O( N * log2 (N) )

由于没有开辟额外的空间,所以空间复杂度为O(1)

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

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

相关文章

详解数据结构之队列、循环队列(源码)

详解数据结构之队列、循环队列(源码) 队列属于线性表 队列&#xff1a;就好比如&#xff0c;我们在排队买东西时排队&#xff0c;第一个先来的第一个买&#xff0c;最后一个到的最后一个买&#xff0c;这里的队列也是满足先进先出&#xff0c;后进后出的规律&#xff08;First …

如果用MATLAB函数delayseq可以对分数延时,但是延时后波形较原波形有幅度上的改变

&#x1f3c6;本文收录于《CSDN问答解答》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收藏&…

Adobe国际认证详解-视频设计认证专家行业应用场景解析

在当今数字化时代&#xff0c;视频设计已成为各行各业不可或缺的一环。而视频设计认证专家&#xff0c;作为经过Adobe国际认证体系严格考核的专业人才&#xff0c;更是行业内炙手可热的存在。他们凭借深厚的视频设计理论基础和实践经验&#xff0c;为行业提供了高质量的视频设计…

ROS2中间件

ROS2 是重新设计的 Robot Operating System&#xff0c;无论从用户API接口到底层实现都进行了改进。这里主要关注ROS2 的中间件。 1. 通信模式 ROS2 使用DDS协议进行数据传输&#xff0c;并通过抽象的rmw&#xff0c;支持多个厂家的DDS实现&#xff08;FastDDS&#xff0c;Cyc…

Django 执行原生SQL

在Django中&#xff0c;你可以使用Raw SQL queries来执行原生的SQL查询。这对于需要进行复杂查询或Django的ORM无法满足的查询非常有用。 1&#xff0c;添加模型 Test/app11/models.py from django.db import modelsclass Post(models.Model):title models.CharField(max_le…

arthas:介绍

文章目录 一、Arthas&#xff08;阿尔萨斯&#xff09;能为你做什么&#xff1f;二、运行环境要求三、快速安装四、卸载五、通过浏览器连接arthas 一、Arthas&#xff08;阿尔萨斯&#xff09;能为你做什么&#xff1f; Arthas是Alibaba开源的Java诊断工具&#xff0c;深受开发…

buu--web做题(4)

目录 [BJDCTF2020]ZJCTF&#xff0c;不过如此 [BUUCTF 2018]Online Tool [BJDCTF2020]ZJCTF&#xff0c;不过如此 <?phperror_reporting(0); $text $_GET["text"]; $file $_GET["file"]; if(isset($text)&&(file_get_contents($text,r)&q…

vue3运行若依前后台项目步骤(2024-07-19)

环境配置 1、jdk > 1.8 (我的1.8&#xff09; 2、mysql >5.7 (我的5.8&#xff09; 3、navicat (数据库管理器&#xff0c;连接mysql使用 ,我的是15) 4、mysql&#xff08;数据库&#xff0c;我的5.0&#xff09; 4、npm (我的是18.20.0) 5、idea编辑器,webtorm &#x…

Unity UGUI 之 Graphic Raycaster

本文仅作学习笔记与交流&#xff0c;不作任何商业用途 本文包括但不限于unity官方手册&#xff0c;唐老狮&#xff0c;麦扣教程知识&#xff0c;引用会标记&#xff0c;如有不足还请斧正 首先手册连接如下&#xff1a; Unity - Manual: Graphic Raycaster 笔记来源于&#xff…

Mike Ferguson:我在麻省理工学院人工智能研究实验室工作一年学到了 5 件事

Mike Ferguson &#xff0c;麻省理工学院大脑和认知科学系 (MIT BCS) 担任研究软件工程师 / ML 工程师。专门研究 Brain-Score&#xff08;一种衡量类脑 AI 的工具&#xff09;。他于 2021 年春季毕业于弗吉尼亚大学&#xff0c;获得计算机科学和应用数学学士学位&#xff0c;以…

【数字电路学习新助手】掌握电路仿真软件,开启数字电路知识的新篇章

在信息科技日新月异的今天&#xff0c;数字电路知识的重要性不言而喻。无论是通信工程、计算机科学与技术&#xff0c;还是电子信息技术等领域&#xff0c;数字电路都是基础中的基础。然而&#xff0c;对于初学者来说&#xff0c;数字电路的学习往往充满了挑战。幸运的是&#…

JUC 06 锁 开始

01.volatile 02.synchronized 03.lock

深入分析 Android ContentProvider (一)

文章目录 深入分析 Android ContentProvider (一)1. Android 中的 ContentProvider 设计说明1.1. ContentProvider 的设计初衷1.2. ContentProvider 的基本结构1.3. ContentProvider 的实现示例&#xff1a;实现一个简单的 ContentProvider 1.4. ContentProvider 的使用 2. Con…

小柴带你学AutoSar系列三、标准和规范篇(1)General

flechazo 小柴冲刺嵌入式系统设计师系列总目录 小柴带你学AutoSar总目录 缘起 一个小小的介绍啦&#xff0c;逐字逐句读规范。&#x1f61c; 不求能记住多少❤️ 只是希望将知识串起来&#xff0c;用到的时候能快速找到就好啦&#xff01; 一起学习AUTOSAR的规范吧 下面呢…

matlab中plot的一些用法

文章目录 一、基本用法二、绘制多个数据集三、设置线型、颜色四、添加标题和标签五、添加图例六、设置轴范围七、绘制网格八、 在同一图中绘制多个子图九、绘制带误差条的图十、绘制半对数图和对数图十一、绘制填充区域图十二、综合案例 一、基本用法 x 0:0.1:10; y sin(x);…

主流大数据调度工具DolphinScheduler之数据采集

今天继续给大家分享主流大数据调度工具DolphinScheduler&#xff0c;以及数据的ETL流程。 一&#xff1a;调度工具DS 主流大数据调度工具DolphinScheduler&#xff0c; 其定位&#xff1a;解决数据处理流程中错综复杂的依赖关系 任务支持类型&#xff1a;支持传统的shell任…

甲骨文闲置ARM实例防回收的方法

前几日挖了个大坑&#xff0c;今天补一下&#xff0c;谈谈甲骨文闲置实例如何防止回收。 回收原则 2022年11月16日 Oracle添加声明&#xff1a; 从 2022 年 11 月 24 日开始&#xff0c;您闲置的 Always Free 计算实例可能会停止。巴拉巴拉&#xff0c;您还可以随时升级您的帐…

Java数据结构(三)——顺序表

文章目录 顺序表前置知识ArrayList的构造ArrayList的常用方法ArrayList的遍历ArrayList的扩容机制ArrayList的模拟实现ArrayList的相关练习 顺序表 前置知识 顺序表是线性表的一种&#xff08;底层是数组&#xff09;&#xff0c;另一种是链表&#xff0c;说到线性表&#xf…

Three.JS飞入定位模型的位置。

源码 flyTo(object, gltfthis) {if (object undefined || object null) {return;}const box3 new THREE.Box3();box3.expandByObject(object); // 计算模型包围盒const size new THREE.Vector3();box3.getSize(size); // 计算包围盒尺寸const center new THREE.Vector3();…

【stm32项目】基于stm32智能宠物喂养(完整工程资料源码)

基于STM32宠物喂养系统 前言&#xff1a; 随着人们生活幸福指数的提高&#xff0c;越来越多的家庭选择养宠物来为生活增添乐趣。然而&#xff0c;由于工作等原因&#xff0c;许多主人无法及时为宠物提供充足的食物与水。为了解决这一问题&#xff0c;我设计了一款便捷的宠物喂…