彻底理解链表(LinkedList)结构

news2024/11/5 22:40:31

目录

  • 比较
  • 操作
  • 结构封装
  • 单向链表
    • 实现
    • 面试题
  • 循环链表
    • 实现
  • 双向链表
    • 实现

链表(Linked List)是一种线性数据结构,由一组节点(Node)组成,每个节点包含两个部分:数据域(存储数据)和指针域(指向下一个节点的地址)。与数组不同,链表中的元素在内存中不是连续存储的,使用指针进行连接

  • 链表类似于火车:有一个火车头,火车头会连接一个节点,节点上有乘客(类似于数据),并且这个节点会连接下一个节点,以此类推
    在这里插入图片描述

  • 实现栈和队列:链表结构非常适合实现这些数据结构。

  • LRU缓存:双向链表和哈希表结合实现。

  • 操作系统进程管理:使用链表管理进程调度队列。

  • 图和树结构:使用链表作为底层存储

比较

链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同

  • 数组:

    • 数组的创建通常需要申请一段连续的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的)

    • 当前数组不能满足容量需求时,需要扩容。 (一般情况下是申请一个更大的数组,比如2倍,然后将原数组中的元素复制过去)

    • 数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移

  • 链表:

    • 链表中的元素在内存中不必是连续的空间,可以充分利用计算机的内存,实现灵活的内存动态管理

    • 链表不必在创建时就确定大小,并且大小可以无限的延伸下去

    • 链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多

    • 链表访问任何一个位置的元素时,都需要从头开始访问。(无法跳过第一个元素访问任何一个元素)

    • 链表无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的元素

  • 时间复杂度对比

    • 在实际开发中,选择使用数组还是链表 需要根据具体应用场景来决定

    • 如果数据量不大,且需要频繁随机 访问元素,使用数组可能会更好

    • 如果数据量大,或者需要频繁插入 和删除元素,使用链表可能会更好

      在这里插入图片描述

操作

  • append(element):向链表尾部添加一个新的项

  • travers():为了可以方便的看到链表上的每一个元素,我们实现一个遍历链表每一个元素的方法

  • insert(position,element):向链表的特定位置插入一个新的项

  • get(position):获取对应位置的元素

  • indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返-1

  • update(position,element):修改某个位置的元素

  • removeAt(position):从链表的特定位置移除一项

  • remove(element):从链表中移除一项

  • peek():头的值

  • isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false

  • size():链表的长度

结构封装

  • 封装一个Node类,用于封装每一个节点上的信息(包括值和指向下一个节点的引用),它是一个泛型类

  • 封装一个LinkedList类,用于表示我们的链表结构和操作

  • 链表中我们保存三个属性,一个是链表的长度,一个是链表中第一个节点,这里也加最后一个节点,方便实现循环和双向链表

class Node<T> {
  value: T;
  next: Node<T>;
  constructor(value: T) {
    this.value = value;
  }
}


export interface ILinkedList<T> {
  append(value: T): void;
  traverse(): void;
  insert(value: T, position: number): boolean;
  removeAt(position: number): T | null;
  get(position: number): T | null;
  update(value: T, position: number): boolean;
  indexOf(value: T): number;
  remove(value: T): T | null;
  isEmpty(): boolean;
  size(): number
}

class LinkedList<T> implements ILinkedList<T> {
  head: Node<T> | null = null;
  tail: Node<T> | null = null;
  length: number = 0;

  append(value: T): void {
    throw new Error("Method not implemented.");
  }
  traverse(): void {
    throw new Error("Method not implemented.");
  }
  insert(value: T, position: number): boolean {
    throw new Error("Method not implemented.");
  }
  removeAt(position: number): T | null {
    throw new Error("Method not implemented.");
  }
  get(position: number): T | null {
    throw new Error("Method not implemented.");
  }
  update(value: T, position: number): boolean {
    throw new Error("Method not implemented.");
  }
  indexOf(value: T): number {
    throw new Error("Method not implemented.");
  }
  remove(value: T): T | null {
    throw new Error("Method not implemented.");
  }
  peek(value: T): T | undefined {
    throw new Error("Method not implemented.");
  }
  isEmpty(): boolean {
    throw new Error("Method not implemented.");
  }
  size(): number {
    throw new Error("Method not implemented.");
  }
}

const linked = new LinkedList<string>();
console.log(linked.head); // null

单向链表

在这里插入图片描述

实现

在下面实现各种方法时,我们会定义变量 previous 来保存前一个节点和 current 保存当前节点

  1. 各种方法实现都是通过操作变量来达到操作链表

  2. 这是因为变量实际上是链表中节点的引用,而不是节点的副本

  3. 链表的节点是对象,变量实际上指向的是链表中某个节点的内存地址(引用)

  4. 因此当我们修改变量时也会影响链表中的节点,这种机制使得我们能够轻松操作链表中的节点

  • 部分方法图解如下

    • append(element) 向链表表尾部追加数据

      链表为空,直接赋值为head

      链表不为空,需要向其他节点后面追加节点

      在这里插入图片描述

    • insert(position,element)

      添加到第一个位置,表示新添加的节点是头,需要将原来的头节点作为新节点的nexthead指向新节点

      添加到其他位置,需要先找到这个节点位置,通过循环向下找,并在这个过程中保存上一个节点和下一个节点,找到正确的位置后,将新节点的next指向下一个节点,将上一个节点的 next 指向新的节点(步骤颠倒后续链表之间的连接就会断掉)

      在这里插入图片描述

    • removeAt(position):从链表的特定位置移除一项

      移除第一项时,直接head指向第二项信息,第一项信息没有引用指向后面会被回收掉

      移除其他项的信息时,通过循环,找到正确的位置,将上一项的next指向current 项的next
      在这里插入图片描述

  • 完整代码如下: 抽取共同方法

    export class Node<T> {
      value: T;
      next: Node<T> | null = null;
      constructor(value: T) {
        this.value = value;
      }
    }
    
    export interface ILinkedList<T> {
      append(value: T): void;
      traverse(): void;
      insert(value: T, position: number): boolean;
      removeAt(position: number): T | null;
      get(positon: number): T | null;
      update(value: T, position: number): boolean;
      indexOf(value: T): number;
      remove(value: T): T | null;
      peek(value: T): T | undefined;
      isEmpty(): boolean;
      size(): number;
    }
    
    export class LinkedList<T> implements ILinkedList<T> {
      // 使用protected也是为了让其子类继承时使用
      protected head: Node<T> | null = null;
      protected tail: Node<T> | null = null;
      protected length: number = 0;
    
      protected getNode(position: number): {
        previous: Node<T> | null;
        current: Node<T> | null;
      } {
        let index = 0;
        let previous: Node<T> | null = null;
        let current = this.head;
    
        while (index++ < position && current) {
          previous = current;
          current = current.next;
        }
        return { current, previous };
      }
    
      private isTail(node: Node<T>) {
        return this.tail === node;
      }
    
      /* 向链表表尾部追加数据 */
      append(value: T): void {
        const newNode = new Node(value);
        // 链表为空,直接赋值为head
        if (!this.head) {
          this.head = newNode;
        } else {
          // 链表不为空,循环找到尾部节点,让其next指向新节点完成追加
          // let current = this.head;
          // while (current.next) {
          //   current = current.next;
          // }
          // current.next = newNode;
          this.tail!.next = newNode;
        }
        this.tail = newNode;
        this.length++;
      }
    
      /* 链表的遍历方法 */
      traverse(): void {
        let values: T[] = [];
    
        let current = this.head;
        while (current) {
          values.push(current.value);
          current = this.isTail(current) ? null : current.next; // 考虑循环链表的情况
        }
        if (this.head && this.tail!.next === this.head) {
          // 循环链表时
          values.push(this.head.value);
        }
        console.log(this.length, values.join(" -> "));
      }
    
      /* 向链表的特定位置插入一个新的项 */
      insert(value: T, position: number): boolean {
        // 1.越界的判断
        if (position < 0 && position > this.length) return false;
    
        // 2.根据value创建新的节点
        const newNode = new Node(value);
        let { previous, current } = this.getNode(position);
    
        // 头部插入
        if (position === 0) {
          newNode.next = this.head;
          this.head = newNode;
        } else {
          // 中尾部插入
          newNode.next = current;
          previous!.next = newNode;
    
          if (position === this.length) {
            // 尾部插入tail为新节点
            this.tail = newNode;
          }
        }
        this.length++;
        return true;
      }
      removeAt(position: number): T | null {
        // 1.越界的判断
        if (position < 0 || position >= this.length) return null;
    
        let { current, previous } = this.getNode(position);
        if (position === 0) {
          this.head = current?.next ?? null;
    
          if (this.length === 1) {
            this.tail = null;
          }
        } else {
          previous!.next = current?.next ?? null;
          if (current === this.tail) {
            // 尾部删除tail为前一个节点
            this.tail = previous;
          }
        }
    
        this.length--;
        return current?.value ?? null;
      }
    
      // 获取方法
      get(position: number): T | null {
        // 越界问题
        if (position < 0 || position >= this.length) return null;
    
        let { current } = this.getNode(position);
        return current?.value ?? null;
      }
    
      // 更新方法
      update(value: T, position: number): boolean {
        if (position < 0 || position >= this.length) return false;
    
        // 获取对应位置的节点, 直接更新即可
        let { current } = this.getNode(position);
        current!.value = value;
        return true;
      }
    
      // 根据值, 获取对应位置的索引
      indexOf(value: T): number {
        let index = 0;
        let current = this.head;
        while (current) {
          if (current.value === value) return index;
          current = this.isTail(current) ? null : current.next; // 考虑循环链表的情况
          index++;
        }
        return -1;
      }
    
      // 删除方法: 根据value删除节点
      remove(value: T): T | null {
        const index = this.indexOf(value);
        return this.removeAt(index);
      }
    
      peek(): T | undefined {
        return this.head?.value;
      }
    
      // 判读单链表是否为空
      isEmpty(): boolean {
        return this.length === 0;
      }
    
      size(): number {
        return this.length;
      }
    }
    
    const linked = new LinkedList<string>();
    linked.append("aaa");
    linked.append("bbb");
    linked.append("ccc");
    linked.traverse(); // 3 aaa -> bbb -> ccc
    
    linked.insert("zzz", 0);
    linked.insert("ddd", 2);
    linked.insert("eee", 5);
    linked.traverse(); // 6 zzz -> aaa -> ddd -> bbb -> ccc -> eee
    
    console.log(linked.removeAt(0)); // zzz
    console.log(linked.removeAt(1)); // ddd
    console.log(linked.removeAt(3)); // eee
    linked.traverse(); // 3 aaa -> bbb -> ccc
    
    console.log(linked.get(0)); // aaa
    console.log(linked.get(1)); // bbb
    console.log(linked.get(2)); // ccc
    console.log(linked.get(3)); // null
    
    console.log(linked.update("aa", 0)); // true
    console.log(linked.update("cc", 2)); // true
    console.log(linked.update("dd", 3)); // false
    linked.traverse(); // 3 aa -> bbb -> cc
    
    console.log(linked.indexOf("aa")); // 0
    console.log(linked.indexOf("ccc")); // -1
    
    linked.remove("bbb");
    linked.traverse(); // 2 aa -> cc
    
    console.log(linked.isEmpty()); // false
    

面试题

  • 设计链表 https://leetcode.cn/problems/design-linked-list/description/ 上面代码已经完成
    在这里插入图片描述

  • 删除链表中的节点 https://leetcode.cn/problems/delete-node-in-a-linked-list/description/
    在这里插入图片描述

    class ListNode {
      val: number;
      next: ListNode | null;
      constructor(val?: number, next?: ListNode | null) {
        this.val = val === undefined ? 0 : val;
        this.next = next === undefined ? null : next;
      }
    }
    
    function deleteNode(node: ListNode | null): void {
      node!.val = node!.next!.val
      node!.next = node!.next!.next
    }
    
  • 反转链表 https://leetcode.cn/problems/reverse-linked-list/description/
    在这里插入图片描述

    • 非递归实现:
      在这里插入图片描述

      class Node {
        val: number;
        next: ListNode | null;
        constructor(val?: number, next?: ListNode | null) {
          this.val = val === undefined ? 0 : val;
          this.next = next === undefined ? null : next;
        }
      }
      function reverseList(head: Node | null): Node | null {
        // 1.判断节点为null, 或者只要一个节点, 那么直接返回即可
        if (head === null || head.next === null) return head;
      
        let previous: Node | null = null;
        while (head) {
          const current: Node | null = head.next;
          head.next = previous;
          previous = head;
          head = current;
        }
        return previous;
      }
      
    • 递归实现:
      在这里插入图片描述

      function reverseList<T>(head: Node | null): Node | null {
        // 如果使用的是递归, 那么递归必须有结束条件
        if (head === null || head.next === null) return head;
        const newHead = reverseList(head?.next ?? null);
        head.next.next = head;
        head.next = null;
        return newHead;
      }
      let n = new Node(1);
      n.next = new Node(2);
      n.next.next = new Node(3);
      n.next.next.next = new Node(4);
      n.next.next.next.next = new Node(5);
      
      let current = reverseList(n);
      while (current) {
        console.log(current.value); // 5 4 3 2 1
        current = current.next;
      }
      

循环链表

循环链表(Circular Linked List)是一种特殊的链表结构,其中链表的最后一个节点指向链表的第一个节点,从而形成一个闭环。它的主要特性是任何一个节点都可以通过不断访问 next 指针回到起点节点,因此在循环链表中没有空指针这种终止条件
在这里插入图片描述

实现

  • 方式一:从零去实现一个新的链表,包括其中所有的属性和方法

  • 方式二:继承自之前封装的LinkedList,只实现差异化的部分,我们使用这个方式

  • 实现代码如下:实现append、实现insert、实现removeAtindexOftraverse在写单向链表时判断了循环的情况不需要再重构

    import { LinkedList } from "./单向链表实现.ts";
    
    class CircularLinkedList<T> extends LinkedList<T> {
      append(value: T): void {
        super.append(value);
        this.tail!.next = this.head;
      }
      insert(value: T, position: number): boolean {
        const isSuccess = super.insert(value, position);
        if (isSuccess && (position === this.length - 1 || position === 0)) {
          // 如果插入成功 && (尾部插入 || 头部插入)都需要更新tail.next
          this.tail!.next = this.head;
        }
        return isSuccess;
      }
      removeAt(position: number): T | null {
        const value = super.removeAt(position);
        if (
          value &&
          this.tail &&
          (position === this.length - 1 || position === 0)
        ) {
          // 如果删除成功 && tail != null &&(尾部删除 || 头部删除)都需要更新tail.next
          this.tail!.next = this.head;
        }
        return value;
      }
    }
    
    const linked = new CircularLinkedList<string>();
    linked.append("aaa");
    linked.append("bbb");
    linked.append("ccc");
    linked.traverse(); // 3 aaa -> bbb -> ccc -> aaa
    
    linked.insert("zzz", 0);
    linked.insert("ddd", 2);
    linked.insert("eee", 5);
    linked.traverse(); // zzz -> aaa -> ddd -> bbb -> ccc -> eee -> zzz
    
    console.log(linked.removeAt(0)); // zzz
    console.log(linked.removeAt(1)); // ddd
    console.log(linked.removeAt(3)); // eee
    linked.traverse(); // 3 aaa -> bbb -> ccc -> aaa
    
    console.log(linked.get(0)); // aaa
    console.log(linked.get(1)); // bbb
    console.log(linked.get(2)); // ccc
    console.log(linked.get(3)); // null
    
    console.log(linked.update("aa", 0)); // true
    console.log(linked.update("cc", 2)); // true
    console.log(linked.update("dd", 3)); // false
    linked.traverse(); // 3 aa -> bbb -> cc -> aa
    
    console.log(linked.indexOf("aa")); // 0
    console.log(linked.indexOf("ccc")); // -1
    
    linked.remove("bbb");
    linked.traverse(); // 2 aa -> cc -> aa
    
    console.log(linked.isEmpty()); // false
    

双向链表

双向链表(Doubly Linked List)是一种数据结构,类似于单向链表,但每个节点包含两个指针,一个指向下一个节点,一个指向前一个节点
在这里插入图片描述

  • 优点:
    • 可以从头到尾、也可以从尾到头进行遍历,灵活性更高

    • 删除和插入操作时,不需要像单向链表那样只能从头遍历找到前一个节点

  • 缺点:
    • 每个节点需要额外的指针(prev,会占用更多的存储空间

    • 每次在插入或删除某个节点时,需要处理四个引用,实现起来要困难一些

实现

  • 封装双向链表节点:需要进一步添加一个prev属性,用于指向前一个节点

  • 实现代码如下:因为差距较大重新实现appendinsertremoveAt,新增加prepend(在头部添加元素)、postTraverse(从尾部遍历所有节点)

    import { LinkedList, Node } from "./单向实现";
    
    class DoublyNode<T> extends Node<T> {
      next: DoublyNode<T> | null = null;
      prev: DoublyNode<T> | null = null;
    }
    
    class DoublyLinkedList<T> extends LinkedList<T> {
      protected head: DoublyNode<T> | null = null;
      protected tail: DoublyNode<T> | null = null;
    
      // 尾部追加元素
      append(value: T): void {
        const newNode = new DoublyNode(value);
        if (!this.head) {
          this.head = newNode;
        } else {
          this.tail!.next = newNode;
          // 不能将一个父类的对象, 赋值给一个子类的类型
          // 可以将一个子类的对象, 赋值给一个父类的类型(多态)
          newNode.prev = this.tail;
        }
        this.tail = newNode;
        this.length++;
      }
    
      // 插入元素
      insert(value: T, position: number): boolean {
        if (position < 0 && position > this.length) return false;
    
        if (position === 0) {
          this.prepend(value);
        } else if (position === this.length) {
          this.append(value);
        } else {
          const newNode = new DoublyNode(value);
          /* 
            使用 as 断言它是 DoublyNode<T> 类型,
            那么在后续代码中,TypeScript 会允许你访问 DoublyNode<T> 类型中的属性(例如 prev),
            即使这个属性在 Node<T> 类型中并未定义
          */
          const current = this.getNode(position).current as DoublyNode<T>;
    
          newNode.next = current;
          newNode.prev = current.prev;
          current.prev!.next = newNode;
          current.prev = newNode;
    
          this.length++;
        }
    
        return true;
      }
    
      // 删除元素
      removeAt(position: number): T | null {
        if (position < 0 || position >= this.length) return null;
        let current = this.head;
        if (position === 0) {
          if (this.length === 1) {
            this.head = null;
            this.tail = null;
          } else {
            this.head = this.head!.next;
            this.head!.prev = null;
          }
        } else if (position === this.length - 1) {
          current = this.tail;
          this.tail = this.tail!.prev;
          this.tail!.next = null;
        } else {
          current = this.getNode(position).current as DoublyNode<T>
          current!.next!.prev = current!.prev;
          current!.prev!.next = current!.next;
        }
    
        this.length--;
        return current?.value ?? null;
      }
    
      // 在头部添加元素
      prepend(value: T): boolean {
        const newNode = new DoublyNode(value);
        newNode.next = this.head;
        if (this.head) {
          this.head.prev = newNode;
        } else {
          this.tail = newNode;
        }
        this.head = newNode;
        this.length++;
        return true;
      }
    
      // 从尾部开始遍历所有节点
      postTraverse() {
        let values: T[] = [];
        let current = this.tail;
        while (current) {
          values.push(current.value);
          current = current.prev;
        }
        console.log(this.length, values.join(" <- "));
      }
    }
    
    const linked = new DoublyLinkedList<string>();
    
    linked.prepend("aaa");
    linked.append("bbb");
    linked.append("ccc");
    linked.traverse(); // 3 aaa -> bbb -> ccc
    linked.postTraverse(); // 3 ccc <- bbb <- aaa
    
    linked.insert("zzz", 0);
    linked.insert("ddd", 2);
    linked.insert("eee", 5);
    linked.traverse(); // 6 zzz -> aaa -> ddd -> bbb -> ccc -> eee
    
    console.log(linked.removeAt(0)); // zzz
    console.log(linked.removeAt(1)); // ddd
    console.log(linked.removeAt(3)); // eee
    linked.traverse(); // 3 aaa -> bbb -> ccc
    
    console.log(linked.get(0)); // aaa
    console.log(linked.get(1)); // bbb
    console.log(linked.get(2)); // ccc
    console.log(linked.get(3)); // null
    
    console.log(linked.update("aa", 0)); // true
    console.log(linked.update("cc", 2)); // true
    console.log(linked.update("dd", 3)); // false
    linked.traverse(); // 3 aa -> bbb -> cc
    
    console.log(linked.indexOf("aa")); // 0
    console.log(linked.indexOf("ccc")); // -1
    
    linked.remove("bbb");
    linked.traverse(); // 2 aa -> cc
    
    console.log(linked.isEmpty()); // false
    

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

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

相关文章

小小猫棒onu替换家用光猫,薅运营商带宽羊毛,突破1000M

小小猫棒onu 一、总体步骤 1 记录原来光猫信息 主要包括SN&#xff0c;ploam密码&#xff0c;loid、loid密码、 mac、上网的vlan id等 一般gpon采用SN、ploam密码、SNploam密码三种中的一种认证方式 一般Epon采用loid&#xff08;逻辑id&#xff09;、mac、loid mac三种中…

【经典论文阅读11】ESMM模型——基于贝叶斯公式的CVR预估

传统的CVR模型&#xff08;也就是直接对conversion rate建模的模型&#xff09;在实际应用中面临两个问题&#xff08;样本选择偏差与数据稀疏性问题&#xff09;。为了解决这两个问题&#xff0c;本文提出ESMM模型。该模型巧妙地利用用户行为序列去建模这个问题&#xff0c;从…

二十八、Python基础语法(面向对象-下)

一、self 从函数的语法上来看, self 是形参 , 是一个普通的参数,那么在调用的时候,就需要传递实参值。从调用上看, 我们没有给 self 这个形参传递实参值, 但是 Python 解释器会自动的将调用这个方法的对象&#xff0c;作为实参值传递给 self。 class Dog:def eat(self):print…

江协科技STM32学习- P27 实验-串口发送/串口接收

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

基于大语言模型(LLM)自主Agent 智能体综述

近年来,LLM(Large Language Model)取得了显著成功,并显示出了达到人类智能的巨大潜力。基于这种能力,使用LLM作为中央控制器来构建自助Agent,以获得类人决策能力。 Autonomous agents 又被称为智能体、Agent。指能够通过感知周围环境、进行规划以及执行动作来完成既定任务。…

自由学习记录(15)

Java注解 else if的省略问题&#xff08;可能看花&#xff09; else if也是取最近的if连通&#xff0c;看上去加了{}就可以正常执行了&#xff0c;缩进要命&#xff0c;不提示真容易看错&#xff0c; 组合数公式和数组参数 在 C 中&#xff0c;数组作为函数参数时&#xff0c;…

直流稳压电源设计

一、设计任务和基本要求&#xff1a; &#xff08;1&#xff09;设计集成直流稳压电源电路。 &#xff08;2&#xff09;输出直流电压5V。 &#xff08;3&#xff09;画出逻辑电路图&#xff0c;对电路进行仿真验证&#xff0c;写出总结报告。 图 1 系统组成框图 二、进度安排…

使用LangChain控制大模型的输出——解析器Parser

LangChain框架中有两个好用的工具&#xff1a; 提示词模板(PromptTemplate)用于指定LLM的输入&#xff0c;解析器(Parser)来正确解释LLM给出的输出 即&#xff1a; 提示词模板(PromptTemplate)&#xff1a;用于格式化地接受输入string变量&#xff0c;作为完整的提示词。 如 给…

如何在Linux系统中使用Apache HTTP Server

如何在Linux系统中使用Apache HTTP Server Apache简介 安装Apache 在Debian/Ubuntu系统中安装 在CentOS/RHEL系统中安装 启动Apache服务 验证Apache是否正在运行 访问Apache默认页面 配置Apache虚拟主机 创建虚拟主机配置文件 示例虚拟主机配置 创建网站根目录 准备静态网站内…

基于Spring Boot的员工与部门信息管理系统:增删改查全攻略

介绍项目的搭建过程&#xff0c;包括依赖管理、数据库设计、实体类的创建、控制器的编写以及前端的简单实现。希望通过本项目的学习&#xff0c;能够加深大家对Spring Boot及相关技术的理解&#xff0c;为后续的开发奠定基础。 文章目录 前言 环境搭建 开发规范 查询部门 删除部…

深度学习优化器【学习率调整和梯度修正,Optimizer】

文章目录 一、学习率调整1 余弦退火&#xff08;1&#xff09;Warm up&#xff08;2&#xff09;Cosine Anneal 2 AdaGrad3 RMSprop算法4 AdaDelta算法 二、梯度估计修正1 动量法2 Nesterov加速梯度3 Adam修正的原因 4 AdamW 三、总结参考资料 在当今快速发展的人工智能领域&am…

在米尔电子MPSOC实现12G SDI视频采集H.265压缩SGMII万兆以太网推流

1. 引言 随着网络视频平台的发展&#xff0c;用户对于4K高清画质的需求日益增长。然而&#xff0c;许多用户发现&#xff0c;即使购买了视频平台的会员&#xff0c;观看4K内容时画质却不如预期&#xff0c;有时甚至还会出现模糊、卡顿的情况。这种现象背后涉及到视频编码、网络…

项目复盘:TapTap聚光灯Gamejam

前言 三周的开发终于落下帷幕&#xff0c;其实我个人还是对结果很满意的&#xff0c;虽然作品不及他人的万分之一&#xff0c;不过主要目的还是以熟悉Godot引擎本身为主。这次我做的事情其实主要还是把在Unity那边学到的东西Copy到Godot来&#xff0c;仅此而已&#xff0c;几乎…

WPF自定义日历控件Calendar 的方法

推荐下载地址 https://www.haolizi.net/example/view_2107.html <UserControl.Resources><local1:DayConverter x:Key"DayConverter"/><!--导入转换器--><Style x:Key"CalendarStyle1"TargetType"{x:Type Calendar}">&…

《ToDesk云电脑vs青椒云性能测试,谁更能实现游戏自由?》

ToDesk云电脑vs青椒云性能测试 【前言】【使用云电脑的意义】【实测软件】【性能参数对比】1. 硬件配置2.游戏兼容性3. 延迟、流畅度与画面清晰度4. 用户体验5. 价格对比6. 附加功能 【游戏性能测试】《游戏一 黑悟空》《游戏二 赛博朋克 2077》《游戏三 CS反恐精英》 【本文小…

ctfshow文件包含web78~81

目录 web78 方法一&#xff1a;filter伪协议 方法二&#xff1a;input协议 方法三&#xff1a;data协议 web79 方法一:input协议 方法二&#xff1a;data协议 web80 方法一&#xff1a;input协议 方法二&#xff1a;日志包含getshell web81 web78 if(isset($_GET[file]…

动态IP是什么?

随着互联网成为人们生活的重要组成部分&#xff0c;以信息传递为主导的时代种&#xff0c;网络连接质量对我们的工作效率、学习进度以及娱乐体验等方面都有很大影响。 动态IP&#xff0c;作为网络连接中的一种重要IP代理形式&#xff0c;越来越受到用户的欢迎。本文将深入解析…

vue下载安装

目录 vue工具前置要求&#xff1a;安装node.js并配置好国内镜像源下载安装 vue 工具 系统&#xff1a;Windows 11 前置要求&#xff1a;安装node.js并配置好国内镜像源 参考&#xff1a;本人写的《node.js下载、安装、设置国内镜像源&#xff08;永久&#xff09;&#xff…

在JAVA中使用Paho MQTT客户端

1.在maven里面配置好依赖 <dependency><groupId>org.eclipse.paho</groupId><artifactId>org.eclipse.paho.client.mqttv3</artifactId><version>1.2.2</version> </dependency> 2.创建APP类 package com.leo;import org.e…

Django+websocket实现一个简单聊天

WebSocket是一种在单个TCP连接上进行全双工通信的协议。它由IETF在2011年定为标准RFC 6455&#xff0c;并由RFC7936补充规范&#xff0c;同时WebSocket API也被W3C定为标准。 1、定义与原理 WebSocket是独立的、创建在TCP上的协议&#xff0c;它使用HTTP/1.1协议的101状态码进…