彻底理解链表(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);// 链表为空,直接赋值为headif (!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 -> ccclinked.insert("zzz", 0);
    linked.insert("ddd", 2);
    linked.insert("eee", 5);
    linked.traverse(); // 6 zzz -> aaa -> ddd -> bbb -> ccc -> eeeconsole.log(linked.removeAt(0)); // zzz
    console.log(linked.removeAt(1)); // ddd
    console.log(linked.removeAt(3)); // eee
    linked.traverse(); // 3 aaa -> bbb -> cccconsole.log(linked.get(0)); // aaa
    console.log(linked.get(1)); // bbb
    console.log(linked.get(2)); // ccc
    console.log(linked.get(3)); // nullconsole.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 -> ccconsole.log(linked.indexOf("aa")); // 0
    console.log(linked.indexOf("ccc")); // -1linked.remove("bbb");
    linked.traverse(); // 2 aa -> ccconsole.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!.valnode!.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 1current = 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.nextthis.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.nextthis.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 -> aaalinked.insert("zzz", 0);
    linked.insert("ddd", 2);
    linked.insert("eee", 5);
    linked.traverse(); // zzz -> aaa -> ddd -> bbb -> ccc -> eee -> zzzconsole.log(linked.removeAt(0)); // zzz
    console.log(linked.removeAt(1)); // ddd
    console.log(linked.removeAt(3)); // eee
    linked.traverse(); // 3 aaa -> bbb -> ccc -> aaaconsole.log(linked.get(0)); // aaa
    console.log(linked.get(1)); // bbb
    console.log(linked.get(2)); // ccc
    console.log(linked.get(3)); // nullconsole.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 -> aaconsole.log(linked.indexOf("aa")); // 0
    console.log(linked.indexOf("ccc")); // -1linked.remove("bbb");
    linked.traverse(); // 2 aa -> cc -> aaconsole.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 <- aaalinked.insert("zzz", 0);
    linked.insert("ddd", 2);
    linked.insert("eee", 5);
    linked.traverse(); // 6 zzz -> aaa -> ddd -> bbb -> ccc -> eeeconsole.log(linked.removeAt(0)); // zzz
    console.log(linked.removeAt(1)); // ddd
    console.log(linked.removeAt(3)); // eee
    linked.traverse(); // 3 aaa -> bbb -> cccconsole.log(linked.get(0)); // aaa
    console.log(linked.get(1)); // bbb
    console.log(linked.get(2)); // ccc
    console.log(linked.get(3)); // nullconsole.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 -> ccconsole.log(linked.indexOf("aa")); // 0
    console.log(linked.indexOf("ccc")); // -1linked.remove("bbb");
    linked.traverse(); // 2 aa -> ccconsole.log(linked.isEmpty()); // false
    

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

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

相关文章

【MySQL系列】字符集设置

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

02- 模块化编程-001 ADC采样与显示

周末把单片机的开发环境理顺了,开始模块化编程的实践&#xff0c;先从外围模块开始&#xff0c;先从独立的模块&#xff0c;然后构建复杂一些的综合应用&#xff0c;条件所限&#xff0c;以protues的仿真为主。 1、单片机内置ADC采样与显示电路 2、电路原理简介 该电路主要由…

VScode设置系统界面字体

现象&#xff1a; 系统界面字体太大&#xff0c;导致菜单栏字体显示不全&#xff0c;每次使用都要先点然后才能打开终端和帮助 缩小字体应该就可以实现全部都看到的效果 解决步骤 1. “齿轮形状”设置中心---->设置 2.输入zoom 3.用户—>窗口—>修改“Window: Zoom…

C++初阶——类和对象(上)

目录 1、类的定义 1.1 类定义格式 1.2 访问限定符 1.3 类域 2、类的实例化 2.1 实例化的概念 3、this指针 1、类的定义 1.1 类定义格式 • class为定义类的关键字&#xff0c;Stack为类的名字&#xff0c;{}中为类的主体&#xff0c;注意类定义结束时后面分号不能省略。…

小北的字节跳动青训营与LangChain系统安装和快速入门学习(持续更新中~~~)

前言 最近&#xff0c;字节跳动的青训营再次扬帆起航&#xff0c;作为第二次参与其中的小北&#xff0c;深感荣幸能借此机会为那些尚未了解青训营的友友们带来一些详细介绍。青训营不仅是一个技术学习与成长的摇篮&#xff0c;更是一个连接未来与梦想的桥梁~ 小北的青训营 X M…

卷积神经网络实验三:模型优化(1)

作者有话说&#xff1a; 这篇文章写的还是比混乱的。因为本人也是第一次做这样的尝试&#xff0c;虽然接触深度学习有一年了&#xff0c;但是对于模型的优化仅仅是局限于理论上。通过这一次的实验&#xff0c;我对于模型的理解也更深了几分。我不期望这篇文章能帮你能解决多大问…

信息安全数学基础(42)分式域

一、定义 设R是一个整环&#xff0c;如果有一个域F使得从R到F有一个单的环同态&#xff0c;并且F中的每一个元素都可以表示成σ(a)σ(b)^(-1)的形式&#xff0c;其中a∈R&#xff0c;b∈R*&#xff08;R的非零元构成的乘法群&#xff09;&#xff0c;那么把F称为R的分式域。 二…

HTML 基础概念:什么是 HTML ? HTML 的构成 与 HTML 基本文档结构

文章目录 什么是 HTML &#xff1f;HTML 的构成 &#xff1f;什么是 HTML 元素&#xff1f;HTML 元素的组成部分HTML 元素的特点 HTML 基本文档结构如何打开新建的 HTML 文件代码查看 什么是 HTML &#xff1f; HTML&#xff08;超文本标记语言&#xff0c;HyperText Markup L…

文本列的性能优化?深入Oracle全文索引

一.什么是全文索引&#xff1f; 全文索引通过分析和处理文本&#xff0c;将文档中的单词分解为词条&#xff08;tokens&#xff09;&#xff0c;然后存储词条与其所在文档的映射关系。这使得数据库可以快速定位包含特定关键字的记录&#xff0c;而不必对所有文本逐字匹配。 二…

CentOS7配置静态IP(非解决方法指导,纯笨蛋记录)

一、这篇博客算是记录我终于解决我安装的虚拟机ping不通外网的问题&#xff0c;前前后后我尝试了很多次花了很多时间&#xff0c;最后弄完发现其实都是因为我之前根本不知道什么是虚拟机的网络配置。。。。。 这个链接介绍了vmware虚拟机三种网络模式及配置详解_vmware 特定虚…

数据丢失不用愁!10款必备的数据恢复软件全介绍

现在我们使用微信的频率愈发高涨&#xff0c;不论是生活、工作&#xff0c;甚至是兴趣还好可能都能在这个软件里看到身影。正因为它涉及了我们的生活如果因为病毒或者其他原因导致聊天记录清空&#xff0c;对很多人来说都很麻烦。那么微信聊天记录删了怎样才能恢复呢&#xff0…

国内版Sketchfab平台 - CG美术之家(3D编辑发布篇)

CG美术之家为您提供了一个简便的模型上传流程&#xff0c;让发布您的3D模型变得轻而易举。只需准备好通用的3D模型格式&#xff0c;如OBJ、FBX或STL&#xff0c;您就可以轻松上传并分享您的创作。我们的平台支持在线3D渲染&#xff0c;您只需花费不到一分钟的时间&#xff0c;就…

安防被动红外和主动红外

被动红外探测器是依靠被动的吸收热能动物活动时身体散发出的红外热能进行报警的&#xff0c;也称热释红外探头&#xff0c;其探测器本身是不会发射红外线的。 被动红外探测器中有2个关键性元件&#xff0c;一个是菲涅尔透镜&#xff0c;另一个是热释电传感器。**自然界中任何高…

使用Flask构建RESTful API

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Flask构建RESTful API Flask简介 环境搭建 安装Flask 项目结构 创建应用 路由定义 请求处理 获取查询参数 获取请求体 响应…

知识竞赛活动选手台桌怎么摆放好

知识竞赛活动选手台桌摆放主要根据选手台桌、舞台及和竞赛规则决定&#xff0c;一般有以下几种方案。 一、一字摆放 如果舞台宽度不够&#xff0c;就一字摆放。这样选手就无法看到大屏&#xff0c;选手就要配置看题平板或电脑。也可以在选手前面放置一台大电视。如图&#xf…

SQL之排名窗口函数RANK()、ROW_NUMBER()、DENSE_RANK() 和 NTILE() 的区别(SQL 和 Hive SQL 都支持)

现有一张student 表&#xff0c;表中包含id、uname、age、score 四个字段&#xff0c;如下所示&#xff1a; 该表的数据如下所示&#xff1a; 一、ROW_NUMBER() 1、概念 ROW_NUMBER() 为结果集中的每一行分配一个唯一的连续整数&#xff0c;编号从 1 开始。‌ 该函数按照指…

NeurIPS - Ariel Data Challenge 2024

1比赛概述 Ariel数据挑战2024 邀请参赛者开发机器学习模型&#xff0c;解决天文学领域中的一项重大挑战——从即将到来的欧洲航天局&#xff08;ESA&#xff09;Ariel任务的模拟观测中提取微弱的系外行星信号。Ariel任务计划于2029年启动&#xff0c;旨在对银河系邻近的约1,00…

Javaweb 实验4 xml

我发现了有些人喜欢静静看博客不聊天呐&#xff0c; 但是ta会点赞。 这样的人呢帅气低调有内涵&#xff0c; 美丽大方很优雅。 说的就是你&#xff0c; 不用再怀疑哦 实验四 XML 目的&#xff1a; 安装和使用XML的开发环境认识XML的不同类型掌握XML文档的基本语法了解D…

CRON组件一个复杂的一个简单的

CRON组件一个复杂的一个简单的 一个是复杂点的一个是简单点。 1.以简单的为例使用&#xff1a; 父组件 import CronSimple from "/views/xxx/components/cron-simple/index.vue";components: {CronSimple}<el-dialog title"调度CRON"v-if"cronV…

乒乓球烧拍日记之三蝴蝶蓝芳碳

朋友给了我个蝴蝶成品拍&#xff0c;要更换胶皮&#xff0c;底板是蓝芳碳磕碰很严重&#xff0c;木材都没了&#xff0c;没法补。淋巴面材&#xff0c;成品拍胶水很牢固非常难去除 把蓝芳碳纤维去除下毛边&#xff0c;然后用乳胶胶水填补缺失部分。(https://img-blog.csdnimg.…