[JS与链表]双向链表

news2024/11/29 1:45:26

前言

阅读本文前请先阅读

[JS与链表]普通链表_AI3D_WebEngineer的博客-CSDN博客

ES6的Class继承

类的继承可以使用extends,让子类继承父类的属性和方法。

而在子类内部(构造函数constructor)必须调用super()实现继承(super()代表父类构造函数

class A {constructor() {this.name ='abc'}}
class B extends A {constructor() {super()}}
// -----------------------------------------
var b = new B() // {name:'abc'}
class A {constructor(userName) {this.name = userName}}
class B extends A {constructor(yourName) {super(yourName)}}
// ---------------------------------------------------
var b = new B('ddd') // {name:'ddd'}

双向链表

双向链表与普通链表的区别在于:普通链表的节点是链向下一个节点的(单向),双向链表的节点是链向上下两个节点的。

我们先从Node节点开始改造:

以下代码

class DoublyNode extends Node {
    #prev;
    constructor(ele,next,prev) {
        super(ele,next);
        this.prev = prev;
    }
}
class DoublyLinkedList extends LinkedList {
    constructor(equalFn=compareFn) {
        super(equalFn);
        this.tail = undefined
    }
}

新的DoublyLinkedList类的构造函数里会初始化equalsFn、head、count和tail四个属性

所谓双向链表就是可以由头到尾,也能由尾到头,甚至于在中间可以灵活转向(因为doublyNode节点有前后节点),而单向链表如果迭代时错过了要找的元素,则需要重新迭代。

通过下标获得链表节点

同链表的getNodeAt或者叫getElementAt方法(已继承)


getNodeAt(index) {
     if (index>0 && index >=this.count) {return undefined}
     if (this.index === 0) {return this.head}
     let currentNode = this.head;
     for (var i =0;i<index;i++) {
           currentNode = currentNode.next
     }
    return currentNode
}

在任意位置插入新节点

比链表多了一种场景。就是尾部插入。

创建新节点:

const node = new DubblyNode(element);

从开头插入:

①空链表

this.head = node;
this.tail = node;

②非空链表

var currentNode = this.head;

node.next = currentNode;
currentNode.prev = node;
this.head = node

从尾巴插入:

var currentNode = this.Tail;

currentNode.next = node;
node.pre = currentNode;
this.tail = node;

在中间插入:

const originNode = this.getNodeAt(index);
const preNode = this.getNodeAt(index - 1);
node.next = originNode;;
node.prev = preNode;
originNode.prev = node;
preNode.next = node;

整合:

insert(element,index) {
    if (index < 0 || index > this.count) {return false}
    const node = new DoublyNode(element);
    let current = this.head;
    // 开头插入
    if (index === 0) {
         // 链表为空
        if (this.head == null) {
            this.head = node;
            this.tail = node;
        }else {
           // 链表非空
            node.next = currentNode;
            currentNode.prev = node;
            this.head = node
        }
    }
    if (index === this.count) {
        // 最后一项
        currentNode = this.Tail;
        currentNode.next = node;
        node.pre = currentNode;
        this.tail = node;
    }
    // 在中间插入
    const originNode = this.getNodeAt(index);
    const prevNode = this.getNodeAt(index - 1);
    node.next = originNode;
    node.prev = prevNode;
    originNode.prev = node;
    prevNode.next = node;
    // 结束
    this.count ++;
    return true
}

根据下标移除元素

需要判断三种场景:

①移除头部

removeAt(index) {
    if (index < 0 || index > this.count || !this.count) {return undefined}
    let current = this.head;
    if (index === 0) {
        this.head = current.next;
        if (this.count === 1) {
            this.tail = undefined
        }
    }
    this.count --;
    return current.value
}
...
if (index === 0) {
        this.head = current.next;
        if (this.count === 1) {
           ...
        }else {
            this.head.prev = undefined
        }
 }
...

②移除尾部

...
if (index === this.count - 1) {
   current = this.tail;
   this.tail=  current.prev;
   this.tail.next = undefined;
 }
...

③正常移除

...
else  {
    current = this.getElementAt(index);
    const prevElement = current.prev;
    const nextElement = current.next;
    prevElement.next =  nextElement;
    nextElement.prev = prevElement;
}
...

整理一下:

 removeAt(index) {
    if (index >= 0 && index < this.count) {
      let current = this.head;
      if (index === 0) {
        this.head = this.head.next;
        // if there is only one item, then we update tail as well //NEW
        if (this.count === 1) {
          // {2}
          this.tail = undefined;
        } else {
          this.head.prev = undefined;
        }
      } else if (index === this.count - 1) {
        // last item //NEW
        current = this.tail;
        this.tail = current.prev;
        this.tail.next = undefined;
      } else {
        current = this.getElementAt(index);
        const previous = current.prev;
        // link previous with current's next - skip it to remove
        previous.next = current.next;
        current.next.prev = previous; // NEW
      }
      this.count--;
      return current.element;
    }
    return undefined;
  }

循环链表

循环链表有单向循环链表和双向循环链表。

单向循环链表和单向链表的区别在于最后一个元素的next不是指向undefined,而是指向了head。

双向循环链表是双向链表多了指向head元素的tail.next(原本是undefined)和指向tail元素的head.prev(原本是undefined)

单向循环链表CircularLinkedList实现:

CircularLinkedList类不需要任何额外的属性。直接扩展LinkedList类(单向链表)并覆盖插入方法和移除方法。

向单向循环链表中插入元素的逻辑和向单向链表中插入元素的逻辑是一样的。不同之处在于我们需要将循环链表尾部节点的next引用指向头部节点。

单向链表:

  insert(element, index) {
    if (index >= 0 && index <= this.count) {
      const node = new Node(element);
      if (index === 0) {
        const current = this.head;
        node.next = current;
        this.head = node;
      } else {
        const previous = this.getElementAt(index - 1);
        node.next = previous.next;
        previous.next = node;
      }
      this.count++;
      return true;
    }
    return false;
  }

循环单向链表:

insert(element, index) {
    if (index >= 0 && index <= this.count) {
      const node = new Node(element);
      let current = this.head;
      if (index === 0) {
        if (this.head == null) {
          // if no node  in list
          this.head = node;
          node.next = this.head;
        } else {
          node.next = current;
          current = this.getElementAt(this.size());
          // update last element
          this.head = node;
          current.next = this.head;
        }
      } else {
        const previous = this.getElementAt(index - 1);
        node.next = previous.next;
        previous.next = node;
      }
      this.count++;
      return true;
    }
    return false;
  }

其实就是多了在插入下标为0的情况处理

单向链表:

removeAt(index) {
    if (index >= 0 && index < this.count) {
      let current = this.head;
      if (index === 0) {
        this.head = current.next;
      } else {
        const previous = this.getElementAt(index - 1);
        current = previous.next;
        previous.next = current.next;
      }
      this.count--;
      return current.element;
    }
    return undefined;
 }

单向循环链表:

removeAt(index) {
    if (index >= 0 && index < this.count) {
      let current = this.head;
      if (index === 0) {
        if (this.size() === 1) {
          this.head = undefined;
        } else {
          const removed = this.head;
          current = this.getElementAt(this.size() - 1);
          this.head = this.head.next;
          current.next = this.head;
          current = removed;
        }
      } else {
        // no need to update last element for circular list
        const previous = this.getElementAt(index - 1);
        current = previous.next;
        previous.next = current.next;
      }
      this.count--;
      return current.element;
    }
    return undefined;
  }

详细代码见:链表代码汇总

有序链表

有序链表是指保持元素有序的链表结构。除了使用排序算法之外。我们还可以将元素插入到正确的位置来保证链表的有序性。

所以我们只需要在单向链表的基础上重写insert、push的逻辑,使得整个链表的插入变得有序。

insert(element, index) {
    if (index >= 0 && index <= this.count) {
      const node = new Node(element);
      if (index === 0) {
        const current = this.head;
        node.next = current;
        this.head = node;
      } else {
        const previous = this.getElementAt(index - 1);
        node.next = previous.next;
        previous.next = node;
      }
      this.count++;
      return true;
    }
    return false;
 }

原本的插入逻辑可以自定义插入的位置。但是有序插入只能通过计算得出插入下标。

为了提现有序,我们需要定义一套比较方法。

export const Compare = {
  LESS_THAN: -1,
  BIGGER_THAN: 1,
  EQUALS: 0
};
export function defaultCompare(a, b) {
  if (a === b) {
    return Compare.EQUALS;
  }
  return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN;
}
export default class SortedLinkedList extends LinkedList {
  constructor(equalsFn, compareFn = defaultCompare) {
    super(equalsFn);
    this.compareFn = compareFn;
  }
}

我们来看insert的改写:

  insert(element, index = 0) {
    if (this.isEmpty()) {
      return super.insert(element, index === 0 ? index : 0);
    }
    const pos = this.getIndexNextSortedElement(element);
    return super.insert(element, pos);
  }

insert方法为什么要给insert一个默认值0?

因为我们需要继承改写insert方法(super),所以我们不能改变继承的insert的传参结构。但是我们又不能让index生效。

getIndexNextSortedElement的方法是为了纠正插入元素的下标的

getIndexNextSortedElement(element) {
    let current = this.head;
    let i = 0;
    for (; i < this.size() && current; i++) {
      const comp = this.compareFn(element, current.element);
      if (comp === Compare.LESS_THAN) {
        return i;
      }
      current = current.next;
    }
    return i;
  }

同理,push也需要纠正一下push的下标

push(element) {
    if (this.isEmpty()) {
      super.push(element);
    } else {
      const index = this.getIndexNextSortedElement(element);
      super.insert(element, index);
    }
 }

完整代码见链表代码汇总

小结:

链表相比数组最重要的优点是无需移动链表中的元素,就能轻松添加和移除元素。当你需要频繁操作(删、增)元素的时候,最好的选择就是链表。

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

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

相关文章

基于MPSOC+C6678+高精度AD/DA的软件无线电处理平台

板卡概述 VPX_XM630 是一款基于6U VPX 总线架构的高速信号处理平台&#xff0c;该平台采用一片Xilinx 的Kintex UltraScale 系列FPGA&#xff08;XCKU115&#xff09;作为主处理器&#xff0c;完成复杂的数据采集、回放以及实时信号处理算法。采用一片带有ARM 内核的高性能嵌入…

k8s简单记录

进入pod中的某个容器并执行命令 # 进入pod中的busybox容器&#xff0c;查看文件内容 # 补充一个命令: kubectl exec pod名称 -n 命名空间 -it -c 容器名称 /bin/sh 在容器内部执行命令 # 使用这个命令就可以进入某个容器的内部&#xff0c;然后进行相关操作了 # 比如&#x…

【论文简述】Multi-View Stereo Representation Revisit: Region-Aware MVSNet(CVPR 2023)

一、论文简述 1. 第一作者&#xff1a;Yisu Zhang 2. 发表年份&#xff1a;2023 3. 发表期刊&#xff1a;CVPR 4. 关键词&#xff1a;MVS、3D重建、符号距离场 5. 探索动机&#xff1a;像素深度估计仍存在两个棘手的缺陷。一是无纹理区域的估计置信度较低。二是物体边界附…

一文读懂DNS解析原理和流程(中科三方)

什么是DNS域名解析 我们首先要了解域名和IP地址的区别。IP地址是互联网上计算机唯一的逻辑地址&#xff0c;通过IP地址实现不同计算机之间的相互通信&#xff0c;每台联网计算机都需要通过IP地址来互相联系和分别。 但由于IP地址是由一串容易混淆的数字串构成&#xff0c;人们很…

awk指令的详细指南

目录 工作原理 命令格式 awk常见的内建变量&#xff08;可直接用&#xff09;如下所示 按行输出文本 按字段输出文本 通过管道、双引号调用 Shell 命令 示例 CPU使用率 数组 ​编辑统计文件的内容出现的次数 使用awk 统计secure 访问日志中每个客户端IP的出现次数? …

云上的二维设计原来是这样的!

今天与大家探索云上的二维设计&#xff0c;3DEXPERIENCE DraftSight基于云平台实现与云端进行连接&#xff0c;实现一定的云上协作&#xff0c;提升绘图工作效率&#xff0c;我们从以下三方面来进行说明&#xff1a; 01&#xff1a;DraftSight设计 02&#xff1a;Revision变更…

ECharts 快速入门

文章目录 1.1 ECharts介绍1.2 vue使用ECharts1&#xff09;vscode打开测试工程2) 工程安装echarts依赖3) 配置echarts4) vue组件使用echarts5) 页面效果&#xff1a; 1.3 项目中 ECharts 的使用1) 配置和使用流程说明2) 前端显示效果 1.1 ECharts介绍 ECharts是百度开发的一个…

JavaScript 循环方法

JavaScript 循环方法 不涉及到具体绑定到 prototype 上的循环方式&#xff0c;即 XXXXX.prototype 中包含的循环方式&#xff08;如 forEach, map&#xff09;。 for for 总共有三种循环方式&#xff0c;一个是传统的 for 循环体&#xff0c;一个是 for in&#xff0c;还有一…

微信最新版本解除【文件只读】

问题 某一天开始&#xff0c;微信自动升级到3.9版本&#xff0c;最大的改变就是接收到的文件是只读属性&#xff0c;网上目前有两个办法&#xff0c;1.降到3.8甚至更早版&#xff1b;2.将version.dll补丁文件复制到微信安装目录&#xff0c;但3.9.2版本就不能用了。 解决办法…

软件测试之测试用例的设计

1. 测试用例的概念 软件测试人员向被测试系统提供的一组数据的集合&#xff0c;包括 测试环境、测试步骤、测试数据、预期结果 2. 为什么在测试前要设计测试用例 测试用例是执行测试的依据 在回归测试的时候可以进行复用 是自动化测试编写测试脚本的依据 衡量需求的覆盖率…

完全了解FPC柔性电路板,生产到市场全讲解

1.什么是FPC 随着社会的不断进步&#xff0c;电子行业的不断更新换代&#xff0c;传统的PCB已经不能满足所有电子产品的需求&#xff0c;FPC的市场需求也越来越大&#xff0c;有很多朋友还不是很清楚FPC是什么&#xff0c;下面来简单的介绍一下: FPC全称&#xff1a;柔性印制电…

利用Springboot来驱动开发桌面程序

众所周知&#xff0c;SpringBoot是一款强大的Javaweb开发程序&#xff0c;这得益于其构造了一个Spring容器&#xff0c;然后通过依赖注入和控制反转&#xff0c;维护起一套Java对象和实例的管理机制&#xff0c;方便开发者去使用。在web应用开发的应用中&#xff0c;Springboot…

python 的垃圾回收机制

一、 引入 python解释器在执行到定义变量的语法时&#xff0c;会申请内存空间来存放变量的值&#xff0c;而内存的容量是有限的&#xff0c;这就涉及到变量值所占用内存空间的回收问题&#xff0c;当一个变量值没有用了&#xff08;简称垃圾&#xff09;就应该将其占用的内存给…

【Linux】IO多路转接-poll

文章目录 I/O多路转接-pollpoll初识poll函数poll的小测试-监控标准输入poll服务器poll_server.cc poll的优点poll的缺点 I/O多路转接-poll poll初识 poll也是系统提供的一个多路转接接口, poll系统调用也可以让我们的程序同时监视多个文件描述符上的事件是否就绪,和select的定…

DDos攻击概述

1.def&#xff1a; 通过大规模互联网流量淹没目标服务器或其周边基础设施&#xff0c;以破坏目标服务器、服务或网络正常流量的恶意行为 目标服务器类比作商店&#xff1b; 网络的正常流量类比作顾客&#xff1b; 此恶意行为便相当于让一堆小混混装成正常顾客涌入商店&…

软件自动化测试有什么优势?自动化测试框架有哪些?

一、 软件自动化测试的优势 在软件测试过程中&#xff0c;自动化测试不断被提高到更高的级别&#xff0c;以提高测试效率以及降低测试成本。 1.节省时间和成本 手动测试需要耗费大量的时间和精力&#xff0c;而自动化测试可以在较短时间内执行多次测试&#xff0c;并且可以在…

Alibaba Sentinel整合SpringBoot,为微服务保驾护航!

前言 随着微服务的流行&#xff0c;服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件&#xff0c;主要以流量为切入点&#xff0c;从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来…

08-05 应用层设计

伸缩性的架构设计——服务器集群的伸缩性 DNS负载均衡 DNS服务器将访问的域名转发到对应的网关&#xff0c;网关层做反向代理。 利用消息组件对业务场景进行解耦 适合用消息组件解耦的场景 长任务&#xff08;时间长&#xff0c;逻辑复杂&#xff0c;可异步&#xff09…

React Antv G2Plot 「指标拆解图」 前端可视化实战 实现渲染、重置、筛选功能

背景 实现对指定数据的「指标拆解图」 渲染&#xff0c;并且可以根据筛选项进行变化。 任务分解 antv 的图表&#xff0c;以及请求后端的载荷对传入的数据结构有严格要求 一个工具函数将后端接口返回的数据格式化成 antv 图表要求的格式一个工具函数将前端提交的请求数据格…

Copilot入门

文章目录 简介安装初试快捷键取消订阅参考文献 简介 Copilot 是一款 GitHub 和 OpenAI 合作开发的 AI 结对编程工具&#xff0c;支持 Visual Studio、Neovim、VS Code、JetBrains IDEs&#xff0c;用于自动补全代码。 本文以 Python PyCharm 为例。 安装 GitHub Copilot&am…