数据结构与算法(三)线性表

news2024/11/19 15:20:10

线性表定义

线性表(List):零个或多个数据元素的有限序列。

首先它是一个序列,其次,线性表强调是有限的。

image-20221225134203855

前驱元素:若A元素在B元素的前面,则称A为B的前驱元素

后继元素:若B元素在A元素的后面,则称B为A的后继元素

线性表的特征

数据元素之间具有一种“一对一”的逻辑关系。

  • 第一个数据元素没有前驱,这个数据元素被称为头结点;
  • 最后一个数据元素没有后继,这个数据元素被称为尾结点;
  • 除了第一个和最后一个数据元素外,其他数据元素有且仅有一个前驱和一个后继。

如果把线性表用数学语言来定义,则可以表示为(a1,...ai-1,ai,ai+1,...an)ai-1领先于ai,ai领先于ai+1,称ai-1ai的前驱元素,ai+1ai的后继元素。

image-20221225134348971

线性表的分类

线性表中数据存储的方式可以是顺序存储,也可以是链式存储,按照数据的存储方式不同,可以把线性表分为顺序表和链表。

顺序表

顺序表是在计算机内存中以数组的形式保存的线性表,线性表的顺序存储是指用一组地址连续的存储单元,依次存储线性表中的各个元素、使得线性表中再逻辑结构上相邻的数据元素存储在相邻的物理存储单元中,即通过数据元素物理存储的相邻关系来反映数据元素之间逻辑上的相邻关系。

image-20221225134539279

顺序表设计

类名SequenceList
构造方法SequenceList(int capacity):创建容量为capacitySequenceList对象
成员方法public void clear():空置线性表
publicboolean isEmpty():判断线性表是否为空,是返回true,否返回false
public int length():获取线性表中元素的个数
public T get(int i):读取并返回线性表中的第i个元素的值
public void insert(int i,E e):在线性表的第i个元素之前插入一个值为t的数据元素。
public void insert(E e):向线性表中添加一个元素t
public T remove(int i):删除并返回线性表中第i个数据元素。
public int indexOf(E e):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1。
成员变量private T[] elements:存储元素的数组
private int n:当前线性表的长度

顺序表代码实现

@SuppressWarnings("unchecked")
public class SequenceList<T> implements Iterable<T> {
    /**
     * 存储元素的数组
     */
    private T[] elements;

    /**
     * 记录当前顺序表中的元素个数
     */
    private int n;

    /**
     * 创建容量为capacity的SequenceList对象
     */
    public SequenceList(int capacity) {
        // 初始化数组
        this.elements = (T[]) new Object[capacity];
        // 初始化长度
        n = 0;
    }

    /**
     * 空置线性表
     */
    public void clear() {
        n = 0;
    }

    /**
     * 判断线性表是否为空,是返回true,否返回false
     */
    public boolean isEmpty() {
        return n == 0;
    }

    /**
     * 获取线性表中元素的个数
     */
    public int length() {
        return n;
    }

    /**
     * 读取并返回线性表中的第i个元素的值
     */
    public T get(int i) {
        if (i < 0 || i >= n) {
            throw new RuntimeException("当前元素不存在!");
        }
        return elements[i];
    }

    /**
     * 在线性表的第i个元素之前插入一个值为t的数据元素
     */
    public void insert(int i, E e) {
        if (n == elements.length) {
            throw new RuntimeException("当前表已满");
        }
        // 先把i索引处的元素及其后面的元素依次向后移动一位
        for (int j = n; j > i; j--) {
            elements[j] = elements[j - 1];
        }
        // 再把t元素放到i索引处
        elements[i] = t;
        n++;
    }

    /**
     * 向线性表中添加一个元素t
     */
    public void insert(E e) {
        if (i == elements.length) {
            throw new RuntimeException("当前表已满");
        }
        if (i < 0 || i > n) {
            throw new RuntimeException("插入的位置不合法");
        }
        elements[n++] = t;
    }

    /**
     * 删除并返回线性表中第i个数据元素
     */
    public T remove(int i) {
        if (i < 0 || i > n - 1) {
            throw new RuntimeException("当前要删除的元素不存在");
        }
        // 记录i索引处的值
        T current = elements[i];

        // 索引i后面元素依次向前移动一位
        for (int j = i; j < n - 1; j++) {
            elements[j] = elements[j + 1];
        }
        // 元素个数-1
        n--;
        return current;
    }

    /**
     * 返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1。
     */
    public int indexOf(E e) {
        if (t == null) {
            throw new RuntimeException("查找的元素不合法");
        }
        for (int i = 0; i < n; i++) {
            if (Objects.equals(elements[i], t)) {
                return i;
            }
        }
        return -1;
    }
}

顺序表的遍历

一般作为容器存储数据,都需要向外部提供遍历的方式,因此我们需要给顺序表提供遍历方式。

在Java中,遍历集合的方式一般都是用的是forEach循环,如果想让我们的SequenceList也能支持forEach循环,则需要做如下操作:

  1. SequenceList实现Iterable接口,重写iterator方法;
  2. SequenceList内部提供一个内部类SIterator,实现Iterator接口,重写hasNext方法和next方法;
public class SequenceList<T> implements Iterable<T> {
    // ...
    @Override
    public Iterator<T> iterator() {
        return new SIterator();
    }

    private class SIterator implements Iterator<T> {
        private int cursor;

        public SIterator() {
            this.cursor = 0;
        }

        @Override
        public boolean hasNext() {
            return cursor < n;
        }

        @Override
        public T next() {
            return elements[cursor++];
        }
    }
}

顺序表容量可变

在之前的实现中,当我们使用SequenceList时,先new SequenceList(5)创建一个对象,创建对象时就需要指定容器的大小,初始化指定大小的数组来存储元素,当我们插入元素时,如果已经插入了5个元素,还要继续插入数据,则会报错,就不能插入了。这种设计不符合容器的设计理念,因此我们在设计顺序表时,应该考虑它的容量的伸缩性。
考虑容器的容量伸缩性,其实就是改变存储数据元素的数组的大小,那我们需要考虑什么时候需要改变数组的大小?

添加元素时扩容

添加元素时,应该检查当前数组的大小是否能容纳新的元素,如果不能容纳,则需要创建新的容量更大的数组,我们这里创建一个是原数组两倍容量的新数组存储元素。

image-20221225140942252

移除元素时缩容

移除元素时,应该检查当前数组的大小是否太大,比如正在用100个容量的数组存储10个元素,这样就会造成内存空间的浪费,应该创建一个容量更小的数组存储元素。如果我们发现数据元素的数量不足数组容量的1/4,则创建一个是原数组容量的1/2的新数组存储元素。

image-20221225141009891

@SuppressWarnings("unchecked")
public class SequenceList<T> {

    /**
     * 向线性表中添加一个元素t
     */
    public void insert(E e) {
        // 如果当前容量已满,那么扩容2倍
        if (n == elements.length) {
            resize(2 * elements.length);
        }
        // ...
    }

    /**
     * 删除并返回线性表中第i个数据元素
     */
    public T remove(int i) {
        // ...

        // 如果当前元素数量小于容量的1/4,那么缩容为1/2
        if (n < elements.length / 4) {
            resize(elements.length / 2);
        }
        return current;
    }

    /**
     * 根据newSize,重置elements的大小
     */
    public void resize(int newSize) {
        // 定义一个临时数组,指向原数组
        T[] temp = elements;
        // 创建新数组
        elements = (T[]) new Object[newSize];
        System.arraycopy(temp, 0, elements, 0, temp.length);
    }
}

扩缩容的原理很简单,是创建一个具有指定新容量的新数组,然后把原来的数据拷贝到新数组。

顺序表的时间复杂度

  • get(i):不论数据元素量n有多大,只需要一次elements[i]就可以获取到对应的元素,所以时间复杂度为O(1)。我们通常把具有这一特点的存储结构称为随机存取结构。
  • insert(int i,E e):每一次插入,都需要把i位置后面的元素移动一次,随着元素数量N的增大,移动的元素也越多,时间复杂为O(n);
  • remove(int i):每一次删除,都需要把i位置后面的元素移动一次,随着数据量N的增大,移动的元素也越多,时间复杂度为O(n);

由于顺序表的底层由数组实现,数组的长度是固定的,所以在操作的过程中涉及到了容器扩容操作。这样会导致顺序表在使用过程中的时间复杂度不是线性的,在某些需要扩容的结点处,耗时会突增,尤其是元素越多,这个问题越明显。

顺序表的优缺点

优点

  • 无需为表示表中元素之间的逻辑关系而增加额外的存储空间
  • 可以快速地存取表中任意位置的元素

缺点

  • 插入和删除操作需要移动大量元素
  • 当线性表长度变化较大时,难以确定存储空间的容量
  • 造成存储空间的“碎片”

链表

虽然顺序表的查询很快,时间复杂度为O(1),但是增删的效率是比较低的,因为每一次增删操作都伴随着大量的数据元素移动。这个问题有没有解决方案呢?有,我们可以使用另外一种存储结构实现线性表,链式存储结构。

链表是一种物理存储单元上非连续、非顺序的存储结构,其物理结构不能只管的表示数据元素的逻辑顺序,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列的结点(链表中的每一个元素称为结点)组成,结点可以在运行时动态生成。

image-20221225142040985

链表节点设计

类名Node
构造方法Node(E e, Node next):创建Node对象
成员变量T item:存储数据
Node next:指向下一个结点
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Node<E> {

    /**
     * 存储元素
     */
    private E item;

    /**
     * 指向下一个节点
     */
    private Node<E> next;
}

单向链表

单向链表是链表的一种,它由多个结点组成,每个结点都由一个数据域和一个指针域组成,数据域用来存储数据,指针域用来指向其后继结点。链表的头结点的数据域不存储数据,指针域指向第一个真正存储数据的结点。

image-20221225142441247

单向链表设计

类名LinkList
构造方法LinkList():创建LinkList对象
成员方法public void clear():空置线性表
public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
public int length():获取线性表中元素的个数
public T get(int i):读取并返回线性表中的第i个元素的值
public void insert(E e):往线性表中添加一个元素;
public void insert(int i, E e):在线性表的第i个元素之前插入一个值为t的数据元素。
public T remove(int i):删除并返回线性表中第i个数据元素。
public int indexOf(E e):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1。
成员变量private Node head:记录首结点
private int n:记录链表的长度

单向链表代码实现

public class LinkList<E> implements Iterable<E> {

    /**
     * 记录首节点
     */
    private final Node<E> head;
    /**
     * 记录链表的长度
     */
    private int n;

    public LinkList() {
        // 初始化头结点
        head = new Node<>(null, null);
        // 初始化元素个数
        n = 0;
    }

    /**
     * 空置线性表
     */
    public void clear() {
        head.next = null;
        n = 0;
    }

    /**
     * 判断线性表是否为空,是返回true,否返回false
     */
    public boolean isEmpty() {
        return n == 0;
    }

    /**
     * 获取线性表中元素的个数
     */
    public int length() {
        return n;
    }

    /**
     * 读取并返回线性表中的第i个元素的值
     */
    public E get(int i) {
        if (i < 0 || i >= n) {
            throw new RuntimeException("位置不合法!");
        }
        // 通过循环,从头结点开始往后找,依次找i次,就可以找到对应的元素
        Node<E> n = head.next;
        for (int index = 0; index < i; index++) {
            n = n.next;
        }
        return n.item;
    }

    /**
     * 往线性表中添加一个元素
     */
    public void insert(E e) {
        // 找到当前最后一个节点
        Node<E> n = head; // 头节点不存储数据,所以不能算作第一个元素
        while (n.next != null) {
            n = n.next;
        }
        // 创建新节点,保存元素t
        // 让当前最后一个元素指向新节点
        n.next = new Node<>(e, null);
        // 元素个数+1
        this.n++;
    }

    /**
     * 在线性表的第i个元素之前插入一个值为t的数据元素
     */
    public void insert(int i, E e) {
        if (i < 0 || i >= n) {
            throw new RuntimeException("位置不合法!");
        }

        // 找到i位置前一个节点
        Node<E> pre = head; // 头节点不存储数据,所以不能算作第一个元素
        for (int index = 0; index < i; index++) {
            pre = pre.next;
        }

        // 找到i位置的节点
        Node<E> current = pre.next;

        // 创建新节点,并且新节点需要指向原来i位置的节点
        // 原来i位置的前一个节点指向新节点
        pre.next = new Node<>(e, current);

        // 元素个数+1
        n++;
    }

    /**
     * 删除并返回线性表中第i个数据元素
     */
    public E remove(int i) {
        if (i < 0 || i >= n) {
            throw new RuntimeException("位置不合法");
        }

        // 找到i位置前一个节点
        Node<E> pre = head;
        for (int index = 0; index < i; index++) {
            pre = pre.next;
        }

        // 找到i位置的节点
        Node<E> current = pre.next;

        // 找到i位置的下一个节点

        // 前一个节点指向下一个节点
        pre.next = current.next;

        // 元素个数 - 1
        n--;
        return current.item;
    }

    /**
     * 返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
     */
    public int indexOf(E e) {
        // 从头结点开始,依次找到每一个节点,取出item,和t比较,如果相同,就返回
        Node<E> n = head;
        for (int i = 0; n.next != null; i++) {
            n = n.next;
            if (n.item.equals(e)) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public Iterator<E> iterator() {
        return new LIterator();
    }

    private static class Node<T> {

        // 存储元素
        T item;
        // 指向下一个节点
        Node<T> next;

        Node(T item, Node<T> next) {
            this.item = item;
            this.next = next;
        }
    }

}

循环链表

对于单向链表,由于每个结点只存储了向后的指针,到了尾标志就停止了向后链的操作,这样,当中某一结点就无法找到它的前驱结点了。

比如,你是一业务员,家在上海。需要经常出差,行程就是上海到北京一路上的城市,找客户谈生意或分公司办理业务。你从上海出发,乘火车路经多个城市停留后,再乘飞机返回上海,以后,每隔一段时间,你基本还要按照这样的行程开展业务,如图所示:

image-20221225170957533

有一次,你先到南京开会,接下来要对以上的城市走一遍,此时有人对你说,不行,你得从上海开始,因为上海是第一站。你会对这人说什么?神经病。哪有这么傻的,直接回上海根本没有必要,你可以从南京开始,下一站蚌埠,直到北京,之后再考虑走完上海及苏南的几个城市。

显然这表示你是从当中一结点开始遍历整个链表,这都是原来的单链表结构解决不了的问题。事实上,把北京和上海之间连起来,形成一个环就解决了前面所面临的困难。这就是循环链表。

从刚才的例子,可以总结出,循环链表解决了一个很麻烦的问题。如何从当中一个结点出发,访问到链表的全部结点

在单向链表中,最后一个节点的指针为NULL,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可。

image-20221225171126533

如果链表中没有元素,那么头结点也需要指向自己,从而形成环

image-20221225171812749

循环链表代码实现

代码实现和单向链表基本一致,只需要在插入尾结点的时候,将尾结点指向头结点即可。

public void insert(E e) {
    // 找到当前最后一个节点
    // 头节点不存储数据,所以不能算作第一个元素
    var n = head;
    while (n.next != null) {
        n = n.next;
    }
    // 创建新节点,保存元素t,让当前最后一个元素指向新节点
    // 循环链表,最后一个元素指向头结点
    n.next = new Node<>(e, head);
    // 元素个数+1
    this.n++;
}

同时,在构造和清空链表时,让头结点指向自己

public CycleLinkList() {
    // 初始化头结点
    head = new Node<>(null, null);
    head.next = head;
    // 初始化元素个数
    n = 0;
}

/**
 * 清空线性表
 */
public void clear() {
    head.next = head;
    n = 0;
}

双向链表

继续刚才的例子,你平时都是从上海一路停留到北京的,可是这一次,你得先到北京开会,谁叫北京是首都呢,会就是多。开完会后,你需要例行公事,走访各个城市,此时你怎么办?

image-20221225194635114

有人又出主意了,你可以先飞回上海,一路再乘火车走遍这几个城市,到了北京后,你再飞回上海。你会感慨,人生中为什么总会有这样出馊主意的人存在呢?真要气死人才行。哪来这么麻烦,我一路从北京坐火车或汽车回去不就完了吗。

image-20221225194724730

我们的单链表,总是从头到尾找结点,难道就不可以正反遍历都可以吗?当然可以,只不过需要加点东西而已。

双向链表:在单链表的每个结点中,再设置一个指向其前驱结点的指针域。所以在双向链表中的结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。

双向链表结点设计

类名Node
构造方法Node(E e, Node pre, Node next):创建Node对象
成员变量E item:存储数据
Node next:指向下一个结点
Node pre:指向上一个结点
@AllArgsConstructor
private static class Node<E> {
    // 存储元素
    E item;
    // 指向上一个节点
    Node<E> pre;
    // 指向下一个节点
    Node<E> next;
}

双向链表设计

类名DoubleLinkList
构造方法DoubleLinkList():创建DoubleLinkList对象
成员方法public void clear():空置线性表
public boolean isEmpty():判断线性表是否为空,是返回true,否返回false
public int length():获取线性表中元素的个数
public T get(int i):读取并返回线性表中的第i个元素的值
public void insert(E e):往线性表中添加一个元素;
public void insert(int i, E e):在线性表的第i个元素之前插入一个值为t的数据元素。
public T remove(int i):删除并返回线性表中第i个数据元素。
public int indexOf(E e):返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1。
public T getFirst():获取第一个元素
public T getLast():获取最后一个元素
成员变量private Node first:记录首结点
private Node last:记录尾结点
private int n:记录链表的长度

双向链表代码实现

public class DoubleLinkList<E> implements Iterable<E> {

    /**
     * 记录首节点
     */
    private final Node<E> head;
    /**
     * 记录尾节点
     */
    private Node<E> last;
    /**
     * 记录链表的长度
     */
    private int n;

    public DoubleLinkList() {
        // 初始化头结点
        head = new Node<>(null, null, null);
        // 初始化尾节点
        last = null;
        // 初始化元素个数
        n = 0;
    }

    /**
     * 空置线性表
     */
    public void clear() {
        head.next = null;
        last = null;
        n = 0;
    }

    /**
     * 判断线性表是否为空,是返回true,否返回false
     */
    public boolean isEmpty() {
        return n == 0;
    }

    /**
     * 获取线性表中元素的个数
     */
    public int length() {
        return n;
    }

    /**
     * 获取头结点
     */
    public E getFirst() {
        if (isEmpty()) {
            return null;
        }
        return head.next.item;
    }

    /**
     * 获取尾节点
     */
    public E getLast() {
        if (isEmpty()) {
            return null;
        }
        return last.item;
    }

    /**
     * 读取并返回线性表中的第i个元素的值
     */
    public E get(int i) {
        if (i < 0 || i >= n) {
            throw new RuntimeException("位置不合法!");
        }
        // 通过循环,从头结点开始往后找,依次找i次,就可以找到对应的元素
        Node<E> n = head.next;
        for (int index = 0; index < i; index++) {
            n = n.next;
        }
        return n.item;
    }

    /**
     * 往线性表中添加一个元素
     */
    public void insert(E e) {
        if (isEmpty()) {
            // 如果链表为空
            // 创建新的节点
            Node<E> newNode = new Node<>(e, head, null);
            // 让新节点成为尾节点
            last = newNode;
            // 让头结点指向尾节点
            head.next = last;
        } else {
            // 如果链表不为空
            Node<E> tempLast = last;
            // 创建新的节点
            Node<E> newNode = new Node<>(e, tempLast, null);
            // 当前的尾节点指向新节点
            tempLast.next = newNode;
            // 让新节点成为尾节点
            last = newNode;
        }

        // 元素个数+1
        n++;
    }

    /**
     * 在线性表的第i个元素之前插入一个值为t的数据元素
     */
    public void insert(int i, E e) {
        if (i < 0 || i >= n) {
            throw new RuntimeException("位置不合法!");
        }

        // 找到i位置的前一个节点
        Node<E> pre = head;
        for (int index = 0; index < i; index++) {
            pre = pre.next;
        }
        // 找到i位置的节点
        Node<E> current = pre.next;
        // 创建新节点
        Node<E> newNode = new Node<>(e, pre, current);
        // 让i位置的前一个节点指向新节点
        pre.next = newNode;
        // 让i位置的前一个节点变为新节点
        current.pre = newNode;
        // 元素个数+1
        n++;
    }

    /**
     * 删除并返回线性表中第i个数据元素
     */
    public E remove(int i) {
        if (i < 0 || i >= n) {
            throw new RuntimeException("位置不合法");
        }

        // 找到i位置前一个节点
        Node<E> pre = head;
        for (int index = 0; index < i; index++) {
            pre = pre.next;
        }

        // 找到i位置的节点
        Node<E> current = pre.next;

        // 找到i位置的下一个节点
        Node<E> next = current.next;

        // i位置的前一个节点的下一个节点指向i位置的下一个节点
        pre.next = next;
        // i位置的下一个节点的前一个节点指向i位置的前一个节点
        next.pre = pre;

        // 元素个数 - 1
        n--;
        return current.item;
    }

    /**
     * 返回线性表中首次出现的指定的数据元素的位序号,若不存在,则返回-1
     */
    public int indexOf(E e) {
        // 从头结点开始,依次找到每一个节点,取出item,和t比较,如果相同,就返回
        Node<E> n = head;
        for (int i = 0; n.next != null; i++) {
            n = n.next;
            if (n.item.equals(e)) {
                return i;
            }
        }
        return -1;
    }

    @Override
    public Iterator<E> iterator() {
        return new LIterator();
    }

    @AllArgsConstructor
    private static class Node<E> {
        // 存储元素
        E item;
        // 指向上一个节点
        Node<E> pre;
        // 指向下一个节点
        Node<E> next;
    }

    private class LIterator implements Iterator<E> {
        private Node<E> n = head;

        @Override
        public boolean hasNext() {
            return n.next != null;
        }

        @Override
        public E next() {
            n = n.next;
            return n.item;
        }
    }

}

链表的时间复杂度

get(int i):每一次查询,都需要从链表的头部开始,依次向后查找,随着数据元素N的增多,比较的元素越多,时间复杂度为O(n)。
insert(int i, E e):每一次插入,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)。
remove(int i):每一次移除,需要先找到i位置的前一个元素,然后完成插入操作,随着数据元素N的增多,查找的元素越多,时间复杂度为O(n)
相比较顺序表,链表插入和删除的时间复杂度虽然一样,但仍然有很大的优势,因为链表的物理地址是不连续的,它不需要预先指定存储空间大小,并且在存储过程中不涉及到扩容等操作,同时它并没有涉及的元素的交换。
相比较顺序表,链表的查询操作性能会比较低。因此,如果我们的程序中查询操作比较多,建议使用顺序表,增删操作比较多,建议使用链表

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

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

相关文章

【代码随想录day23】不同路径

题目 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&#xff1f; 示…

明茨伯格的人际关系角色理论

明茨伯格的人际关系角色理论是由社会心理学家明茨伯格&#xff08;William Schutz&#xff09;在20世纪50年代提出的一种关于人际关系的理论。该理论主要探讨了人际关系中的三个角色&#xff1a;包容性、控制性和亲密性。这些角色代表了人们在互动中所表现出的行为和需求。下面…

ClickHouse进阶(六):副本与分片-2-Distributed引擎

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术,IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &#x1f4cc;订阅…

Qt各个版本下载及安装教程(离线和非离线安装)

Qt各个版本下载链接&#xff1a; Index of /archive/qthttps://download.qt.io/archive/qt/ 离线安装 &#xff0c;离线安装很无脑&#xff0c;下一步下一步就可以。 我离线下载 半个小时把2G的exe下载下来了

使用boost::geometry::union_ 合并边界(内、外)- 方案一

使用boost::geometry::union_ 合并边界&#xff08;内、外&#xff09;&#xff1a;方案一 结合 boost::geometry::read_wkt() 函数 #include <iostream> #include <vector>#include <boost/geometry.hpp> #include <boost/geometry/geometries/point_x…

linux C编程 获取系统时间

1.clock_gettime #include<time.h> int clock_gettime(clockid_t clk_id,struct timespec *tp); struct timespec {time_t tv_sec; /* 秒*/long tv_nsec; /* 纳秒*/ }clk_id : CLOCK_BOOTTIME&#xff0c;以系统启动时间为时间原点的时间体系&#xff0c;不受其它因素的…

PowerQuery动态加载M公式

Power Query 是Excel中的强大数据处理与转换工具&#xff0c;如果需要“动态”处理数据&#xff0c;大家第一时间想到的是可以使用VBA&#xff0c;利用代码创建M公式&#xff0c;进而创建PQ查询&#xff0c;但是复杂的M公式可能有很多行&#xff0c; 使用VBA处理起来并不是很方…

只考一门数据结构!安徽工程大学计算机考研

安徽工程大学 考研难度&#xff08;☆&#xff09; 内容&#xff1a;23考情概况&#xff08;拟录取和复试分析&#xff09;、院校概况、23专业目录、23复试详情、各专业考情分析、各科目考情分析。 正文992字&#xff0c;预计阅读&#xff1a;3分钟 2023考情概况 安徽工程大…

程序员自由创业周记#8:怎么设计

软件的样子 就像建造房子&#xff0c;工人施工需要照着图纸&#xff0c;没有图纸直接上手施工倒是也可以&#xff0c;只是房子的质量和样子都不敢恭维。程序员在一定意义上与建筑工人很像&#xff0c;只不过他们码砖&#xff0c;我们码码。软件开发之前也需要提前设计好界面&a…

本地开机启动jar

1&#xff1a;首先有个可运行的jar包 本地以ruiyi代码为例打包 2&#xff1a;编写bat命令---命名为.bat即可 echo off java -jar D:\everyDay\test\RuoYi\target\RuoYi.jar 3&#xff1a;设置为开机自启动启动 快捷键winr----输入shell:startup---打开启动文档夹 把bat文件复…

交换空间和虚拟内存的区别

分析&回答 交换空间和虚拟内存的区别在于使用的系统不一样,产生的技术手段不一样 交换空间 Linux 中的交换空间&#xff08;Swap space&#xff09;在物理内存&#xff08;RAM&#xff09;被充满时被使用。如果系统需要更多的内存资源&#xff0c;而物理内存已经充满&am…

UG\NX CAM二次开发 插入工序 UF_OPER_create

文章作者:代工 来源网站:NX CAM二次开发专栏 简介: UG\NX CAM二次开发 插入工序 UF_OPER_create 效果: 代码: void MyClass::do_it() {tag_t setup_tag=NULL_TAG;UF_SETUP_ask_setup(&setup_tag);if (setup_tag==NULL_TAG){uc1601("请先初始化加工环境…

linux操作系统中环境变量详解

目录 1.环境变量的基本概念 2.查看环境变量 3.PATH 4.root和普通用户的HOME 5.和环境变量相关的命令 6.环境变量的组织方式 6.1通过代码获取环境变量 6.2通过第三方变量environ获取 6.3通过系统调用获取环境变量 6.4通过系统调用设置环境变量 1.环境变量的基本概念 …

R语言图形绘制

&#xff08;1&#xff09;条形图 > barplot(c(1,2,4,2,6,4,3,5)) > barplot(c(1,2,4,2,6,4,3,5),horiz TRUE) #besideTRUE 表示将多个组别的图形并排显示&#xff0c;使它们在水平方向上对齐 #而当 besideFALSE&#xff08;默认值&#xff09;时&#xff0c;多个组别的…

达梦8 在CentOS 系统下静默安装

确认系统参数 [rootlocalhost ~]# ulimit -a core file size (blocks, -c) unlimited data seg size (kbytes, -d) unlimited【1048576(即 1GB)以上或 unlimited】 scheduling priority (-e) 0 file size (blocks, -f) unlimite…

IP地址、网关、网络/主机号、子网掩码关系

一、IP地址 IP地址组成 IP地址分为两个部分&#xff1a;网络号和主机号 &#xff08;1&#xff09;网络号:标识网段&#xff0c;保证相互连接的两个网段具有不同的标识。 &#xff08;2&#xff09;主机号:标识主机&#xff0c;同一网段内&#xff0c;主机之间具有相同的网…

程序员自由创业周记#9:最开心的事

人一天中最开心的时刻是什么时候&#xff1f;莫过于下班的时候&#xff1b;一天中最开心的事是什么&#xff1f;莫过于下班后干的任何事&#xff1b; 这么重要的时刻&#xff0c;能不能有一款产品只要手机在手就能知道还有多久下班&#xff0c;不论是手机在锁屏界面&#xff0c…

CSS学习笔记04

CSS笔记04 浮动 标准文档流 标准文档流就是浏览器按照各种元素标签排版布局中默认的状态。浏览器在渲染代码的时候是从左往右、从上到下开始渲染&#xff0c;元素也是从左往右、从上往下的流式排列。也就是没有被其他排版浮动和定位相关的 CSS 属性干扰的就叫标准文档流。标…

春秋云镜 CVE-2018-19422

春秋云镜 CVE-2018-19422 Subrion CMS 4.2.1 存在文件上传漏洞 靶标介绍 Subrion CMS 4.2.1 存在文件上传漏洞。CVE-2021-41947同一套cms。 启动场景 漏洞利用 admin/admin登陆后台管理界面 执行SQL命令&#xff0c;获取flag select load_file(/flag); 得到flag flag{174…

文心一言初体验,和ChatGPT语言理解能力比较

文章目录 第一个考验&#xff0c;语义理解第二个考验&#xff0c;历史问题的回答推荐阅读 百度旗下AI大模型文心一言宣布向全社会全面开放,所有用户都可以体验这款AI大模型了。要比较这两个语言模型&#xff0c;我们先设计好题目。 第一个考验&#xff0c;语义理解 题目1&…