数据结构 - 3(链表12000字详解)

news2024/11/24 11:31:59

一:LinkedList的使用

1.1 ArrayList的缺陷

上篇文章我们已经基本熟悉了ArrayList的使用,并且进行了简单模拟实现。由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移,时间复杂度为O(n),效率比较低,因此ArrayList不适合做任意位置插入和删除比较多的场景。因此:java集合中又引入了LinkedList,即链表结构。

1.2 链表的概念及结构

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。
在这里插入图片描述
在这里插入图片描述
实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

  • 单向或者双向
    在这里插入图片描述
  • 带头或者不带头
    在这里插入图片描述
  • 循环或者非循环
    在这里插入图片描述

虽然有这么多的链表的结构,但是我们重点掌握两种:

  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。

在这里插入图片描述

  • 无头双向链表:在Java的集合框架库中LinkedList底层实现就是无头双向循环链表。

1.3 LinkedList的使用

LinkedList的底层是双向链表结构(链表后面介绍),由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。
在这里插入图片描述
在集合框架中,LinkedList也实现了List接口,具体如下:
在这里插入图片描述
【说明】

  1. LinkedList实现了List接口
  2. LinkedList的底层使用了双向链表
  3. LinkedList没有实现RandomAccess接口,因此LinkedList不支持随机访问
  4. LinkedList的任意位置插入和删除元素时效率比较高,时间复杂度为O(1)
  5. LinkedList比较适合任意位置插入的场景

1.4 LinkedList的构造方法

LinkedList的构造方法

方法解释
LinkedList()无参构造
public LinkedList(Collection<? extends E> c)使用其他集合容器中元素构造List

public LinkedList(Collection<? extends E> c) 是 Java 中 LinkedList 类的一个构造方法。它的作用是创建一个新的 LinkedList 对象,并将指定集合中的所有元素添加到该链表中。

下面是代码示例:

List<String> list1 = new ArrayList<>();
list1.add("Apple");
list1.add("Banana");
list1.add("Orange");

LinkedList<String> linkedList = new LinkedList<>(list1);

在这个例子中,我们先创建了一个 ArrayList 类型的集合 list1,并向其中添加了三个字符串元素。然后我们使用 LinkedList 的构造方法将 list1 转换为一个新的 LinkedList 对象,并将 list1 中的所有元素添加到链表中。

1.5 LinkedList的常用方法

方法解释
boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c)尾插 c 中的元素
E remove(int index)删除 index 位置元素
boolean remove(Object o)删除遇到的第一个 o
E get(int index)获取下标 index 位置元素
E set(int index, E element)将下标 index 位置元素设置为 element
void clear()清空
boolean contains(Object o)判断 o 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List subList(int fromIndex, int toIndex)截取部分 list

下面是使用示例:

public static void main(String[] args) {
  LinkedList<Integer> list = new LinkedList<>();
  
   // add(elem): 表示尾插
  list.add(1); 
  list.add(2);
  list.add(3);
  list.add(4);
  list.add(5);
  list.add(6);
  list.add(7);
  System.out.println(list.size());
  System.out.println(list);
 
  // add(index, elem): 在index位置插入元素elem
  list.add(0, 0);  
  System.out.println(list);
  
 // remove(): 删除第一个元素,内部调用的是removeFirst()
  list.remove();     
  // removeFirst(): 删除第一个元素
  list.removeFirst();   
  // removeLast(): 删除最后元素
  list.removeLast();   
   // remove(index): 删除index位置的元素
  list.remove(1); 
  System.out.println(list);
 
  // contains(elem): 检测elem元素是否存在,如果存在返回true,否则返回false
  if(!list.contains(1)){
    list.add(0, 1);
 }
list.add(1);
  System.out.println(list);
  
  // indexOf(elem): 从前往后找到第一个elem的位置
  System.out.println(list.indexOf(1));  
   // lastIndexOf(elem): 从后往前找第一个1的位置
  System.out.println(list.lastIndexOf(1)); 

 // get(index): 获取指定位置元素
  int elem = list.get(0);  
  
   // set(index, elem): 将index位置的元素设置为elem
  list.set(0, 100);     
  System.out.println(list);
 
  // subList(from, to): 用list中[from, to)之间的元素构造一个新的LinkedList返回
  List<Integer> copy = list.subList(0, 3); 
  System.out.println(list);
  System.out.println(copy);

 // 将list中元素清空
  list.clear();        
  System.out.println(list.size());
}

1.6ArrayList和LinkedList的区别

不同点ArrayListLinkedList
存储空间上物理上一定连续逻辑上连续,但物理上不一定连续
随机访问支持O(1)不支持:O(N)
头插需要搬移元素,效率低O(N)只需修改引用的指向,时间复杂度为O(1)
插入空间不够时需要扩容没有容量的概念
应用场景元素高效存储+频繁访问任意位置插入和删除频繁

二:LinkedList模拟实现

2.1无头单向非循环链表

// 1、无头单向非循环链表实现
public class SingleLinkedList {
  //头插法
  public void addFirst(int data){
 }
  //尾插法
  public void addLast(int data){
 }
  //任意位置插入,第一个数据节点为0号下标
  public void addIndex(int index,int data){
 }
  //查找是否包含关键字key是否在单链表当中
  public boolean contains(int key){
    return false;
 }
  //删除第一次出现关键字为key的节点
  public void remove(int key){
 }
//删除所有值为key的节点
  public void removeAllKey(int key){
 }
  //得到单链表的长度
  public int size(){
    return -1;
 }
 //清空链表
  public void clear() {
 }
 //打印链表
  public void display() {}
}

下面是模拟实现后的代码:


public class SingleLinkedList {
    private Node head; // 链表头节点

    // 节点类
    private class Node {
        int data;
        Node next;

        public Node(int data) {
            this.data = data;
            this.next = null;
        }
    }

    // 头插法,在链表头部插入新节点
    public void addFirst(int data) {
        Node newNode = new Node(data); // 创建一个新节点
        newNode.next = head; // 将新节点的next指向当前头节点
        head = newNode; // 将新节点设置为头节点
    }

    // 尾插法,在链表尾部插入新节点
    public void addLast(int data) {
        Node newNode = new Node(data); // 创建一个新节点
        if (head == null) { // 如果链表为空,将新节点设置为头节点并返回
            head = newNode;
            return;
        }

        Node curr = head; // 从头节点开始遍历链表,找到最后一个节点
        while (curr.next != null) {
            curr = curr.next;
        }
        curr.next = newNode; // 将新节点链接到最后一个节点的next上
    }

    // 任意位置插入,在指定位置插入新节点
    public void addIndex(int index, int data) {
        if (index < 0 || index > size()) { // 检查插入位置的合法性
            throw new IndexOutOfBoundsException("Index out of range"); // 抛出越界异常
        }

        if (index == 0) { // 如果插入位置是头部,调用addFirst方法
            addFirst(data);
        } else if (index == size()) { // 如果插入位置是尾部,调用addLast方法
            addLast(data);
        } else { // 在指定位置插入新节点
            Node newNode = new Node(data); // 创建新节点
            Node prev = getNode(index - 1); // 获取插入位置前一个节点
            newNode.next = prev.next; // 将新节点的next指向插入位置节点的next
            prev.next = newNode; // 插入位置节点的next指向新节点
        }
    }

    // 查找是否包含关键字key
    public boolean contains(int key) {
        Node curr = head; // 从头节点开始遍历链表
        while (curr != null) {
            if (curr.data == key) { // 如果节点的data等于关键字key,返回true
                return true;
            }
            curr = curr.next; // 继续向下一个节点遍历
        }
        return false; // 遍历完链表都没有找到关键字key,返回false
    }

    // 删除第一次出现关键字为key的节点
    public void remove(int key) {
        if (head == null) { // 如果链表为空,直接返回
            return;
        }

        if (head.data == key) { // 如果头节点的data等于关键字key,将头节点指向下一个节点并返回
            head = head.next;
            return;
        }

        Node prev = head; // 设置prev指针指向头节点
        Node curr = head.next; // 设置curr指针指向头节点的下一个节点
        while (curr != null) { // 从头节点的下一个节点开始遍历链表
            if (curr.data == key) { // 如果节点的data等于关键字key,将prev节点的next指向当前节点的下一个节点并返回
                prev.next = curr.next;
                return;
            }
            prev = curr; // prev和curr指针向后移动
            curr = curr.next;
        }
    }

    // 删除所有值为key的节点
    public void removeAllKey(int key) {
        if (head == null) { // 如果链表为空,直接返回
            return;
        }

        // 处理头节点为key的情况
        while (head != null && head.data == key) { // 如果头节点的data等于关键字key,将头节点指向下一个节点
            head = head.next;
        }

        Node prev = head; // 设置prev指针指向头节点
        Node curr = head != null ? head.next : null; // 设置curr指针指向头节点的下一个节点
        while (curr != null) { // 从头节点的下一个节点开始遍历链表
            if (curr.data == key) { // 如果节点的data等于关键字key,将prev节点的next指向当前节点的下一个节点
                prev.next = curr.next;
            } else { // 否则prev指针和curr指针向后移动
                prev = curr;
            }
            curr = curr.next;
        }
    }

    // 得到单链表的长度
    public int size() {
        int count = 0; // 计数变量
        Node curr = head; // 从头节点开始遍历链表
        while (curr != null) {
            count++; // 每遍历一个节点,计数器加一
            curr = curr.next; // 移动到下一个节点
        }
        return count; // 返回计数器的值
    }

    // 清空链表
    public void clear() {
        head = null; // 将头节点置为null
    }

    // 获取指定位置上的节点
    private Node getNode(int index) {
        if (index < 0 || index >= size()) { // 检查索引的合法性
            throw new IndexOutOfBoundsException("Index out of range"); // 抛出越界异常
        }

        Node curr = head; // 从头节点开始遍历链表
        for (int i = 0; i < index; i++) { // 遍历到指定位置的节点
            curr = curr.next;
        }
        return curr; // 返回指定位置的节点
    }

    // 打印链表
    public void display() {
        Node curr = head; // 从头节点开始遍历链表
        while (curr != null) {
            System.out.print(curr.data + " "); // 打印当前节点的数据
            curr = curr.next; // 移动到下一个节点
        }
        System.out.println(); // 换行
    }

    public static void main(String[] args) {
        SingleLinkedList list = new SingleLinkedList();

        list.addFirst(1); // 在链表头部插入节点 1
        list.addLast(3);  // 在链表尾部插入节点 3
        list.addIndex(1, 2); // 在下标为 1 的位置插入节点 2

        list.display(); // 打印链表:1 2 3

        System.out.println("Contains 2: " + list.contains(2)); // 判断链表是否包含关键字 2,结果为 true

        list.remove(2); // 删除首次出现的关键字为 2 的节点
        list.display(); // 打印链表:1 3

        list.addLast(1);
        list.addLast(2);
        list.addLast(3);
        list.display(); // 打印链表:1 3 1 2 3

        list.removeAllKey(1); // 删除所有值为 1 的节点
        list.display(); // 打印链表:3 2 3

        System.out.println("Size: " + list.size()); // 获取链表长度,结果为 3

        list.clear(); // 清空链表
        list.display(); // 打印链表:空
    }
}

2.2 无头双向非循环链表

// 2、无头双向链表实现
public class MyLinkedList {

 //头插法
  public void addFirst(int data){ }
  
  //尾插法
  public void addLast(int data){}
  
  //任意位置插入,第一个数据节点为0号下标
  public void addIndex(int index,int data){}
  
  //查找是否包含关键字key是否在单链表当中
  public boolean contains(int key){}
  
  //删除第一次出现关键字为key的节点
  public void remove(int key){}
  
  //删除所有值为key的节点
  public void removeAllKey(int key){}
  
  //得到单链表的长度
  public int size(){}
  
  
  public void display(){}
  
  //清空
  public void clear(){}
}

下面是模拟实现后的代码:

// 2、无头双向链表实现
public class MyLinkedList {
    // 节点类
    private class Node {
        private int data; // 节点存储的数据
        private Node prev; // 前一个节点的引用
        private Node next; // 下一个节点的引用

        public Node(int data) {
            this.data = data; // 初始化节点的数据为传入的data
            this.prev = null; // 初始化前一个节点的引用为null
            this.next = null; // 初始化下一个节点的引用为null
        }
    }

    private Node head; // 头节点
    private Node tail; // 尾节点
    private int size; // 链表长度

    // 头插法
    public void addFirst(int data) {
        Node newNode = new Node(data); // 创建一个新节点,存储传入的数据
        if (head == null) { // 如果头节点为null,说明链表为空
            head = newNode; // 将头节点指向新节点
            tail = newNode; // 将尾节点指向新节点
        } else { // 链表不为空
            newNode.next = head; // 将新节点的下一个节点指向当前头节点
            head.prev = newNode; // 将当前头节点的前一个节点指向新节点
            head = newNode; // 将头节点指向新节点
        }
        size++; // 链表长度加1
    }

    // 尾插法
    public void addLast(int data) {
        Node newNode = new Node(data); // 创建一个新节点,存储传入的数据
        if (tail == null) { // 如果尾节点为null,说明链表为空
            head = newNode; // 将头节点指向新节点
            tail = newNode; // 将尾节点指向新节点
        } else { // 链表不为空
            tail.next = newNode; // 将当前尾节点的下一个节点指向新节点
            newNode.prev = tail; // 将新节点的前一个节点指向当前尾节点
            tail = newNode; // 将尾节点指向新节点
        }
        size++; // 链表长度加1
    }

    // 任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) {
        if (index < 0 || index > size) { // 如果索引小于0或者大于链表长度,抛出索引超出范围的异常
            throw new IndexOutOfBoundsException("Index out of bound");
        }
        if (index == 0) { // 如果要插入的位置是头节点前面,即插入为头节点
            addFirst(data); // 使用头插法
        } else if (index == size) { // 如果要插入的位置是尾节点后面,即插入为尾节点
            addLast(data); // 使用尾插法
        } else { // 在中间位置插入节点
            Node newNode = new Node(data); // 创建一个新节点,存储传入的数据
            Node currNode = goToIndex(index); // 找到要插入位置的前一个节点
            newNode.prev = currNode.prev; // 将新节点的前一个节点指向要插入位置的前一个节点
            newNode.next = currNode; // 将新节点的下一个节点指向要插入位置的节点
            currNode.prev.next = newNode; // 将要插入位置的前一个节点的下一个节点指向新节点
            currNode.prev = newNode; // 将要插入位置的节点的前一个节点指向新节点
            size++; // 链表长度加1
        }
    }

    // 查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        Node currNode = head; // 从头节点开始查找
        while (currNode != null) { // 当前节点不为null
            if (currNode.data == key) { // 如果当前节点的数据等于关键字
                return true; // 返回true
            }
            currNode = currNode.next; // 继续下一个节点
        }
        return false; // 没找到关键字,返回false
    }

    // 删除第一次出现关键字为key的节点
    public void remove(int key) {
        Node currNode = head; // 从头节点开始查找
        while (currNode != null) { // 当前节点不为null
            if (currNode.data == key) { // 如果当前节点的数据等于关键字
                if (currNode == head) { // 如果当前节点是头节点
                    head = currNode.next; // 将头节点指向当前节点的下一个节点
                    if (head != null) {
                        head.prev = null; // 如果头节点不为null,将新头节点的前一个节点设置为null
                    }
                    if (currNode == tail) { // 如果当前节点是尾节点
                        tail = null; // 将尾节点置为null
                    }
                } else if (currNode == tail) { // 如果当前节点是尾节点
                    tail = currNode.prev; // 将尾节点指向当前节点的前一个节点
                    tail.next = null; // 将新尾节点的下一个节点设置为null
                } else { // 在中间位置删除节点
                    currNode.prev.next = currNode.next; // 将要删除节点的前一个节点的下一个节点指向要删除节点的下一个节点
                    currNode.next.prev = currNode.prev; // 将要删除节点的下一个节点的前一个节点指向要删除节点的前一个节点
                }
                size--; // 链表长度减1
                return; // 删除完成,结束方法
            }
            currNode = currNode.next; // 继续下一个节点
        }
    }

    // 删除所有值为key的节点
    public void removeAllKey(int key) {
        Node currNode = head; // 从头节点开始查找
        while (currNode != null) { // 当前节点不为null
            if (currNode.data == key) { // 如果当前节点的数据等于关键字
                if (currNode == head) { // 如果当前节点是头节点
                    head = currNode.next; // 将头节点指向当前节点的下一个节点
                    if (head != null) {
                        head.prev = null; // 如果头节点不为null,将新头节点的前一个节点设置为null
                    }
                    if (currNode == tail) { // 如果当前节点是尾节点
                        tail = null; // 将尾节点置为null
                    }
                    currNode = head; // 将当前节点指向新的头节点
                } else if (currNode == tail) { // 如果当前节点是尾节点
                    tail = currNode.prev; // 将尾节点指向当前节点的前一个节点
                    tail.next = null; // 将新尾节点的下一个节点设置为null
                    currNode = null; // 将当前节点置为null,用于结束循环
                } else { // 在中间位置删除节点
                    currNode.prev.next = currNode.next; // 将要删除节点的前一个节点的下一个节点指向要删除节点的下一个节点
                    currNode.next.prev = currNode.prev; // 将要删除节点的下一个节点的前一个节点指向要删除节点的前一个节点
                    Node tempNode = currNode.next; // 保存需要删除的节点的下一个节点的引用
                    currNode.next = null; // 将要删除节点的下一个节点置为null
                    currNode.prev = null; // 将要删除节点的前一个节点置为null
                    currNode = tempNode; // 将当前节点指向保存的下一个节点
                }
                size--; // 链表长度减1
            } else {
                currNode = currNode.next; // 继续下一个节点
            }
        }
    }

    // 得到双向链表的长度
    public int size() {
        return size; // 返回链表的长度
    }

    // 辅助函数,用于定位到指定索引的节点
    private Node goToIndex(int index) {
        Node currNode = head; // 从头节点开始
        int count = 0; // 计数器,记录已经遍历的节点个数
        while (count < index) { // 当计数器小于索引时
            currNode = currNode.next; // 继续下一个节点
            count++; // 计数器加1
        }
        return currNode; // 返回找到的节点
    }

    // 清空双向链表
    public void clear() {
        head = null; // 将头节点置为null
        tail = null; // 将尾节点置为null
        size = 0; // 链表长度置为0
    }

    // 打印双向链表
    public void display() {
        Node currNode = head; // 从头节点开始
        while (currNode != null) { // 当前节点不为null
            System.out.print(currNode.data + " "); // 打印当前节点的数据
            currNode = currNode.next; // 继续下一个节点
        }
        System.out.println(); // 换行
    }

    public static void main(String[] args) {
        MyLinkedList list = new MyLinkedList(); // 创建一个新的双向链表对象

        list.addFirst(1); // 在链表头插入数据1
        list.addLast(2); // 在链表尾插入数据2
        list.addIndex(1, 3); // 在索引1处插入数据3
        list.display(); // 输出:1 3 2

        System.out.println(list.contains(2)); // 输出:true

        list.remove(3); // 删除数据3
        list.display(); // 输出:1 2

        list.removeAllKey(2); // 删除所有值为2的节点
        list.display(); // 输出:1

        list.clear(); // 清空链表
        System.out.println(list.size()); // 输出:0
    }
}

2.3LinkedList的遍历

当我们使用Java中的LinkedList类时,有几种方式可以进行遍历。我们可以使用foreach循环,也可以使用迭代器对链表进行正向和反向遍历。下面是代码示例:

首先,让我们创建一个LinkedList对象并向其添加一些元素

import java.util.LinkedList;

public class LinkedListTraversal {
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();

        linkedList.add("Apple");
        linkedList.add("Banana");
        linkedList.add("Cherry");
        linkedList.add("Durian");
    }
}
  1. 使用foreach循环遍历

使用foreach循环可以直接遍历LinkedList中的元素。在每次迭代中,我们可以通过一个变量来访问当前元素。下面是使用foreach循环遍历LinkedList的示例代码:

for (String fruit : linkedList) {
    System.out.println(fruit);
}

输出结果为:

Apple
Banana
Cherry
Durian
  1. 使用迭代器进行正向遍历

LinkedList实现了Iterable接口,因此我们可以使用迭代器在链表上进行正向遍历。通过调用iterator()方法,我们可以获取一个Iterator对象,然后使用hasNext()next()方法来迭代遍历链表中的元素。下面是使用迭代器进行正向遍历的示例代码:

Iterator<String> iterator = linkedList.iterator();
while (iterator.hasNext()) {
    String fruit = iterator.next();
    System.out.println(fruit);
}

输出结果为:

Apple
Banana
Cherry
Durian
  1. 使用迭代器进行反向遍历

LinkedList还提供了一个descendingIterator()方法,返回一个逆向迭代器,使我们可以反向遍历链表中的元素。通过调用hasNext()next()方法,我们可以从尾部开始向前迭代遍历LinkedList。下面是使用迭代器进行反向遍历的示例代码:

Iterator<String> descendingIterator = linkedList.descendingIterator();
while (descendingIterator.hasNext()) {
    String fruit = descendingIterator.next();
    System.out.println(fruit);
}

输出结果为:

Durian
Cherry
Banana
Apple

以上就是LinkedList遍历的三种方式!

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

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

相关文章

Java设计模式:Callback

介绍 回调&#xff08;Callback&#xff09;是一种设计模式&#xff0c;在这种模式中&#xff0c;一个可执行的代码被作为参数传递给其他代码&#xff0c;接收方的代码可以在适当的时候调用它。 在真实世界的例子中&#xff0c;当我们需要在任务完成时被通知时&#xff0c;我…

【Linux】从零开始学习Linux基本指令(一)

&#x1f6a9;纸上得来终觉浅&#xff0c; 绝知此事要躬行。 &#x1f31f;主页&#xff1a;June-Frost &#x1f680;专栏&#xff1a;Linux入门 &#x1f525;该文章主要了解Linux操作系统下的基本指令。 目录&#xff1a; ⌛️指令的理解⏳目录和文件的理解⏳一些常见指令✉…

C++入门篇11 模板进阶

一、非类型模板参数 模板参数分为类型形参和非类型形参 类型形参&#xff1a;出现在模板参数列表里&#xff0c;跟在class/typename之后的参数类型名称非类型参数&#xff1a;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中可将参数当作常量来使用 …

SRAM电路设计

RAM是随机存取存储器&#xff08;random access memory&#xff09;&#xff0c;是计算机内部存储器中的一种&#xff0c;也是其中最重要的&#xff0c;计算机和手机中一般把其叫做&#xff08;运行&#xff09;内存&#xff0c;它的速度要比硬盘快得多&#xff0c;所以用运行程…

设计师都应该知道的事:极简主义家具该怎么去用

这座房子有黑暗而沉重的特征&#xff0c;包括棕色和白色的马赛克浴室瓷砖&#xff0c;弯曲的锻铁壁灯和土黄色的威尼斯石膏墙。但由于房屋与他们的风格相去甚远&#xff0c;白色&#xff0c;干净和简约&#xff0c;接下来我们就着这个方向去帮助房主进行改造。 她解释说&#x…

uniapp 小程序实现图片宽度100%、高度自适应的效果

因为image组件默认是有宽度跟高度的&#xff0c;所以这个高度不怎么好写 通过load事件来控制图片的高度 话不多说&#xff0c;直接上代码&#xff0c; <image class"img" src"/static/image.png" :style"{ height: imgHeight px }"mode&q…

【linux】E45: ‘readonly‘ option is set (add ! to override)

vim 编辑文件保存时 E45:设置了“只读”选项&#xff08;添加&#xff01;以覆盖&#xff09; 输入&#xff1a; wq! 提示 "/etc/my.cnf" E212: Cant open file for writing 依然是没有权限&#xff1a; 解决一&#xff1a; 切换用户&#xff1a; su root 解…

Elastic Cloud v.s. Zilliz Cloud:性能大比拼

Elastic Cloud v.s. Zilliz Cloud:性能大比拼 Zilliz 经常会收到来自开发者和架构师的提问:“Zilliz Cloud 和 Elastic Cloud 比起来,谁进行向量处理能力比较强?” 诸如此类的问题很多,究其根本,大都是开发者/架构师在为语义相似性检索系统进行数据库选型时缺少决策依据有…

网络层:常见的面试题和答案

1、什么是IPv4和IPv6&#xff1f;它们有什么区别&#xff1f; 答&#xff1a;IPv4是32位的IP地址格式&#xff0c;而IPv6是128位的IP地址格式。IPv4地址空间有限&#xff0c;而IPv6地址空间更大&#xff0c;可以提供更多的地址。 2、说说 HTTP 和HTTPS 的区别&#xff1f; H…

【C++】适配器模式 - - stack/queue/deque

目录 一、适配器模式 1.1迭代器模式 1.2适配器模式 二、stack 2.1stack 的介绍和使用 2.2stack的模拟实现 三、queue 3.1queue的介绍和使用 3.2queue的模拟实现 四、deque&#xff08;不满足先进先出&#xff0c;和队列无关&#xff09; 4.1deque的原理介绍 4.2dequ…

基于matomo实现业务数据埋点采集上报

matomo是一款Google-analytics数据埋点采集上报的平替方案&#xff0c;可保护您的数据和客户的隐私&#xff1b;正如它官网的slogan: Google Analytics alternative that protects your data and your customers privacy; 该项目源码开源免费&#xff0c;支持私有化部署&#x…

零基础Linux_17(进程间通信)VSCode环境安装+进程间通信介绍+pipe管道mkfifo

目录 1. VSCode环境安装 1.1 使用VSCode 1.2 远程链接到Linux机器 1.3 VSCode调试 2. 进程间通讯介绍 2.1 进程间通讯的概念和意义 2.2 进程间通讯的策略和本质 3. 管道 3.1 管道介绍 3.2 匿名管道介绍 3.3 匿名管道示例代码 3.3.1 建立管道的pipe 3.3.2 匿名管道…

YOLOv5算法改进(10)— 如何去添加多层注意力机制(包括代码+添加步骤+网络结构图)

前言:Hello大家好,我是小哥谈。注意力机制是近年来深度学习领域内的研究热点,可以帮助模型更好地关注重要的特征,从而提高模型的性能。注意力机制可被应用于模型的不同层级,以便更好地捕捉图像中的细节和特征,这种模型在计算资源有限的情况下,可以实现更好的性能和效率。…

二十一、【文本工具组】

文章目录 横排文本工具字符选项卡段落文字 横排文本工具 需要注意的是一些不是免费的商业字体&#xff0c;一定不要拿去使用&#xff0c;否则后边很容易会受到法律索赔。 在制作海报等一些图形时&#xff0c;需要经常用到文本工具我们需要对文本进行变形&#xff0c;分段&…

Qt开发工程师成系统性长体系教程

QT跨平台开发工程师必备技术栈 基础原理-案例分析-项目实战&#xff0c;紧跟QT开发岗位技术需求. 一、Qt C 语言编程基础专栏 1.1 Qt C 语言编程基础 Visual Studio 2022安装C语言基础概述C指针与引用C类与对象(一)C类与对象(二)类的基它特性构造函数&析构函数&拷贝…

rhel8 nmcli学习

25.3.1 配置动态IP连接 25.3.1.1 配置IP 要使用 DHCP 分配网络时&#xff0c;可以使用动态IP配置添加网络配置文件&#xff0c;命令格式如下&#xff1a; # nmcli connection add type ethernet con-name connection-name ifname interface-name 例如创建名为net-test的动态…

虚拟机的发展史:从分时系统到容器化

一、前世 早期计算机的价格非常昂贵&#xff0c;一台计算机可能需要花费几十万甚至上百万美元。例如&#xff0c;ENIAC计算机&#xff0c;作为世界上第一台通用电子数字计算机&#xff0c;当时的造价约为48万美元。科学家或者工程师们需要计算机的能力&#xff0c;但是买不起整…

C. JoyboardCodeforces Round 902

C. Joyboard 样例1列表找规律&#xff1a; #include<iostream> #define int long long using namespace std; signed main() {int T;cin>>T;while(T--){int n,m,k;cin>>n>>m>>k;if(k1){cout<<1<<endl;}else if(k2){cout<<m…

vue2时间处理插件——dayjs

在vue时间处理上有很多的方法和实现&#xff0c;可以自己实现&#xff0c;但是效率不高&#xff0c;所以&#xff0c;在框架开发中我们一般不会手写&#xff0c;一般是使用集成的第三方插件来解决我们的问题&#xff0c;在vue3中大家一般都使用Moment.js来处理&#xff0c;所以…

Defects4j数据集安装及使用

一、Defects4j数据集安装 在Ubuntu系统上进行操作&#xff0c;具体的在&#xff1a;Defects4j数据集安装 二、Defects4j数据集的使用 1. 常用命令 Getting started ---------------- #### Setting up Defects4J 1. Clone Defects4J:- git clone https://github.com/rjust/d…