数据结构学习之路-队列

队列(Queue)

  • 定义
  • 队列的接口设计(使用双向链表)
  • 用栈实现队列的接口设计
  • 双端队列(Deque)
  • 循环队列(Circle Queue)
  • 循环双端队列(Ciecle Deque)

定义

队列是一种特殊的线性表,只能在头尾两端进行操作。

  • 队尾(rear):只能从队尾添加元素,一般叫做enQueue,入队
  • 队头(front):只能从队头移除元素,一般叫做deQueue,出队
  • 先进先出原则,First In First Out,FIFO

在这里插入图片描述

队列的接口设计(使用双向链表)

在这里插入图片描述

PS:出队是从队列(Queue)中删除队头元素,而获取队头元素仅仅是为了访问,不删除。

队列(Queue)的内部实现是否可以直接利用以前学习过的数据结构呢?答案当然是可以。那使用链表还是数组呢?

我为什么要问这样一个问题呢?当然是为了复杂度而考虑。

因为队列(Queue)只能在队头或者队尾操作。对于数组来说,移除队尾元素复杂度是O(1),但是移除队头元素,复杂度就是O(n)了。单向链表移除队尾元素复杂度是O(n)。所以,这里最好的选择就是双向链表!!

因为,双向链表操作头结点和尾结点都是复杂度是O(1),非常适合作为队列(Queue)的底层框架。

下面我们来设计队列的接口,看看是怎么利用双向链表这一数据结构的。

  • 首先队列(Queue)就是使用双向链表,只不过封装在队列(Queue)的函数内。让调用者觉得是在调用队列(Queue),本质是调用双向链表。
public class Queue<Type> {
    private List<Type> list = new DoubleLinkedList<>();
	//------
}
  • 调用size()函数,返回队列的长度。我们这样设计,队列就是双向链表,因此直接清空双向链表就行了。
/**
 * 返回队列的长度
 * @return
 */
public int size(){
    return list.size();
}
  • 调用isEmpty(),判断队列是否为空。那就是判断双向链表是否为空
/**
 * 判断队列是否为空
 * @return
 */
public boolean isEmpty() {
    return list.isEmpty();
}
  • 调用enQueue(Type element),执行入队操作。入队,只能从队尾入队,其实就是向队尾添加元素,那就是向链表尾部添加元素,调用双向链表的append(element)即可。
/**
 * 入队,只能从队尾入队,其实就是向队尾添加元素
 * @param element
 */
public void enQueue(Type element){
    list.append(element);
}
  • 调用deQueue(),完成队头元素出队操作。出队,只能从队头弹出元素,其实就是删除index=0处的节点,并返回该值
/**
 * 出队,只能从队头弹出元素,其实就是删除index=0处的节点,并返回该值
 * @return
 */
public Type deQueue(){
    Type front = list.remove(0);
    return front;
}
  • 调用front()函数,获得队头元素。本质就是访问链表的头结点。
/**
 * 获得队头元素,也就是访问队头元素,本质就是访问链表的头结点
 * @return
 */
public Type front(){
    return list.get(0);
}
  • 调用clear()函数,清空队列。本质就是清空队列所依赖的双向链表。
/**
 * 清空队列,因为队列就是双向链表,所以本质上就是清空双向链表
 */
public void clear(){
    list.clear();
}

下面我们来测试一下队列的入队和出队情况。

public class Main {
    public static void main(String[] args) {

        Queue<Integer> queue = new Queue<>();

		//先依次入队,11先入,最后44再入
        queue.enQueue(11);
        queue.enQueue(22);
        queue.enQueue(33);
        queue.enQueue(44);

		//不断地出队,并打印出队元素
        while (!queue.isEmpty()){
            System.out.println(queue.deQueue());
        }

    }
}

可以看出,先入的11,先出了。至此队列设计完成。
在这里插入图片描述

用栈实现队列的接口设计

栈(Stack)是后进先出,队列(Queue)则是先进先出。怎么用栈来实现队列的接口设计呢??

准备两个栈:inStack和outStacK

在这里插入图片描述

  • 入队时,push元素到inStack
  • 出队时,有两种情况需要判断:如果outStack为空,那就将inStack里的所有元素逐一弹出,push到outStack。然后从outStack中弹出栈顶元素完成此次的出队;如果outStack不为空,说明刚才inStack已经弹完元素到outStack,并且outStack的栈顶元素永远都是当前队列的队头。

我们来模拟一下这个过程:

1、11 22 33依次入栈(在调用者眼里就是依次入队)

在这里插入图片描述

2、此刻我要出队,那么应该先出11才对。但是只依靠inStack恐怕只能出队33,所以借助outStack,把inStack的元素依次弹出到outStack。如下所示,这时outStack就符合队列的要求了

在这里插入图片描述

3、此刻,我们弹出outStack的栈顶元素(就相当于完成出队的操作,因为出队的是11,符合队列特性)
在这里插入图片描述

这只是第一种情况,当outStack为空时的情况。当outStack不为空呢?

4、就如下图所示,现在outStack不为空,我还要出队。那就不用把inStack里的元素依次弹到outStack了。因为outStack都不为空了,我就继续出队就行了。

在这里插入图片描述

假设有如下操作:11入队,22入队,出队,33入队,出队

1、(11入队,22入队)入队的时候只将元素压入到inStack中
在这里插入图片描述

2、要出队了,判断outStack是否为空,如果为空,就把inStack逐一弹到outStack。显然现在outStack为空

在这里插入图片描述

3、11出队完成,符合队列性质
在这里插入图片描述

4、现在33要入队了,那就无脑push到inStack中就行了。

在这里插入图片描述
5、现在我要出队,怎么做?出队其做判断,如果outStack不为空,就直接弹出outStack的栈顶元素就行。

在这里插入图片描述
4、 我现在又要出队。出队前判断,如果为空,就把inStack逐一弹到outStack。显然现在outStack为空。然后把33弹到outStack后,再弹出。就完成了模拟队列的过程

总结用栈设计队列的过程:入栈(入队)不用做任何判断,无脑入inStack即可。出队(出栈)要做判断,判断当前outStack是否为空,如果为空就逐一把inStack元素弹到outStack中,然后outStack弹出栈顶完成出队;如果不为空,直接从outStack弹出栈顶完成出队。

接下来设计代码:

几个关键的地方需要注意。

  • 需要声明两个栈,一个inStack,一个outStack
public  Stack<Type> inStack = new Stack<>();
private Stack<Type> outStack = new Stack<>();
  • 判断队列是否为空,需要两个栈同时判断,都不为空才不为空,一个为空队列就为空
/**
 * 判断队列是否为空
 * @return
 */
public boolean isEmpty() {
    return inStack.isEmpty() && outStack.isEmpty();
}
  • 求队列的size,需要把两个栈的size加起来
/**
 * 返回队列的长度
 * @return
 */
public int size(){
    return inStack.size() + outStack.size();
}
  • 入队只需要管inStack即可
/**
 * 入队,只能从队尾入队,其实就是向队尾添加元素
 * @param element
 */
public void enQueue(Type element){
    inStack.push(element);
}
  • 最重要的来了!!出队:需要判断outStack是否为空,然后最后弹出outStack的栈顶元素(也就相当于是出队队头元素)
/**
 * 出队,只能从队头弹出元素,其实就是删除index=0处的节点,并返回该值
 * @return
 */
public Type deQueue(){

    if(!outStack.isEmpty()){
        return outStack.pop();
    }else {
        while (!inStack.isEmpty()){
            outStack.push(inStack.pop());
        }
        return outStack.pop();
    }

}
  • 清空,那就需要把两个栈都清空
/**
 * 清空队列,
 */
public void clear(){
    inStack.clear();
    outStack.clear();
}
  • 获取队头元素跟出队一样,只不过最终返回的是队头元素且不需要出队
/**
 * 获得队头元素,
 * @return
 */
public Type front(){
    if(!outStack.isEmpty()){
        return outStack.top();
    }else {
        while (!inStack.isEmpty()){
            outStack.push(inStack.pop());
        }
        return outStack.top();
    }
}

整个利用栈实现队列的程序如下:

public class QueueForStack<Type> {

    public  Stack<Type> inStack = new Stack<>();
    private Stack<Type> outStack = new Stack<>();

    /**
     * 返回队列的长度
     * @return
     */
    public int size(){
        return inStack.size() + outStack.size();
    }

    /**
     * 判断队列是否为空
     * @return
     */
    public boolean isEmpty() {
        return inStack.isEmpty() && outStack.isEmpty();
    }

    /**
     * 入队,只能从队尾入队,其实就是向队尾添加元素
     * @param element
     */
    public void enQueue(Type element){
        inStack.push(element);
    }

    /**
     * 出队,只能从队头弹出元素,其实就是删除index=0处的节点,并返回该值
     * @return
     */
    public Type deQueue(){

        if(!outStack.isEmpty()){
            return outStack.pop();
        }else {
            while (!inStack.isEmpty()){
                outStack.push(inStack.pop());
            }
            return outStack.pop();
        }

    }

    /**
     * 获得队头元素,
     * @return
     */
    public Type front(){
        if(!outStack.isEmpty()){
            return outStack.top();
        }else {
            while (!inStack.isEmpty()){
                outStack.push(inStack.pop());
            }
            return outStack.top();
        }
    }

    /**
     * 清空队列,
     */
    public void clear(){
        inStack.clear();
        outStack.clear();
    }

}

下面我们进行测试:

public class Main {
    public static void main(String[] args) {

        QueueForStack<Integer> queue = new QueueForStack<>();

        queue.enQueue(11);
        queue.enQueue(22);
        queue.enQueue(33);
        queue.enQueue(44);


        System.out.println(queue.deQueue());
        System.out.println(queue.deQueue());
        System.out.println(queue.deQueue());
        System.out.println(queue.deQueue());

    }
}

可以看出,完全实现了队列的先入先出。

在这里插入图片描述

双端队列(Deque)

双端队列(Deque) 是能在头尾两端添加、删除的队列!(普通队列智能在队尾添加元素,队头删除元素)

在这里插入图片描述

相比较于普通队列,双端队列(Deque)可以从队尾入队,也可以从队头入队;可以从队头出队,也可以从队尾出队。因此,接口就多了几个而已!

在这里插入图片描述

原理还是用双向链表实现。只不过从队尾入队就是list.append(element),从队头入队就是list.add(0, element)而已。从队头出队list.remove(0),那从队尾出队就是list.remove(size-1)呗。获取队头元素就是list.get(0),那获取队尾元素就是list.get(szie-1)呗。

这里就不一一实现了。

循环队列(Circle Queue)

其实队列不仅可以使用双向链表、栈实现,还可以使用动态数组实现。并且各项接口也可以优化到O(1)复杂度。

本节介绍的就是用动态数组实现的队列,叫做:循环队列。下面介绍一下循环队列到底是怎么回事。

循环队列用动态数组实现,所以本质就是一个数组。只不过呢,这个数组有一个特性:有一个队头指针(front)一直记录着当前队头的下标索引。

在这里插入图片描述

当我删除队头元素时,队头指针要挪到下一个队头处。(不像动态数组,我删除数组头元素,后面的元素还要向前移动)。循环队列只需要确定谁是队头元素就可以。

在这里插入图片描述

然后我不断地向队尾添加元素,一直添加到数组尾部(77)。按照动态数组的惯例,再次添加就需要动态扩容了。但是我输循环队列,我要查看当前数组容量是不是满了,如果不满,我就把(88)添加到索引为0的地方,形成一种循环的感觉。

在这里插入图片描述

接下来我们实现一下循环队列的接口:

  • 首先,循环队列依靠数组,并且有一个front记录队头的位置,还要有队列的size。既然有数组,那就得为数组分配存储空间,还有要初始空间的大小。因此,循环队列所需要的成员如下:
public class CircleQueue<Type> {

    private int size;
    private int front;
    private Type elements[];
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 默认数组长度为10
     */
    @SuppressWarnings("unchecked")
    public CircleQueue() {
        elements = (Type[]) new Object[DEFAULT_CAPACITY];
    }
}
  • size()isEmpty()跟数组一样就行
/**
 * 返回队列的长度,并不是数组的容量,而是存储在数组中队列的长度
 * @return
 */
public int size(){
    return size;
}

/**
 * 判断队列是否为空
 * @return
 */
public boolean isEmpty() {
    return size == 0;
}
  • 入队!也就是向目前队列的队尾添加元素,也就是向数组的某一个索引处添加元素。怎么确定这个索引呢?

在这里插入图片描述

例如上图:如果向4、5、6处添加元素,似乎只需要front + size就能确定对应的数组索引。因为front一直记录着队头的索引,size是队列的长度,也就是现在数组中元素的个数。入了三个元素(55,66,77),如下所示:

在这里插入图片描述

此时,我还要入队,按照循环队列的分析,应该转过头来,入在数组的index=0处。那么,front + size是否还可以确定这个索引呢?front + size = 2 + 5 = 7,显然数组下标越界。但是又想回到数组index=0处,这时只需要上数组长度就行了

在这里插入图片描述
也就是说,队尾的位置通过(front + size) % elements.length就可以唯一确定!!!下面时入队的接口设计:

/**
 * 入队,只能从队尾入队,其实就是向队尾添加元素
 * 由于是循环队列,因此当入队到了数组的末尾时还要入队,front + size这个索引就不准确了,此时必须模上数组的长度,回到数组头
 * 因此:(front + size) % elements.length这个索引才是可以应对循环队列的
 * @param element
 */
public void enQueue(Type element){
    elements[(front + size) % elements.length] = element;
    size++;
}
  • 出队!那就是删除队头元素。因为front不断地记录队头在数组中的位置,因此出队之后,front要++,同时size要--。但是!front还存在另一种情况:

如下图所示,(目前队列有两个元素,队头是55,队尾是66)此时,还要出队。55就没了,front++?front直接变成7了,数组越界了就。所以还要转过头来,将front移动到66处。所以front的下一步不能只是front+1,还要模上数组长度,也就是(front + 1) % elements.length

在这里插入图片描述

所以,出队的实现如下:

/**
 * 出队,只能从队头弹出元素,并返回该值
 * 由于在数组中使用一个索引记录着队头,因此这个队头要实时移动
 * 当移动到数组尾部时,这个队头还要循环回来,移动到数组头部,因此这块也需要模上数组长度
 * @return
 */
public Type deQueue(){
    Type front_element = elements[front];
    elements[front] = null;
    front = (front + 1) % elements.length;
    size--;
    return front_element;
}
  • 获取队头元素,那就取出队头索引处的数组值即可。
/**
 * 获得队头元素,也就是访问队头元素,那就是直接访问front处的数组内容
 * @return
 */
public Type front(){
    return elements[front];
}

至此,一个不需要扩容版的循环队列就设计好了。嗯?我为什么要这么说话?那是因为在刚才入队的时候,还可能会发生下面的情况

在这里插入图片描述

当我入队完99之后,我还想入队。数组此时满了,无论如何,我都没法入队了,否则就覆盖掉原先的值了。这时候怎么办?难道循环队列的长度要受到数组长度的限制?答案当然是不可以!!!!

因此,我们要在此基础上,为数组赋予动态扩容的功能。如下图所示:假设,我们的数组容量是3,此时入队满了,所以要申请一块更大的存储空间。问题的关键是:如何将原数组内的元素移动到新数组中

回忆数组的动态扩容:将原数组元素按照对应索引一个一个移动到新数组。但是,本质上我们是队列,而不是数组。因此,按照索引一个一个移动到新数组,队头和队尾就变了。所以我们要按照队列的索引,一个一个移动的新数组。如下图所示:

在这里插入图片描述

将队列按照从头到尾的顺序移动到新数组,并将front置位0。相对于以前来说,我们放到新数组的索引跟以前一样,从0开始一直到元素个数;但是,原数组的索引必须要遵循循环队列的性质了。也就是说,新数组的index=i处要放原数组index=?

新数组的i对应的是原数组的i+front,比如新数组的0处,对应放原数组的front处;新数组的1处,对应放原数组的front+1

所以,新数组的i处对应放原数组的i+front。但是循环队列,要上数组容量,这里不多说。所以,放在新数组index=i处的元素,对应原数组index= (i + front) % elements.length处。最大的改动就是这,如下所示:

for (int i = 0;i < size; i++){
    //把原来数组的队列,按照队列的顺序放到新的存储空间
    // newArrayPointer[i] = elements[i];//之前动态数组的移动法
    newArrayPointer[i] = elements[(i + front) % elements.length];
}

强调:index= (i + front) % elements.length其实就是队列中的元素,在数组中的真实索引!!!!!!

并且,把队列按照队头到队尾放在新数组时,队头就是从0开始了。所以,新数组中的新队列,front要置位0。整体代码如下:

/**
 * 保证扩容之后的容量要够用
 * @param capacity 想要保证的最小容量
 */
@SuppressWarnings("unchecked")
private void ensureCapacity(int capacity){
    int oldCapacity = elements.length; //返回数组的容量,length就是内存中的大小了,而不是真实的元素个数
    if (oldCapacity >= capacity) return; //如果数组的容量大于目前我想要的最小容量,说明不需要扩容

    int newCapacity = oldCapacity + (oldCapacity >> 1); //扩充1.5倍,为什么不直接乘1.5?因为浮点数运算更慢,>>1表示一个数除以2,这种效率更高

    Type[] newArrayPointer = (Type[]) new Object[newCapacity]; //新申请一块更大的存储空间,用于存放数组元素
    for (int i = 0;i < size; i++){
        //把原来数组的队列,按照队列的顺序放到新的存储空间
        newArrayPointer[i] = elements[(i + front) % elements.length];
    }
    elements = newArrayPointer;
    System.out.println("扩容:"+"oldCapacity:"+oldCapacity+" -> newCapacity:"+newCapacity);
    front = 0;
}

因此,入队的代码就添加上这一句即可:保证有每次入队前,数组都有size+1的容量。

public void enQueue(Type element){
    ensureCapacity(size + 1);
    elements[(front + size) % elements.length] = element;
    size++;
}

下面我们测试一下循环队列的实现:

CircleQueue<Integer> Cqueue = new CircleQueue<>();

//入队10个
for (int i = 0; i < 10 ; i++) {
    Cqueue.enQueue(i);
}
//再入队5个
for (int i = 10; i < 15 ; i++) {
    Cqueue.enQueue(i);
}
//全部出队
while (!Cqueue.isEmpty()){
    System.out.println(Cqueue.deQueue());
}

可以看到,出队的队头元素完全符合队列的先入先出原则,并且动态数组也成功扩容。

在这里插入图片描述

不知道有没有发现,循环队列最大最大的一个特点就是索引的处理。我们为什么要这么写索引呢:(front + size) % elements.length,是因为我们想根据数组的索引找到真实的循环队列的索引。这个过程就是数组索引到循环队列真实索引的一个映射过程!因此,我们可以封装一下:

private int index(int Arrayindex){
    return (Arrayindex + front) % elements.length;
}

最值得我们注意的一点就是:循环队列的索引映射过程!需要根据数组的真实索引,算出队列的真实索引。

循环双端队列(Ciecle Deque)

循环队列的入队和出队跟队列一样,只能从队头出队,只能从队尾入队。循环双端队列,顾名思义,可以从队头或者队尾入队出队。也就是在循环队列的基础上,增加一个双端操作的功能。

具体怎么实现呢?如果队头出队,队尾入队的话不需要任何变化。如果队头入队,只需要把放新元素的索引改为(front -1) % elements.length即可。但是要考虑一种情况,当队头就在数组的0处,还要入队,新索引就变成了-1,这是不允许的。

在这里插入图片描述
因此,我们要把新索引加上数组的长度,让其转到数组尾端。所以,索引映射需要更改:

private int index(int Arrayindex){
	if(Arrayindex < 0){
		return (Arrayindex + front) % elements.length + elements.length;
	}
    return (Arrayindex + front) % elements.length;
}

所以,对于循环双端队列来说,在队头入队时,需要额外注意边界情况。并且最后需要更新front

public void enQueue(Type element){
    ensureCapacity(size + 1);
    elements[index(-1)] = element;
    size++;
    front = index(-1);
}

如果是队尾出队,只需要找到size-1处的真实索引,然后置位null,并且size–即可。

/**
 * 出队,只能从队头弹出元素,并返回该值
 * 由于在数组中使用一个索引记录着队头,因此这个队头要实时移动
 * @return
 */
public Type deQueue(){
    Type rear_element = elements[index(size-1)];
    elements[index(size-1)] = null;
    size--;
    return rear_element ;
}

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

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

相关文章

企业短视频推广怎么玩?制造业短视频推广干货分享

短视频作为一种新型营销方式&#xff0c;已经成为企业推广的重要手段。通过合理的推广策略、精美的短视频制作、适当的社交媒体平台选择和与用户的互动&#xff0c;企业可以实现短视频推广的效果。同时&#xff0c;借助短视频制作工具可以提高制作效率和降低制作成本&#xff0…

文件IO知识(一)

作者&#xff1a;爱塔居 专栏&#xff1a;JavaEE 作者简介&#xff1a;大三学生&#xff0c;希望和大家一起进步。 文章目录 目录 文章目录 前言 一、路径 二、文本文件和二进制文件 三、文件系统操作 四、“字符流”和“字节流” 五、utf8和unicode 前言 平时谈到的“文件”&…

Spring 源码解析 - BeanPostProcessor 扩展接口

一、BeanPostProcessor 扩展接口 BeanPostProcessor是Spring中的一个扩展接口&#xff0c;它可以在Spring容器实例化bean之后&#xff0c;在执行 bean的初始化方法前后&#xff0c;允许我们自定义修改新的 bean实例。比如修改 bean 的属性&#xff0c;将 bean 替换为动态代理等…

《Effective Objective-C 2.0 》 阅读笔记 item6

第6条&#xff1a;理解“属性”这一概念 1. 属性的概念 “属性”&#xff08;property&#xff09;是Objective-C的一项特性&#xff0c;用于封装对象中的数据。 Objective-C对象通常会把所需要的数据保存为各种实例变量&#xff0c;实例变量一般通过“存取方法”&#xff08…

GPT-4 免费体验方法

POE 在Quora上非常受欢迎的手机聊天机器人Poe App已经集成ChatGPT助手&#xff01;除了最初集成的三个聊天机器人Sage、Claude和Dragonfly外&#xff0c;Poe现在还加入了第四位ChatGPT。由于使用了ChatGPT API&#xff0c;因此Poe拥有真正的ChatGPT。 现在更是第一批集成了GP…

JDK1.8去除永久代引入元空间的原因您知道吗

之前写了一篇文章 JVM中的堆和栈到底存储了什么 重点介绍了Java虚拟机运行时数据区中堆、栈以及方法区存储数据的相关知识很受大家欢迎&#xff0c;今天来介绍一下jdk 1.8开始引入的元空间&#xff0c;元空间的引入也是与Java虚拟机运行时存储数据有关。 元空间 JDK8之后就没…

02-Maven高级-分模块开发、依赖传递、聚合、继承(SpringBoot的部分底层原理)、多模块开发(环境切换)、Nexus私服搭建与使用

文章目录学习目标一、分模块开发与设计1. 分模块开发的意义问题导入模块拆分原则2. 分模块开发&#xff08;模块拆分&#xff09;问题导入2.1 创建Maven模块2.2 书写模块代码2.3 通过maven指令安装模块到本地仓库&#xff08;install指令&#xff09;2.4 代码演示二、依赖管理1…

高低温真空磁场探针台T8-EM5的技术参数

高低温真空磁场探针台是具备提供高低温、真空以及磁场环境的高精度实验台&#xff0c;它的诸多设计都是专用的。因此&#xff0c;高低温磁场探针台的配置主要是根据需求进行选配及设计。例如&#xff0c;要求的磁场值&#xff0c;均匀区大小、均匀度大小、样品台的尺寸等&#…

OJ系统刷题 第三篇

11202 - 任意两个数的和 时间限制 : 1 秒 内存限制 : 128 MB 编程序&#xff0c;电脑任意输入两个整数&#xff0c;计算出他们的和。 输入 a b&#xff08;a b为整数&#xff0c;范围是-2,147,483,648~2,147,483,647&#xff09; 输出 ab的和 样例 输入 1 1 输出 2 答案&a…

含分布式电源的配电网可靠性评估研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

外网访问本地Tomcat服务器【cpolar内网穿透】

文章目录前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置3.公网访问测试4.结语前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff08;让…

第二届ACC(AcWing Cup)全国联赛 C4943. 方格迷宫

题意 题目大意就是给定一个地图&#xff0c;给定一个起点和终点&#xff0c;要求我们以最小步数到达终点&#xff0c;其中不可以落入陷阱并且每步可以走1−−k步题目大意就是给定一个地图&#xff0c;给定一个起点和终点&#xff0c;要求我们以最小步数到达终点&#xff0c;其中…

基于粒子群优化算法的分布式电源选址与定容【多目标优化】【IEEE33节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

crud删除(1.5小时)

一、servlet删除 页面效果 删除一个重复的韩非&#xff0c;可以看到无论是list显示还是navicate全都删除成功了 编写servlet页面时一定要注意&#xff0c;我们不光要在list页面开辟一个新的单元格以及加上超链接&#xff0c;还要给它传入当前行的id参数&#xff0c;这样delete…

企业如何利用大数据精准获客

打造大数据硬核组织 运营商大数据精准获客&#xff0c;助力企业高效获客 导语 获客难、成本高一直是困扰各个企业的一大难点。在大数据获客弥漫的今天&#xff0c;我们仿佛看见了眼前影影绰绰的都是客户&#xff0c;但当伸手去抓&#xff0c;却发现寥寥无几&#xff0c;什么…

Web-Http基本概念(请求与响应)

目录 1、http请求 &#xff08;1&#xff09;get &#xff08;2&#xff09;host &#xff08;3&#xff09;accept &#xff08;4&#xff09;referer &#xff08;5&#xff09;accept-language &#xff08;6&#xff09;user-agent 2、http响应 &#xff08;1&…

Linux 文件系统是怎么工作的?

同 CPU、内存一样&#xff0c;磁盘和文件系统的管理&#xff0c;也是操作系统最核心的功能。 磁盘为系统提供了最基本的持久化存储。 文件系统则在磁盘的基础上&#xff0c;提供了一个用来管理文件的树状结构。 那么&#xff0c;磁盘和文件系统是怎么工作的呢&#xff1f;又有…

毕业设计源码基于springboot的旧物置换系统的实现

摘 要 随着时代在一步一步在进步&#xff0c;旧物也成人们的烦恼&#xff0c;许多平台网站都在推广自已的产品像天猫、咸鱼、京东。所以开发出一套关于旧物置换网站成为必需。旧物置换网站主要是借助计算机&#xff0c;通过对用户进行管理。为减少管理员的工作&#xff0c;同…

WEB前端作业——banner的切换

实现banner的左右切换按钮 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title><style>div,ul,li,a,span,img{margin:0;padding:0;}#banner { overflow:hidden; width:100%; height:400px; position:rela…

火速上线zkSync Era主网,盘点SpaceFi的Web3布局

最近zkSync Era主网的上线引发了市场对Layer2的和零知识证明技术的关注&#xff0c;而作为Web3跨链应用平台的SpaceFi也在第一时间对zkSync Era进行了支持&#xff0c;并与3月28日上线DEX、Farm、Plant NFT等多个产品&#xff0c;一时间成为zkSync上的热门生态项目。打造一站式…