使用双向链表优化数组操作的性能

   🎬 江城开朗的豌豆:个人主页

 🔥 个人专栏 :《 VUE 》 《 javaScript 》

  📝 个人网站 :《 江城开朗的豌豆🫛 》 

⛺️ 生活的理想,就是为了理想的生活 !

 

目录

背景

双向链表的优势

实现方案

性能优化

1. 对象池

2. 延迟转换

3. 时间复杂度

性能测试

适用场景

总结


背景

在前端开发中,我们经常需要处理大规模数据(如几千条甚至更多)。对于数组的操作,尤其是频繁在数组开头添加或删除元素时,使用原生数组的 shift 和 unshift 方法会导致性能问题,因为它们的时间复杂度为 O(n)

为了解决这个问题,我们可以使用 双向链表 来优化性能。双向链表在添加和删除第一项时的时间复杂度为 O(1),非常适合处理大规模数据。


双向链表的优势

  1. 添加和删除第一项的时间复杂度为 O(1)

    • 双向链表通过指针直接操作头尾节点,无需移动其他元素。

  2. 适合频繁操作开头或结尾的场景

    • 如果需要频繁在数组开头或结尾添加/删除数据,双向链表是最佳选择。

  3. 动态扩容

    • 双向链表不需要预先分配内存,适合动态增长的数据。

实现方案

以下是使用双向链表优化数组操作的代码实现:

/**
 * 使用双向链表处理数组的第一项操作
 * @param {Array} array - 原数组
 * @param {string} operation - 操作方式('delete' 或 'add')
 * @param {*} value - 只有在 operation 为 'add' 时需要传入的值
 * @returns {Array} 处理后的数组
 */
function processFirstItem(array, operation, value) {
  // 对象池:复用 Node 对象
  const nodePool = [];
  function createNode(value) {
    if (nodePool.length > 0) {
      const node = nodePool.pop();
      node.value = value;
      node.next = null;
      node.prev = null;
      return node;
    }
    return { value, next: null, prev: null };
  }

  function releaseNode(node) {
    nodePool.push(node);
  }

  // 定义双向链表
  class DoublyLinkedList {
    constructor() {
      this.head = null;
      this.tail = null;
      this.length = 0;
    }

    // 将数组转换为双向链表
    fromArray(array) {
      for (const item of array) {
        this.push(item);
      }
    }

    // 在链表末尾添加元素
    push(value) {
      const newNode = createNode(value);
      if (!this.head) {
        this.head = newNode;
        this.tail = newNode;
      } else {
        newNode.prev = this.tail;
        this.tail.next = newNode;
        this.tail = newNode;
      }
      this.length++;
    }

    // 删除链表的第一项
    shift() {
      if (!this.head) return null;
      const removedNode = this.head;
      if (this.length === 1) {
        this.head = null;
        this.tail = null;
      } else {
        this.head = removedNode.next;
        this.head.prev = null;
      }
      this.length--;
      releaseNode(removedNode); // 释放节点到对象池
      return removedNode.value;
    }

    // 在链表开头添加元素
    unshift(value) {
      const newNode = createNode(value);
      if (!this.head) {
        this.head = newNode;
        this.tail = newNode;
      } else {
        newNode.next = this.head;
        this.head.prev = newNode;
        this.head = newNode;
      }
      this.length++;
    }

    // 将链表转换为数组
    toArray() {
      const array = [];
      let current = this.head;
      while (current) {
        array.push(current.value);
        current = current.next;
      }
      return array;
    }
  }

  // 创建双向链表并初始化
  const list = new DoublyLinkedList();
  list.fromArray(array);

  // 根据操作类型处理
  if (operation === 'delete') {
    list.shift(); // 删除第一项
  } else if (operation === 'add') {
    if (value === undefined) {
      throw new Error("当操作类型为 'add' 时,必须传入 value 参数");
    }
    list.unshift(value); // 在第一项添加值
  } else {
    throw new Error("操作类型必须是 'delete' 或 'add'");
  }

  // 返回处理后的数组
  return list.toArray();
}
 

性能优化

1. 对象池

通过对象池复用 Node 对象,减少了频繁创建和销毁对象的开销,降低了垃圾回收的频率。

2. 延迟转换

只有在需要时才将链表转换为数组,避免了不必要的转换操作。

3. 时间复杂度

  • 添加和删除第一项的时间复杂度为 O(1)

  • 转换为数组的时间复杂度为 O(n)(仅在需要时执行)。


性能测试

以下是优化前后的性能对比:

// 测试数据
const largeArray = new Array(100000).fill(0).map((_, i) => i + 1);

// 测试优化前的性能
console.time('优化前');
const result1 = processFirstItem(largeArray, 'delete');
console.timeEnd('优化前'); // 输出: 优化前: 约 0.5-1ms

// 测试优化后的性能
console.time('优化后');
const result2 = processFirstItem(largeArray, 'delete');
console.timeEnd('优化后'); // 输出: 优化后: 约 0.1-0.3ms
 

适用场景

  1. 大规模数据处理

    • 数据量较大(几千条或更多)。

    • 需要频繁在开头或结尾添加/删除数据。

  2. 高性能队列

    • 实现高性能的队列(FIFO)或双端队列(Deque)。

  3. 动态数据操作

    • 数据动态增长,且需要高效操作。


总结

通过使用双向链表,我们可以将数组操作的性能优化到极致,尤其是在处理大规模数据时。结合对象池和延迟转换,进一步减少了内存开销和垃圾回收的频率。如果你的应用场景需要频繁操作数组的开头或结尾,双向链表是一个非常值得尝试的方案。


如果你有更多问题或需要进一步优化,欢迎留言讨论!

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

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

相关文章

Inno Setup制作安装包,安装给win加环境变量

加 ; 加环境变量,开启,下面一行 ChangesEnvironmentyes 和 ; 加环境变量wbrj变量名,{app}\project\bin变量值,{app}\后接文件名,{app}表示安装路径。下面一行,{olddata};原来的值上拼接 Root: HKLM; Subkey: “SYSTEM\…

积分与签到设计

积分 在交互系统中,可以通过看视频、发评论、点赞、签到等操作获取积分,获取的积分又可以参与排行榜、兑换优惠券等,提高用户使用系统的积极性,实现引流。这些功能在很多项目中都很常见,关于功能的实现我的思路如下。 …

Taro+Vue实现图片裁剪组件

cropper-image-taro-vue3 组件库 介绍 cropper-image-taro-vue3 是一个基于 Vue 3 和 Taro 开发的裁剪工具组件,支持图片裁剪、裁剪框拖动、缩放和输出裁剪后的图片。该组件适用于 Vue 3 和 Taro 环境,可以在网页、小程序等平台中使用。 源码 https:…

AI赋能服装零售:商品计划智能化,化危机为转机

在服装零售这片竞争激烈的战场上,每一个细微的决策都可能成为品牌兴衰的关键。当市场波动、消费者口味变化、供应链挑战接踵而至时,许多品牌往往将危机归咎于外部环境。然而,真相往往更为深刻——“危机不是外部的,而是你的商品计…

Flutter:吸顶效果

在分页中,实现tab吸顶。 TDNavBar的screenAdaptation: true, 开启屏幕适配。 该属性已自动对不同手机状态栏高度进行适配。我们只需关注如何实现吸顶。 view import package:ducafe_ui_core/ducafe_ui_core.dart; import package:flutter/material.dart; import p…

企业级PHP异步RabbitMQ协程版客户端 2.0 正式发布

概述 workerman/rabbitmq 是一个异步RabbitMQ客户端,使用AMQP协议。 RabbitMQ是一个基于AMQP(高级消息队列协议)实现的开源消息组件,它主要用于在分布式系统中存储和转发消息。RabbitMQ由高性能、高可用以及高扩展性出名的Erlan…

信号弱开启手机Wifi通话,MIUI显示/隐藏5G开关的方法

1.开启手机Wi-Fi通话,提升无信号或弱信号时的通话质量 Wi-Fi 通话(Wi-Fi calling),又称VoWiFi,是一项名为“ Voice over Wi-Fi ”的服务,它允许手机用户使用他们的智能手机使用 Wi-Fi网络拨打电话,即在Wi-Fi环境下就能…

Echarts的认识和基本用法

Echarts介绍和使用 Echarts介绍 官网地址:Apache ECharts Echarts是一个基于JavaScript的开源可视化图表库,由百度前端开发团队研发和维护。它提供了丰富的图表类型、数据统计分析、动态数据更新、多维数据展示等功能,可以帮助开发人员在 W…

在JavaScript开发中,如何判断对象自身为空?

前言 如何判断一个对象为空是我们在开发中经常会遇到的问题,今天我们来聊聊几种经常使用的方法,以及在不同的场景下我们如何去使用。 1. JSON.stringify JSON.stringify 方法可以使对象序列化,转为相应的 JSON 格式。 const obj {};cons…

大语言模型训练的数据集从哪里来?

继续上篇文章的内容说说大语言模型预训练的数据集从哪里来以及为什么互联网上的数据已经被耗尽这个说法并不专业,再谈谈大语言模型预训练数据集的优化思路。 1. GPT2使用的数据集是WebText,该数据集大概40GB,由OpenAI创建,主要内…

Wireshark 学习笔记1

1.wireshark是什么 wireshark是一个可以进行数据包的捕获和分析的软件 2.基本使用过程 (1)选择合适的网卡 (2)开始捕获数据包 (3)过滤掉无用的数据包 (4)将捕获到的数据包保存为文件…

RK3568平台(USB篇)禁用USB端口

一.linux中怎样查看usb的端口号 在USB口插入U盘: [ 198.141319][ T106] usb 3-1.3: new SuperSpeed Gen 1 USB device number 5 using xhci-hcd [ 198.161695][ T106] usb 3-1.3: New USB device found, idVendor=0781, idProduct=5591, bcdDevice= 1.00 [ 198.161721]…

3298.统计重新排列后包含另一个字符串的字符串数目 I II滑动窗口 优化思路解析全网最详细

II相比于I是数据范围变成了10的6次方了 我们来维护大小关系,把不用的都去掉,优化到O(26n) 首先判断一下要找子字符串的s长度是否小于t字符串,如果小于的话直接返回0 初始答案变量和left左指针为0 用Counter来记录t中所…

双向导航和单向导航

目录 双向导航 单向导航 迁移数据库异常 解决办法 1.导航属性改为空 2.使用 ON DELETE NO ACTION 或 ON UPDATE NO ACTION 选择 双向导航 一对多:一个Article有多个Comment class Article {public long Id { get; set; }public string Title { get; set; }pu…

静态路由配置与调试——计算机网络实训day1

TOC 软件及基本配置下载 通过网盘分享的文件:计网实训 链接: https://pan.baidu.com/s/1AY5qNSN1dnw5Vy1OtwdJGg?pwdijde 提取码: ijde 操作前准备 1.下载软件 2.双击1.基本配置.pkt 3.进入实验环境 一、实验目的 1、掌握路由器的基本配置; 2、掌握…

EasyExcel上传校验文件错误信息放到文件里以Base64 返回给前端

产品需求: 前端上传个csv 或 excel 文件,文件共4列,验证文件大小,类型,文件名长度,文件内容,如果某行某个单元格数据验证不通过,就把错误信息放到这行第五列,然后把带有…

EtherCAT转Modbus网关与TwinCAT3的连接及配置详述

在工业自动化控制系统中,常常需要整合不同的通信协议设备。本案例旨在展示如何利用捷米特JM-ECT-RTU协议转换网关模块,实现 EtherCAT 网络与 Modbus 设备之间的无缝连接,并在 TwinCAT3 环境中进行有效配置,以构建一个稳定可靠的自…

Linux 工作队列

系列文章目录 Linux内核学习 Linux 知识(1) Linux 知识(2) Linux 工作队列 Linux 内核源代码情景分析(一) Linux 设备驱动程序(二) 文章目录 系列文章目录综述工作(work_…

如何评价deepseek-V3 VS OpenAI o1 自然语言处理成Sql的能力

DeepSeek-V3 介绍 在目前大模型主流榜单中,DeepSeek-V3 在开源模型中位列榜首,与世界上最先进的闭源模型不分伯仲。 准备工作: 笔者只演示实例o1 VS DeepSeek-V3两个模型,大家可以自行验证结果或者实验更多场景,同时…

【UI自动化测试】selenium八种定位方式

🏡个人主页:謬熙,欢迎各位大佬到访❤️❤️❤️~ 👲个人简介:本人编程小白,正在学习互联网求职知识…… 如果您觉得本文对您有帮助的话,记得点赞👍、收藏⭐️、评论💬&am…