[Collection与数据结构] PriorityQueue与堆

1. 优先级队列

1.1 概念

前面介绍过队列,队列是一种先进先出(FIFO)的数据结构,但有些情况下,操作的数据可能带有优先级,一般出队列时,可能需要优先级高的元素先出队列,该中场景下,使用队列显然不合适,比如:在手机上玩游戏的时候,如果有来电,那么系统应该优先处理打进来的电话.
在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。
在这种情况下,数据结构应该提供两个最基本的操作,一个是返回最高优先级对象,一个是添加新的对象。这种数据结构就是优先级队列(Priority Queue)。
在这里插入图片描述

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

PriorityQueue底层实现使用了这种数据结构,堆实际上就是在完全二叉树的基础上做了一些调整,使得里面的元素按照一定地规则排列,而堆的元素存储在一个数组中.

2.1 堆的概念

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >= K2i+2) i = 0,1,2…,则称为 小堆(或大堆)也就是任意拿出一棵子树来,子结点都比父结点小(或大).将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆
在这里插入图片描述

2.2 堆的性质

  • 堆是一颗完全二叉树
  • 堆中的某个结点的值总是不大于或者不小于父结点的值

2.3 堆的存储方式

从堆的概念可知,堆是一棵完全二叉树,因此可以层序的规则采用顺序的方式来高效存储.
实例如上图所示.
将元素存储到数组中后,可以根据二叉树章节的性质对树进行还原。假设i为节点在数组中的下标,则有:

  • 如果i为0,则i表示的节点为根节点,否则i节点的双亲节点为 (i - 1)/2
  • 如果2 * i + 1 小于节点个数(前提),则节点i的左孩子下标为2 * i + 1,否则没有左孩子
  • 如果2 * i + 2 小于节点个数(前提),则节点i的右孩子下标为2 * i + 2,否则没有右孩子

2.4 堆的创建

2.4.1 向下调整创建堆

对于集合{ 27,15,19,18,28,34,65,49,25,37 }中的数据,如果将其创建成堆呢?
首先将数组中的元素按照二叉树层序遍历的方法进行存放.
在这里插入图片描述
接下来,我们要做的就是对上面这颗二叉树进行向下调整.那么如何向下调整呢:我们以大根堆为例来说明.

  • 从堆的最后一棵子树开始比较父结点和子结点的大小关系(之所以从最后一棵子树开始调整,是因为必须让子树满足堆的特性,后续才好调整).使用parent标记最后一棵子树的父节点,使用child标记最后一棵子树的左孩子.
  • 把parent的值和child的值进行比较:
    • 比较右孩子的值是否大于左孩子,如果大于,child++,child变量引用移动到右孩子上.
    • 比较parent和child的值,如果child的值大于parent的值,则交换两个值,如果不是则停止向下调整.
    • 之后把parent和child向下移动,再次向下调整,即parent = child,child = child*2+1.直到child的值大于存储元素的数组的最后一个下标.
  • 之后把parent向上移动,即parent–,继续上述过程.
    下面画图来举例,我们给定一个任意完全二叉树:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    之后以此类推…
    为什么要从子树开始调整呢?因为首先要保证子树是大根堆,调整根层数更小的树的时候才会好调整.就像我们盖房子一样,先要把地基打牢,才可以盖得起高楼大厦.
    在这里插入图片描述
    完整过程如下:
    在这里插入图片描述

接下来我们通过代码来创建一个大根堆:

public class MyPriorityQueue {public int usedSize;public int[] elem;//堆通过数组来实现public MyPriorityQueue(int[] elem) {this.elem = elem;usedSize += elem.length;}public void createBigHeap(){for (int parent = (usedSize-2)/2; parent >= 0 ; parent--) {//从最后一棵子树开始调整//之后根向上父结点向上走shiftDown(parent,usedSize-1);//向下调整//子结点永远是这棵树的最后一个结点}}/*** 向下调整* @param parent 父节点* @param end 结束位置*/private void shiftDown(int parent,int end){int child = 2*parent+1;//此时child是左孩子while (child <= end){if (child+1 < usedSize && elem[child] < elem[child+1]){//若右孩子存在,并且大于左孩子child++;//让child到右孩子这里}if (elem[parent] < elem[child]){swap(parent,child);parent = child;child = 2*child+1;}else {break;//如果父结点不大于子结点,说明已经调整完成,因为是从下往上调的}}}private void swap(int s1,int s2){int tmp = elem[s1];elem[s1] = elem[s2];elem[s2] = tmp;}
}

接下来我们来看小根堆如何进行调整,小根堆的调整恰好和大根堆相反,小根堆的调整是从根节点开始调整,直到叶子结点.

  1. 让parent标记需要调整的结点,child标记parent的左孩子节点.
  2. 如果parent的左孩子存在,即child<size,进行以下的操作,直到parent的左孩子不存在.
    • parent右孩子是否存在,存在的话,就找到左右孩子中最小的孩子,让child进行标注.
    • 将parent和child进行比较,如果parent的值小于child,停止调整.否则交换parent和child的值,交换完成之后parent的值和child的值向下移动,即parent = child;child = parent*2+1之后重复上述步骤,直到child大于存储元素数组的最后一个位置.
  3. 之后让parent向下移动,即parent++.重复上述步骤,直到parent到达最后一棵子树.
    在这里插入图片描述
    代码实现:
/*** 创建小根堆*/
public void createSmallHeap(){for (int parent = 0;parent <= (usedSize-2)/2;parent++){shiftDown2(parent,usedSize-1);}
}/*** 小根堆向下调整*/
private void shiftDown2(int parent,int end) {int child = parent*2+1;if (child+1 < usedSize && elem[child] > elem[child+1]){child++;}while(child <= end){if (elem[parent] > elem[child]){swap(parent, child);parent = child;child = 2*child+1;}else{break;}}
}

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

2.4.1 创建堆的时间复杂度推导

在这里插入图片描述
因此:建堆的时间复杂度为O(N).

2.5 堆的插入与删除

2.5.1 堆的插入

堆的插入分下面两个步骤:

  • 把结点插入树的最后一个位置
  • 把这个结点进行向上调整

那么,向上调整又如何调整呢?我们以大根堆为例

  • 把插入元素所在的结点与父节点进行比较.
  • 如果父结点小于插入结点,则交换两个结点,否则停止向上调整.
  • 如果成功交换,重复上述步骤,知道父结点大于子结点或者比较到根结点.
    下面我们通过代码来展示:
/*** 插入元素* @param val 要插入的值*/public void offer(int val){if (isFull()){this.elem = Arrays.copyOf(elem,elem.length*2);//如果数组满了的话,需要进行扩容}elem[usedSize] = val;usedSize++;shiftUp(usedSize-1);//这里注意是usedSize-1,因为usedSize++过,现在需要//向上调整的元素是usedSize-1位置上的元素}/*** 向上调整* @param child 因为向上调整的终点都是根结点,所以传入child*/private void shiftUp(int child){int parent = (child - 1)/2;while (child > 0){if (elem[parent] < elem[child]){swap(elem[parent],elem[child]);child = parent;parent = (parent-1)/2;}else {break;}}}/*** 判断堆元素是否为满* @return*/private boolean isFull(){if (elem.length == usedSize){return true;}else {return false;}}

2.5.2 堆的删除

注意:在删除元素的时候,一定删除的是堆顶元素.

  • 将堆顶元素和最后一个元素进行交换
  • 删除最后一个元素
  • 将堆顶结点进行向下调整
    在这里插入图片描述
    下面通过代码来展示:
/**
* 删除堆顶元素
*/
public void poll(){if (usedSize == 0){return;}swap(elem[0],elem[usedSize-1]);usedSize --;shiftDown(0,usedSize-1);
}

这里需要注意的是,与堆的创建不同,这里只需要堆根节点进行向下调整即可,不需要堆整棵树进行向下调整.

3. PriorityQueue

3.1 PriorityQueue的性质

Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,本文主要介绍PriorityQueue.
关于PriorityQueue的使用要注意:

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

3.2 PriorityQueue的使用

  1. 构造方法
构造器功能介绍
PriorityQueue()创建一个空的优先级队列,默认容量是11
PriorityQueue(int initialCapacity)创建一个初始容量为initialCapacity的优先级队列,注意:initialCapacity不能小于1,否则会抛IllegalArgumentException异常
PriorityQueue(Collection<? extends E> c)用一个集合来创建优先级队列
public PriorityQueue(Comparator<? super E> comparator)传入比较器----改变元素之间的比较规则

源码如下:

private static final int DEFAULT_INITIAL_CAPACITY = 11;//默认容量
public PriorityQueue() {this(DEFAULT_INITIAL_CAPACITY, null);}
public PriorityQueue(int initialCapacity) {//用户传入自定义容量this(initialCapacity, null);}
public PriorityQueue(Comparator<? super E> comparator) {this(DEFAULT_INITIAL_CAPACITY, comparator);//通过比较器改变优先级队列中元素的比较规则}
public PriorityQueue(Collection<? extends E> c) {if (c instanceof SortedSet<?>) {SortedSet<? extends E> ss = (SortedSet<? extends E>) c;this.comparator = (Comparator<? super E>) ss.comparator();initElementsFromCollection(ss);}else if (c instanceof PriorityQueue<?>) {PriorityQueue<? extends E> pq = (PriorityQueue<? extends E>) c;this.comparator = (Comparator<? super E>) pq.comparator();initFromPriorityQueue(pq);}else {this.comparator = null;initFromCollection(c);}}

使用实例:

static void TestPriorityQueue(){// 创建一个空的优先级队列,底层默认容量是11PriorityQueue<Integer> q1 = new PriorityQueue<>();// 创建一个空的优先级队列,底层的容量为initialCapacityPriorityQueue<Integer> q2 = new PriorityQueue<>(100);ArrayList<Integer> list = new ArrayList<>();list.add(4);list.add(3);list.add(2);list.add(1);// 用ArrayList对象来构造一个优先级队列的对象// q3中已经包含了三个元素PriorityQueue<Integer> q3 = new PriorityQueue<>(list);System.out.println(q3.size());System.out.println(q3.peek());}

通过传入比较器来创建大根堆,需要注意的是,这里在创建比较器的时候,需要实现Comparator接口(注意单词的拼写),在接口之后指定泛型参数,并且重写compare()方法.

public class Compare implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2-o1;}
}
public class Main {public static void main(String[] args) {PriorityQueue<Integer> priorityQueue1 = new PriorityQueue<>(new Compare());priorityQueue1.offer(1);priorityQueue1.offer(2);priorityQueue1.offer(3);priorityQueue1.offer(4);priorityQueue1.offer(5);System.out.println(priorityQueue1);//通过传入比较器对象,可以构建大根堆}
}

也可以通过传入比较器使得不可比较的对象变为可比较的对象:

public class Student{public String name;public int age;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}public Student(String name, int age) {this.name = name;this.age = age;}
}
import java.util.Comparator;/*** 年龄比较规则*/
public class Compare1 implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o2.age - o1.age;}
}
public class Main {public static void main(String[] args) {PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>(new Compare1());//由于Student没有默认创建比较方法,所以必须传入比较器对象,否者异常priorityQueue2.offer(new Student("zhangsan",12));priorityQueue2.offer(new Student("lisi",17));priorityQueue2.offer(new Student("wangwu",19));System.out.println(priorityQueue2);//按照年龄进行大根堆构建}
}

我们也可以使用lambda表达式代替比较器.其中lambda表达式中,括号中的两个参数表示的是优先级队列中的两个对象,箭头后面表示的是比较的规则.

public class Student{public String name;public int age;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}public Student(String name, int age) {this.name = name;this.age = age;}
}
public class Main {public static void main(String[] args) {PriorityQueue<Student> priorityQueue2 = new PriorityQueue<>((s1,s2) -> s2.age-s1.age);//由于Student没有默认创建比较方法,所以必须传入比较器对象,否者异常priorityQueue2.offer(new Student("zhangsan",12));priorityQueue2.offer(new Student("lisi",17));priorityQueue2.offer(new Student("wangwu",19));System.out.println(priorityQueue2);//按照年龄进行大根堆构建}
}

也可以不传入比较器,在类中实现Comparable接口,让这个对象变成一个可比较的对象,并重写其中的compareTo方法:

public class Student implements Comparable<Student>{public String name;public int age;@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic int compareTo(Student o) {return this.name.compareTo(o.name);}
}
public class Main {public static void main(String[] args) {PriorityQueue<Student> priorityQueue3 = new PriorityQueue<>();priorityQueue3.offer(new Student("zhang",12));priorityQueue3.offer(new Student("li",15));priorityQueue3.offer(new Student("wang",17));System.out.println(priorityQueue3);}
}
  1. 插入,删除,获取优先级队列中的方法
函数名功能介绍
boolean offer(E e)插入元素e,插入成功返回true,如果e对象为空,抛出NullPointerException异常,时间复杂度 ,注意:空间不够时候会进行扩容
E peek()获取优先级最高的元素,如果优先级队列为空,返回null
E poll()移除优先级最高的元素并返回,如果优先级队列为空,返回null
int size()获取有效元素的个数
void clear()清空
boolean isEmpty()检测优先级队列是否为空,空返回true
  1. 优先级队列的扩容
    jdk17 的源码如下:
public boolean offer(E e) {if (e == null)throw new NullPointerException();modCount++;int i = size;if (i >= queue.length)grow(i + 1);//大于队列大小的时候,进行扩容siftUp(i, e);size = i + 1;return true;}
private void grow(int minCapacity) {int oldCapacity = queue.length;// Double size if small; else grow by 50%int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity, /* minimum growth */oldCapacity < 64 ? oldCapacity + 2 : oldCapacity >> 1/* preferred growth */);queue = Arrays.copyOf(queue, newCapacity);}

优先级队列的扩容说明:

  • 容量小于64的时候,按照2倍扩容.
  • 容量大于64的时候,按照1.5倍扩容.

4. top-k问题

这种算法一般适用于数据比较大的情况下,比如要在1亿,甚至10亿数据中找出前k的数据.
OJ链接
在这里插入图片描述
这里在寻找top-k元素的时候为什么要创建大根堆呢,因为在元素与对顶元素比较的时候,比较的是大根堆中最大的元素,如果说这个元素比大根堆中最大的元素小的话,那么就会替换掉大根堆中最大的元素,每次删除的时候都删除的是堆中最大的元素, 最终保证大根堆中都是比较小的元素, 最终堆顶就是k个元素中最大的元素,即第k小的元素.

class BigComparator implements Comparator<Integer> {@Overridepublic int compare(Integer o1, Integer o2) {return o2-o1;//通过比较器来创建大根堆}
}
public class Top_k {public int[] smallestK(int[] arr, int k) {if (k <= 0){;return new int[0];//如果k==0,返回空数组}PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new BigComparator());//先取前k个元素放入for (int i = 0; i < k; i++) {priorityQueue.offer(arr[i]);}//如果堆顶元素大于遍历到的元素,删除堆顶元素,让遍历到的元素进来for (int i = k; i < arr.length; i++) {if (priorityQueue.peek() > arr[i]){priorityQueue.poll();priorityQueue.offer(arr[i]);}}int[] array = new int[k];//使用数组取出前k个元素for (int i = 0; i < k; i++) {array[i] = priorityQueue.poll();}return array;}
}

当然我们在每次入队列的时候也可以不与堆顶元素进行比较,直接让元素入队列,只需要保证每次队列中的元素是k个即可.如果队列中的元素个数超过了k个,就把堆顶元素弹出.
这种情况一般用于arr.length < k的情况,如果是上面的代码去跑arr.length < k的情况,会报出数组越界异常.

class Solution {public int[] smallestK(int[] arr, int k) {PriorityQueue<Integer> q = new PriorityQueue<>((x,y)->y-x);int[] ret = new int[k];for (int i = 0;i < arr.length;i++){q.offer(arr[i]);if (q.size() > k){q.poll();}}for (int i = 0;i < k;i++){ret[i] = q.poll();}return ret;}
}

其实这种做法和上面那种做法本质上是一样的,如果入了一个比堆顶元素大的元素,那么优先级队列在重新调整的时候,这个元素依然会把这个元素调整到堆顶,堆顶还是会被弹出,相当于这个元素没有进来过,如果小于堆顶元素,那么调整后的堆顶元素就不是这个元素,下一次出去的元素也不是这个元素,就相当于这个元素进入了队列.

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32网络通讯之CubeMX实现LWIP项目设计(十五)

STM32F407 系列文章 - ETH-LWIP-CubeMX&#xff08;十五&#xff09; 目录 前言 一、软件设计 二、CubeMX实现 1.配置前准备 2.CubeMX配置 1.ETH模块配置 2.时钟模块配置 3.中断模块配置 4.RCC及SYS配置 5.LWIP模块配置 3.生成代码 1.main文件 2.用户层源文件 3.…

【React】静态组件动态组件

目录 静态组件动态组件创建一个构造函数(类)使用 class 实现组件**使用 function 实现类组件** 静态组件 函数组件是静态组件&#xff1a; 组件第一次渲染完毕后&#xff0c;无法基于内部的某些操作让组件更新「无法实现自更新」&#xff1b;但是&#xff0c;如果调用它的父组…

我的世界-与门、或门、非门等基本门电路实现

一、红石比较器 (1) 红石比较器结构 红石比较器有前端单火把、后端双火把以及两个侧端 其中后端和侧端是输入信号,前端是输出信号 (2) 红石比较器的两种模式 比较模式 前端火把未点亮时处于比较模式 侧端>后端 → 0 当任一侧端强度大于后端强度时,输出…

持续集成 01|Gitee介绍、Pycharm使用Gitee

目录 一、理论 二、 git的简介与安装 三、Gitee 1、注册网易163邮箱 2、注册Gitee账号 3、git和gitee管理代码工作原理 三、PyCharm安装配置Gitee 四、Pycharm使用Gitee插件的五种场景 1、将 Gitee的新仓库 Checkout&#xff08;检出&#xff09;到 Pycharm中 2、推送…

【Qt】03-页面切换

前言一、按键实现界面切换1.1 创建新的类文件1.1.1 创建1.1.2 细节选择 1.2 代码以及需要注意的点mywidget.cppsecondwidget.cppmywidget.hsecondwidget.h 1.3 结果展示 二、signal关键字2.1 代码以及解释mywidget.cppsecondwidget.cppmywidget.hsecondwidget.h解释 2.2 现象 三…

软件授权管理中的软件激活向导示例

软件激活向导示例 在软件许可中&#xff0c;提供许可应该是简单和安全的。这适用于想要在中央许可证服务器上创建新许可证的软件开发人员&#xff0c;也适用于需要在其设备上获得许可证的最终用户。如果所讨论的系统有互联网连接&#xff0c;或是暂时的连接&#xff0c;就可以…

02JavaWeb——JavaScript-Vue(项目实战)

一、JavaScript html完成了架子&#xff0c;css做了美化&#xff0c;但是网页是死的&#xff0c;我们需要给他注入灵魂&#xff0c;所以接下来我们需要学习 JavaScript&#xff0c;这门语言会让我们的页面能够和用户进行交互。 1.1 介绍 通过JS/js效果演示提供资料进行效果演…

【java】java入门

盘符名称冒号---------盘符切换 dir---------------查看当前路径下的内容 cd目录--------进入单级目录 cd..----------回退到上一级目录 cd \----------回退到盘符目录 cls----------清屏 exit 为什么要配环境变量&#xff1f; 在任意的目录下都可以打开指定的软件。把软件的路…

利用爬虫获取某学习软件的考试题库(带源码)

首先要重新进行账号的登陆用来获取cookie 按下F12在控制台输入&#xff1a; // 获取当前页面的 cookies var cookies document.cookie.split(";");// 创建一个数组来存储 cookies 对象 var cookieArray [];// 遍历每个 cookie 并将其转换为对象 cookies.forEach(f…

【C++篇】红黑树的实现

目录 前言&#xff1a; 一&#xff0c;红黑树的概念 1.1&#xff0c;红黑树的规则 1.2&#xff0c;红黑树的最长路径 1.3&#xff0c;红黑树的效率分析 二&#xff0c;红黑树的实现 2.1&#xff0c;红黑树的结构 2.2&#xff0c;红黑树的插入 2.2.1&#xff0c;大致过程…

Autodl转发端口,在本地机器上运行Autodl服务器中的ipynb文件

通过 SSH 隧道将远程端口转发到本地机器 输入服务器示例的SSH指令和密码&#xff0c;将远程的6006端口代理到本地 在服务器终端&#xff0c;激活conda虚拟环境 conda activate posecnnexport PYOPENGL_PLATFORMegljupyter notebook --no-browser --port6006 --allow-root从…

镭速大文件传输视频文件预览实现原理

镭速可以支持视频预览&#xff0c;在测试过程中需要大量不同格式的视频&#xff0c;如果直接去找各种格式的视频不太现实&#xff0c;所以就会用到一个视频格式转换的工具ffmpeg&#xff0c;本文将介绍ffmpeg的基本使用方法。FFmpeg 是一个免费开源的音视频处理工具&#xff0c…

达梦8-DMSQL程序设计学习笔记1-DMSQL程序简介

1、DMSQL程序简介 DMSQL程序是达梦数据库对标准SQL语言的扩展&#xff0c;是一种过程化SQL语言。在DMSQL程序中&#xff0c;包括一整套数据类型、条件结构、循环结构和异常处理结构等&#xff0c;DMSQL程序中可以执行SQL语句&#xff0c;SQL语句中也可以使用DMSQL函数。 DMSQ…

C# 获取PDF文档中的字体信息(字体名、大小、颜色、样式等

在设计和出版行业中&#xff0c;字体的选择和使用对最终作品的质量有着重要影响。然而&#xff0c;有时我们可能会遇到包含未知字体的PDF文件&#xff0c;这使得我们无法准确地复制或修改文档。获取PDF中的字体信息可以解决这个问题&#xff0c;让我们能够更好地处理这些文件。…

git操作(Windows中GitHub)

使用git控制GitHub中的仓库版本&#xff0c;并在Windows桌面中创建与修改代码&#xff0c;与GitHub仓库进行同步。 创建自己的GitHub仓库 创建一个gen_code实验性仓库用来学习和验证git在Windows下的使用方法&#xff1a; gen_code仓库 注意&#xff0c;创建仓库时不要设置…

thinkphp6 + redis实现大数据导出excel超时或内存溢出问题解决方案

redis下载安装&#xff08;window版本&#xff09; 参考地址&#xff1a;https://blog.csdn.net/Ci1693840306/article/details/144214215 php安装redis扩展 参考链接&#xff1a;https://blog.csdn.net/jianchenn/article/details/106144313 解决思路&#xff1a;&#xff0…

设计模式-工厂模式/抽象工厂模式

工厂模式 定义 定义一个创建对象的接口&#xff0c;让子类决定实列化哪一个类&#xff0c;工厂模式使一个类的实例化延迟到其子类&#xff1b; 工厂方法模式是简单工厂模式的延伸。在工厂方法模式中&#xff0c;核心工厂类不在负责产品的创建&#xff0c;而是将具体的创建工作…

学习threejs,使用OrbitControls相机控制器

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.OrbitControls 相机控…

Linux:认识Shell、Linux用户和权限

谈起Linux&#xff0c;一般是在说区别于Windows的Linux操作系统&#xff0c;而Linux刚开始用来指Linux内核。值得一提的是&#xff0c;安卓底层也是Linux内核&#xff0c;而安卓系统又是区别于Linux的操作系统。 目录 一、Shell 为什么会有Shell 什么是Shell Shell相关具体…

HCIP-VLAN-hybrid接口+DHCP实验

1.拓扑及实验需求 2.需求分析 需要用到的技术 1、虚拟局域网&#xff08;VLAN&#xff09; 2、动态主机配置协议&#xff08;DHCP&#xff09; 3、单臂路由 首先先完成交换部分&#xff0c;创建对应的vlan将PC1与PC3划入vlan2且接口类型为access&#xff0c;而以外的PC都划…