彻底理解链表(LinkedList)结构

目录

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

链表(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
    

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

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

相关文章

使用Docker Compose搭建多服务应用

使用Docker Compose搭建多服务应用 Docker Compose简介 安装Docker Compose 在Linux上安装Docker Compose 在macOS上安装Docker Compose 在Windows上安装Docker Compose 创建项目结构 Flask应用 安装依赖 Dockerfile 配置Docker Compose 构建和运行应用 访问应用 高级配置 环…

LLaMA系列一直在假装开源...

伙伴们&#xff0c;很奇怪~ 关于LLM的开源与闭源模型的竞争又开始愈发激烈。 众所周知&#xff0c;开源模型以其开放性和社区驱动的特点受到一部分用户的青睐&#xff0c;而闭源模型则因其专业性和性能优化被广泛应用于商业领域。由于大模型最近2年的突然兴起&#xff0c;开源…

i2c与从设备通讯编程示例之开发板测试

编译elf1_cmd_i2c程序 &#xff08;一&#xff09;设置交叉编译环境 &#xff08;二&#xff09;查看elf1_cmd_i2c文件夹Makefile文件。查看当前编译规则&#xff0c;i2c_demo是编译整个工程&#xff0c;clean是清除工程。 &#xff08;三&#xff09;在03_elf1_cmd_i2c文件夹…

开源办公软件 ONLYOFFICE 深入探索

文章目录 引言1. ONLYOFFICE 创建的背景1. 1 ONLYOFFICE 项目启动1. 2 ONLYOFFICE 的发展历程 2. 核心功能介绍2. 1 桌面编辑器2. 1. 1 文档2. 1. 2 表格2. 1. 3 幻灯片 2. 2 协作空间2. 3 文档编辑器 - 本地部署版 3. 技术介绍4. 安装5. 优势与挑战6. 个人体验7. 强大但不止于…

C++ -- 模板进阶

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

【力扣】Go语言回溯算法详细实现与方法论提炼

文章目录 一、引言二、回溯算法的核心概念三、组合问题1. LeetCode 77. 组合2. LeetCode 216. 组合总和III3. LeetCode 17. 电话号码的字母组合4. LeetCode 39. 组合总和5. LeetCode 40. 组合总和 II小结 四、分割问题6. LeetCode 131. 分割回文串7. LeetCode 93. 复原IP地址小…

HarmonyOS 私仓搭建

1. HarmonyOS 私仓搭建 私仓搭建文档&#xff1a;https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-ohpm-repo-quickstart-V5   发布共享包[https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-har-publish-0000001597973129-V5]…

LabVIEW 离心泵机组故障诊断系统

开发了一套基于LabVIEW图形化编程语言设计的离心泵机组故障诊断系统。系统利用先进的数据采集技术和故障诊断方法&#xff0c;通过远程在线监测与分析&#xff0c;有效提升了离心泵的预测性维护能力&#xff0c;保证了石油化工生产的连续性和安全性。 项目背景及意义 离心泵作…

小林渗透入门:burpsuite+proxifier抓取小程序流量

目录 前提&#xff1a; 代理&#xff1a; proxifier&#xff1a; 步骤&#xff1a; bp证书安装 bp设置代理端口&#xff1a; proxifier设置规则&#xff1a; proxifier应用规则&#xff1a; 结果&#xff1a; 前提&#xff1a; 在介绍这两个工具具体实现方法之前&#xff0…

C++_STL_xx_番外01_关于STL的总结(常见容器的总结;关联式容器分类及特点;二叉树、二叉搜索树、AVL树(平衡二叉搜索树)、B树、红黑树)

文章目录 1. 常用容器总结2. 关联式容器分类3. 二叉树、二叉搜索树、AVL树、B树、红黑树 1. 常用容器总结 针对常用容器的一些总结&#xff1a; 2. 关联式容器分类 关联式容器分为两大类&#xff1a; 基于红黑树的set和map&#xff1b;基于hash表的unorder_set和unorder_ma…

Apache InLong数据集成工具安装部署和功能介绍

环境部署 在开始之前&#xff0c;我们需要安装 InLong 的全部组件 安装 ClickHouse 使用 Docker 快速部署 ClickHouse 数据库&#xff0c;命令如下&#xff1a; docker run -d --rm --nethost --name clickhouse -e CLICKHOUSE_USERadmin -e CLICKHOUSE_PASSWORDinlong -e C…

【开源免费】基于SpringBoot+Vue.JS网上超市系统(JAVA毕业设计)

本文项目编号 T 037 &#xff0c;文末自助获取源码 \color{red}{T037&#xff0c;文末自助获取源码} T037&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

练习LabVIEW第三十二题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第三十二题&#xff1a; 利用labview elapsed time(已用时间)定时设计输出一个方波 开始编写&#xff1a; 前面板放置一…

从“点”到“面”,热成像防爆手机如何为安全织就“透视网”?

市场上测温产品让人眼花缭乱&#xff0c;通过调研分析&#xff0c;小编发现测温枪占很高比重。但是&#xff0c;测温枪局限于显示单一数值信息&#xff0c;无法直观地展示物体的整体温度分布情况&#xff0c;而且几乎没有功能拓展能力。以AORO A23为代表的热成像防爆手机改变了…

恋爱脑学Rust之智能指针Rc,RefCell和Weak指针

小明和小丽为了维系彼此的关系&#xff0c;一起探索了智能指针的奥秘。通过 Rc、RefCell 和 Weak 的帮助&#xff0c;他们得以克服情感中遇到的种种困境。 第一章&#xff1a;Rc 智能指针的共生 小明和小丽搬进了一个共同的小屋&#xff0c;他们彼此相爱&#xff0c;决定共用…

C语言 | Leetcode C语言题解之第530题二叉搜索树的最小绝对差

题目&#xff1a; 题解&#xff1a; void dfs(struct TreeNode* root, int* pre, int* ans) {if (root NULL) {return;}dfs(root->left, pre, ans);if (*pre -1) {*pre root->val;} else {*ans fmin(*ans, root->val - (*pre));*pre root->val;}dfs(root->…

image_points_to_world_plane详解

分三个部分来聊聊这个算子 一,算子的参数介绍 二,算法的计算过程 三,举例实现 第一部分,算子的介绍 image_points_to_world_plane( : : CameraParam, WorldPose, Rows, Cols, Scale : X, Y) 参数介绍: CameraParam,:相机内参 WorldPose 世界坐标系,也叫物体坐标系(成…

“发放父作业单”是“过数”用例里面的内容吗

刘京城 2020-4-14 23:01 。。。。(注&#xff1a;这是一个人的昵称&#xff0c;不是省略号) 首先&#xff0c;执行者是同一个&#xff0c;那么思考焦点要关注“过数”用例是不是“发放父作业单”用例的一个步骤&#xff0c;和行为操作的频率无关&#xff0c;而是和责任有关&am…

vue3 ref,shallowRef,reactive,shallowReactive使用的简单异同点说明

1、这几个都是负责data的存储及读取的&#xff0c;我们常用的是ref,reactive。 2、看一下shallowRef这个,shallowReactive与其类似。 说明&#xff1a;以官网的说明&#xff0c;这个state.value.count 2是不会触发视图更新的&#xff0c;但是如果直接在<script setup lang…

Java | Leetcode Java题解之第517题超级洗衣机

题目&#xff1a; 题解&#xff1a; class Solution {public int findMinMoves(int[] machines) {int tot Arrays.stream(machines).sum();int n machines.length;if (tot % n ! 0) {return -1;}int avg tot / n;int ans 0, sum 0;for (int num : machines) {num - avg;s…