【TypeScript】常见数据结构与算法(二):链表

文章目录

    • 链表结构(LinkedList)
      • 链表以及数组的缺点
        • 数组
        • 链表的优势
      • 什么是链表?
      • 封装链表相关方法
      • 源码
        • 链表常见面试题
          • 237-删除链表中的节点
          • 206 - 反转链表
      • 数组和链表的复杂度对比

链表结构(LinkedList)

链表以及数组的缺点

  • 链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。
  • 这一章中,我们就来学习一下另外一种非常常见的用于存储数据的线性结构:链表。
数组
  • 要存储多个元素,数组(或称为链表)可能是最常用的数据结构。
  • 我们之前说过,几乎每一种编程语言都有默认实现数组结构。

但是数组也有很多缺点

  • 数组的创建通常需要申请一段连续的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的),所以当当前数组不能满足容量需求时,需要扩容。(一般情况下是申请一个更大的数组,比如2倍。然后将原数组中的元素复制过去
  • 而且在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移。
  • 尽管JavaScript的Array底层可以帮我们做这些事,但背后的原理依然是这样。
链表的优势

要存储多个元素,另外一个选择就是链表

但不同于数组,链表中的元素在内存中不必是连续的空间。

  • 链表的每个元素有一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者链接)组成。

相对于数组,链表的优势:

  • 内存空间不是必须连续的。

    √可以充分利用计算机的内存,实现灵活的内存动态管理。

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

  • 链表在插入和删除数据时,时间复杂度可以达到O(1)

    √相对数组效率高很多

相对数组,链表的缺点:

  • 链表访问任何一个位置的元素时,都要从头开始访问。(无法跳过第一个元素访问任何一个元素)。
  • 无法通过下标直接访问元素,需要从头一个个访问,直到找到对应元素。

什么是链表?

  • 其实上面我们已经简单的提过了链表的结构,我们这里更加详细的分析一下。
  • 链表类似于火车:有一个火车头,火车头会连接一个节点,节点上有乘客(类似于数据),并且这个节点会连接下一个节点,以此类推。

image.png

image.png

封装链表相关方法

我们先来认识一下,链表中应该有哪些常见的操作
append(element):向链表尾部添加一个新的项
insert(position,element):向链表的特定位置插入一个新的项。
image.png
get(position):获取对应位置的元素
image.png
indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1。
update(position,element):修改某个位置的元素
removeAt(position):从链表的特定位置移除一项。
image.png
remove(element):从链表中移除一项。
isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
size():返回链表包含的元素个数。与数组的length属性类似。

源码

// 1.创建Node节点类
class Node<T> {
  value: T;
  next: Node<T> | null = null;
  constructor(value: T) {
    this.value = value;
  }
}

// 2.创建LinkedList的类
class LinkedList<T> {
  private head: Node<T> | null = null;
  private size: number = 0;

  get length() {
    return this.size;
  }
  // 封装私有方法
  // 根绝positon获取当前的节点(不是节点的value,而是获取节点)
  private getNode(position: number): Node<T> | null {
    let index = 0;
    let current = this.head;
    while (index++ < position && current) {
      current = current.next;
    }
    return current;
  }

  // 追加节点
  append(value: T) {
    // 1.根据value新建一个Node节点
    const newNode = new Node(value);
    // 2.
    if (!this.head) {
      this.head = newNode;
    } else {
      let current = this.head;
      while (current.next) {
        current = current.next;
      }
      // current肯定指向最后一个节点
      current.next = newNode;
    }
    // 3.size++
    this.size++;
  }

  // 遍历链表的方法
  traverse() {
    const values: T[] = [];
    let current = this.head;
    while (current) {
      values.push(current.value);
      current = current.next;
    }
    console.log(values.join(" -> "));
  }

  //链表插入元素的方法
  insert(value: T, position: number): boolean {
    // 1.越界判断
    if (position < 0 || position > this.size) return false;
    // 2.根据value创建新的节点
    const newNode = new Node(value);
    /* 3.判断
     * 是否插入头部
     * 否则就找到需要插入的位置,然后记录前一个节点和当前节点,在前一个节点的next等于newNode,newNode的next等于后一个节点
     */
    if (position === 0) {
      newNode.next = this.head;
      this.head = newNode;
    } else {
      const previous = this.getNode(position - 1);

      newNode.next = previous!.next;
      previous!.next = newNode;
    }
    this.size++;

    return true;
  }

  //链表插入元素的方法
  removeAt(position: number): T | null {
    // 1.越界判断
    if (position < 0 || position >= this.size) return null;

    let current = this.head;
    if (position === 0) {
      this.head = current?.next ?? null;
    } else {
      const previous = this.getNode(position - 1);
      previous!.next = previous?.next?.next ?? null;
    }
    this.size--;

    return current?.value ?? null;
  }

  // 获取方法
  get(position: number): T | null {
    if (position < 0 || position >= this.size) return null;

    return this.getNode(position)?.value ?? null;
  }
  // 更新方法
  update(value: T, position: number): boolean {
    if (position < 0 || position >= this.size) return false;
    const currentNode = this.getNode(position);
    currentNode!.value = value;
    return true;
  }
  // 根据值获取该值位置索引
  indexOf(value: T): number {
    let current = this.head;
    let index = 0;
    while (current) {
      if (current.value === value) {
        return index;
      }
      index++;
      current = current.next;
    }
    return -1;
  }

  // 删除方法,根据value删除
  remove(value: T): T | null {
    const index = this.indexOf(value);
    return this.removeAt(index);
  }

  // 判断单链表是否为空的方法
  isEmpty() {
    return this.size === 0;
  }
}

export {};

链表常见面试题
237-删除链表中的节点
  • https://leetcode.cn/problems/delete-node-in-a-linked-list/

image.png
解题

// Definition for singly-linked list.
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;
  }
}

/**
 Do not return anything, modify it in-place instead.
 */
function deleteNode(node: ListNode | null): void {
  node!.val = node!.next!.val
  node!.next = node!.next!.next
}

206 - 反转链表
  • https://leetcode.cn/problems/reverse-linked-list/

image.png

  • 用栈结构解决
function reverseList(head: ListNode | null): ListNode | null {
  if(head === null) return null
  if(head.next === null) return head


  const stack:ListNode[] = []
  let current:ListNode | null = head
  while(current) {
    stack.push(current)
    current = current.next
  }


  const newHead:ListNode = stack.pop()!
  let newHeadCurrent = newHead
  while(stack.length) {
    const node = stack.pop()!
    newHeadCurrent.next = node
    newHeadCurrent = newHeadCurrent.next
  }


  newHeadCurrent.next = null


  return newHead
};
  • 用非递归解题
function reverseList(head: ListNode | null): ListNode | null {
  if (head === null || head.next === null) return head;

  let newHead: ListNode | null = null;
  // 1.next = 2, 2.next = 3, 3.next = 4
  while (head) {
    // 让current指向下一个节点
    // 目的:保留下个节点的引用,可以拿到,并且不会销毁(current = 2)
    const current= head.next;
    // 改变head当前指向的节点,指向newHead
    // 这里反转链表对于第一节点来说,指向newHead就是null(1.next = null)
    head.next = newHead;
    // 让newhead指向head节点
    // 这里开始准备反转新的节点,目的是下一次遍历时,可以让下一个节点指向第一个节点(newHead = 1)
    newHead = head;
    // 让head指向下个节点也就是current(head = 2)
    head = current;
  }

  return newHead;
}

image.png

  • 用递归方案解题
function reverseList(head: ListNode | null): ListNode | null {
  // 递归停止条件,当递归到最后一个节点时停止
  if (head === null || head.next === null) return head;
  // 一直递归循环直到符合head === null 时停止递归
  // 那么拿到的就是链表倒数第二个节点
  const newHead = reverseList(head.next ?? null)
  // 反转链表,让最后一个节点指向head开始正式反转
  head.next.next = head
  // 让倒数第二个节点的next指向null
  head.next = null
  // 最后递归完了就是反转后的链表了
  return newHead
}

数组和链表的复杂度对比

接下来,我们使用大O表示法来对比一下数组和链表的时间复杂度:

image.png

  • 数组是一种连续的存储结构,通过下标可以直接访问数组中的任意元素。

  • 时间复杂度:对于数组,随机访问时间复杂度为o(1),插入和删除操作时间复杂度为o(n)。

  • 空间复杂度:数组需要连续的存储空间,空间复杂度为o(n)。

  • 链表是一种链式存储结构,通过指针链接起来的节点组成,访问链表中元素需要从头结点开始遍历。

  • 时间复杂度:对于链表,随机访问时间复杂度为o(n),插入和删除操作时间复杂度为o(1)。

  • 空间复杂度:链表需要为每个节点分配存储空间,空间复杂度为O(n)。

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

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

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

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

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

相关文章

数据库-MySQL之数据库必知必会10-13章

第10章 创建计算字段 拼接字段 使用Concat()函数 执行算术计算 示例&#xff1a;从 Products 表中返回 prod_id、prod_price 和 sale_price。sale_price 是一个包含促销价格的计算字段。提示&#xff1a;可以乘以 0.9&#xff0c;得到原价的 90%&#xff08;即 10%的折扣&…

手机APP-MCP走蓝牙无线遥控智能安全帽~执法记录仪~拍照录像,并可做基础的配置,例如修改服务器IP以及配置WiFi等

手机APP-MCP走蓝牙无线遥控智能安全帽~执法记录仪~拍照录像,并可做基础的配置,例如修改服务器IP以及配置WiFi等 手机APP-MCP走蓝牙无线遥控智能安全帽~执法记录仪~拍照录像,并可做基础的配置,例如修改服务器IP以及配置WiFi等&#xff0c; AIoT万物智联&#xff0c;智能安全帽…

Docker的项目资源参考

Docker的项目资源包括以下内容&#xff1a; Docker官方网站&#xff1a;https://www.docker.com/ Docker Hub&#xff1a;https://hub.docker.com/ Docker文档&#xff1a;https://docs.docker.com/ Docker GitHub仓库&#xff1a;https://github.com/docker Docker官方博客…

C#,《小白学程序》第二课:数组,循环与排序

1 什么是数组&#xff1f; 数组 Array 是一组数值&#xff08;数 或 值&#xff09;。 int[] a; int[,] b; int[][] c; Anything[] d; 都是数组。 2 排序 排序就是按大小、名字、拼音或你指定的信息进行比较后排队。 排序是数组最基本的功能需求。 3 文本格式 /// <summa…

使用v-md-editor开发sql查看器--实战

v-md-editor markdown编辑器 文档&#xff1a;https://code-farmer-i.github.io/vue-markdown-editor/zh/ echo 创建一个空目录&#xff0c;使用vscode打开此空目录&#xff0c;进入终端&#xff0c;输入如下命令 npm create vitelatest . -- --template vue echo 选择 vue 和 …

Log4j2.xml不生效:WARN StatusLogger Multiple logging implementations found:

背景 将 -Dlog4j.debug 添加到IDEA的类的启动配置中 运行上图代码&#xff0c;这里log4j2.xml控制的日志级别是info&#xff0c;很明显是没生效。 DEBUG StatusLogger org.slf4j.helpers.Log4jLoggerFactory is not on classpath. Good! DEBUG StatusLogger Using Shutdow…

应用内测分发平台如何上传应用包体?

●您可免费将您的应用&#xff08;支持苹果.ios安卓.apk文件&#xff09;上传至咕噜分发平台&#xff0c;我们将免费为应用生成下载信息&#xff0c;但咕噜分发将会对应用的下载次数进行收费&#xff08;每个账号都享有免费赠送的下载点数以及参加活动的赠送点数&#xff09;&a…

pat实现基于邻接表表示的深度优先遍历[含非递归写法]

文章目录 1.递归2.非递归 1.递归 void DFS(ALGraph G, int v) {visited[v] 1;printf("%c ", G.vertices[v].data);for (ArcNode* cur G.vertices[v].firstarc; cur ! nullptr; cur cur->nextarc){if (!visited[cur->adjvex])DFS(G, cur->adjvex);} }2.非…

Maven - 打包之争:Jar vs. Shade vs. Assembly

文章目录 Pre概述Jar 打包方式_maven-jar-pluginOverview使用官方文档 Shade 打包方式_maven-shade-pluginOverview使用将部分jar包添加或排除将依赖jar包内部资源添加或排除自动将所有不使用的类排除将依赖的类重命名并打包进来 &#xff08;隔离方案&#xff09;修改包的后缀…

【VSCode】VSCode 使用

目录 文章目录 目录插件配置设置代码不显示 git 提示 "xxx months ago | 1 author"设置打开项目不自动选择 CMakeLists 插件 以下插件为 C 开发偏好设置。 C/CCMakeCMake ToolsGitLensRemote DevelopmentRemote Explorer 配置 设置代码不显示 git 提示 “xxx mon…

基于java实现捕鱼达人游戏

开发工具eclipse,jdk1.8 文档截图&#xff1a; package com.qd.fish;import java.awt.Graphics; import java.awt.image.BufferedImage; import java.util.Random;public class Fish {//定义鱼的图片BufferedImage fishImage;//定义鱼的数组帧BufferedImage[] fishFrame;//…

leetcode刷题详解一

算法题常用API std::accumulate 函数原型&#xff1a; template< class InputIt, class T > T accumulate( InputIt first, InputIt last, T init );一般求和的&#xff0c;代码如下&#xff1a; int sum accumulate(vec.begin() , vec.end() , 0);详细用法参考 lo…

python opencv -模板匹配

python opencv -模板匹配 模板匹配就是&#xff0c;我们现有一个模板和一个图片&#xff0c;然后&#xff0c;在这个图片中寻找和模板近似的部分。 在opencv 中主要通过cv2.matchTemplate这个函数去实现。 下面我们先看一下&#xff0c;模板图片和需要匹配的图片&#xff1a…

【完美世界】叶倾仙强势登场,孔雀神主VS护道人,石昊重逢清漪

Hello,小伙伴们&#xff0c;我是拾荒君。 《完美世界》国漫第138集已更新。在这一集中&#xff0c;天人族的行为让人大跌眼镜&#xff0c;他们不仅没有对石昊感恩戴德&#xff0c;反而一心想要夺取他身上的所有秘法宝术。天人族的护道人登场&#xff0c;试图以强大的实力压制石…

12V降3.3V100mA稳压芯片WT7133

12V降3.3V100mA稳压芯片WT7133 WT71XX系列是一款采用CMOS工艺实现的三端高输入电压、低压差、小输出电流电压稳压器。 它的输出电流可达到100mA&#xff0c;输入电压可达到18V。其固定输出电压的范围是2.5V&#xff5e;8.0V&#xff0c;用户 也可通过外围应用电路来实现可变电压…

STM32入门--CAN

目录 一、bxCan简介 二、bxCAN总体描述 2.1概述 2.2CAN框图 三、bxCA的工作模式 3.1初始化模式 3.2正常模式 3.3睡眠模式&#xff08;低功耗&#xff09; 四、测试模式 4.1静默模式 4.2环回模式 五、bxCAN功能描述 5.1 发送处理 ​编辑 5.2接收管理 5.2.1 标识符过…

用SOLIDWORKS画个高尔夫球,看似简单的建模却大有学问

SOLIDWORKS软件提供了大量的建模功能&#xff0c;如果工程师能灵活使用这些功能&#xff0c;就可以绘制得到各式各样的模型&#xff0c;我们尝试使用SOLIDWORKS绘制高尔夫球模型&#xff0c;如下图所示。 为什么选用solid works进行建模&#xff1f; solid works是一款功能强大…

MMFN-AL

MMFN means ‘multi-modal fusion network’ 辅助信息 作者未提供代码

mac VScode 添加PHP debug

在VScode里面添加PHP Debug 插件,根据debug描述内容操作 1: 随意在index里面写个方法,然后用浏览器访问你的hello 方法,正常会进入下边的内容 class IndexController {public function index(){return 您好&#xff01;这是一个[api]示例应用;}public function hello() {phpin…

pat实现基于邻接矩阵表示的深度优先遍历[含非递归写法]

文章目录 1.递归2.非递归 1.递归 void DFS(Graph G, int v) {visited[v] 1;printf("%c ", G.vexs[v]);for (int i 0; i < G.vexnum; i) {if (!visited[i] && G.arcs[v][i]) DFS(G, i);} }2.非递归 #include <stack> #include <iostream> …