【数据结构】JavaScript列表实现

List,即线性表,也叫做列表。有以下存储结构:

  1. 顺序存储方式:数组
  2. 链式存储方式:单向链表、双向链表、单向循环链表、双向循环链表

在JavaScript数据结构中,一直缺少List这种常用的数据结构。这里通过对比其他语言的数据结构,比如java的ArrayList、LinkedList等源码,通过顺序存储方式、链式存储方式实现List这一功能。

一、数组方式

1.1、数据结构优缺点

定义:数组(Array)是有序的元素序列

优点:读取效率极高

缺点:插入、删除效率极低(需要移动该节点后续所有数据)

1.2、功能实现

参照java sdk的ArrayList源码,简单实现JavaScript版的ArrayList数据结构。

通过对ArrayList源码查看,我们发现其本质也是数组的存储结构

image-20220127202646857

对于add方法的实现,则是在数组末尾追加

image-20220127202810611

按照jdk的思路,我们初步定义ArrayList数据结构如下

class ArrayList {
  constructor() {
    this.list = [];
  }

  // 大小
  size() {
    return this.list.length;
  }

  // 新增元素
  add(object) {
    this.list[this.list.length] = object;
  }

  // 移除指定元素
  remove(object) {
    let removeIndex = this.list.indexOf(object);
    if (removeIndex !== -1) {
      this.list.splice(removeIndex, 1);
    }
  }

  // 按下标移除元素
  removeAtIndex(index) {
    if (this.list.length > index) {
      this.list.splice(index, 1);
    }
  }

  // 清空
  clear() {
    this.list = [];
  }

  // 查找
  get(index) {
    if (this.list.length > index) {
      return this.list[index];
    } else {
      return null;
    }
  }
}

1.3、验证

简单测试,符合逾期无bug

image-20220127203128891

二、单向链表方式

2.1、数据结构优缺点

定义:a1是a2的前驱,ai+1 是ai的后继,a1没有前驱,an没有后继

n为线性表的长度 ,若n==0时,线性表为空表

优点:增加、删除效率极高

缺点:读取效率低,特别是逆向读取效率极低

2.2、功能实现

在js、java中我们以对象代替指针,首先我们需要定义节点数据模型

class Node {
  constructor(item, next) {
    this.item = item;
    this.next = null;
  }
}

然后提供OneWayList为单向链表的数据结构,提供增删查改等功能

// 单向链表
class OneWayList {
  constructor() {
    this.headNode = new Node(null, null);//头指针
    this.size = 0;//链表长度
  }

  // 在链表末尾追加
  add(object) {
    let node = this.headNode;
    for (let i = 0; i < this.size; i++) {
      node = node.next;
    }
    let newNode = new Node(object, null);
    node.next = newNode;
    this.size = this.size + 1;
  }

  // 修改
  set(index, object) {
    let node = this.headNode;
    node = node.next;
    if (index < this.size && index > -1) {
      for (let i = 0; i < index; i++) {
        node = node.next;
      }
      node.item = object;
    }
  }

  // 移除指定index的数据、或移除最后一个数据
  remove(index = this.size - 1) {
    let node = this.headNode;
    if (index < this.size && index > -1) {
      for (let i = 0; i < index; i++) {
        node = node.next;
      }
      //断开中间数据
      node.next = node.next?.next;
      this.size = this.size - 1;
    }
  }

  // 链表长度
  size() {
    return this.size;
  }

  // 清空单向链表
  clear() {
    this.headNode = new Node(null, null);
    this.size = 0;
  }

  // 列表转字符串
  toArray() {
    let arrayList = [];
    let node = this.headNode;
    for (let i = 0; i < this.size; i++) {
      node = node.next;
      arrayList[arrayList.length] = node.item;
    }
    return arrayList;
  }
}

2.3、验证

对上述数据结构做简单的测试,符合预期

const temp = new OneWayList();
temp.add('第一个');
temp.add('第二个');
temp.add('第三个');
temp.add('第四个');
temp.set(1, '修改第二个');
temp.remove(2)
console.log(temp.toArray())
//[ '第一个', '修改第二个', '第四个' ]

三、双向链表方式

3.1、数据结构优缺点

定义:双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。不过一般我们都构造双向循环链表

优点:增加、删除效率较高

缺点:读取效率较低

3.2、功能实现

这里需要注意的是对链表的处理,需要同时处理前驱指针与后继指针,稍微麻烦点

由于是双向非循环链表,

1,当size为0时,头指针的前驱与后继皆为空;

2,当size非0时,头指针的前驱为空,后继非空。最后一个指针的前驱非空,后继为空

class Node {
  constructor(item, next, prev) {
    this.item = item;
    this.next = next;
    this.prev = prev;
  }
}

// 单向链表
class LinkedList {
  constructor() {
    this.headNode = new Node(null, null, null);//头指针
    this.size = 0;//链表长度
  }

  // 在链表末尾追加
  add(object) {
    let node = this.headNode;
    for (let i = 0; i < this.size; i++) {
      node = node.next;
    }
    // 添加前驱指针
    let newNode = new Node(object, null, node);
    // 添加后继指针
    node.next = newNode;
    this.size = this.size + 1;
  }

  // 修改
  set(index, object) {
    let node = this.headNode;
    node = node.next;
    if (index < this.size && index > -1) {
      for (let i = 0; i < index; i++) {
        node = node.next;
      }
      node.item = object;
    }
  }

  // 移除指定index的数据、或移除最后一个数据
  remove(index = this.size - 1) {
    let node = this.headNode;
    if (index < this.size && index > -1) {
      for (let i = 0; i < index + 1; i++) {
        node = node.next;
      }
      //断开前驱指针
      if (node.prev != null) {
        node.prev.next = node.next;
      }

      //断开后继指针
      if (node.next != null) {
        node.next.prev = node.prev;
      }

      this.size = this.size - 1;
    }
  }

  // 链表长度
  size() {
    return this.size;
  }

  // 清空单向链表
  clear() {
    this.headNode = new Node(null, null, null);
    this.size = 0;
  }

  // 列表转字符串
  toArray() {
    let arrayList = [];
    let node = this.headNode;
    for (let i = 0; i < this.size; i++) {
      node = node.next;
      arrayList[arrayList.length] = node.item;
    }
    return arrayList;
  }
}

3.3、验证

简单的验证

const temp = new LinkedList();
temp.add('第一个');
temp.add('第二个');
temp.add('第三个');
temp.add('第四个');
temp.set(1, '修改第二个');
temp.remove(3)
console.log(temp.toArray())
// [ '第一个', '修改第二个', '第三个' ]

四、双向循环链表方式

4.1、数据结构优缺点

双向循环链表是单向循环链表的每个结点中,再设置一个指向其前驱结点的指针域

对于空的双向循环链表,则是收尾相连

优点:增加、删除效率较高

缺点:读取效率低

4.2、功能实现

由于是双向循环链表,

1,当size为0时,头指针的前驱与后继皆为本身;

2,当size非0时,头指针的前驱为最后一个元素,最后一个元素后继为头指针

3,另外在循环查找元素时候,可根据查找路径灵活使用prev与next指针

class Node {
  constructor(item, next, prev) {
    this.item = item;
    this.next = next;
    this.prev = prev;
  }
}

// 双向循环链表
class LinkedList {
  constructor() {
    this.headNode = new Node(null, null, null);//头指针
    this.headNode.next=this.headNode;
    this.headNode.prev=this.headNode;
    this.size = 0;//链表长度
  }

  // 在链表末尾追加
  add(object) {
    let node = this.headNode;
    for (let i = 0; i < this.size; i++) {
      node = node.next;
    }
    // 添加新节点的前驱指针、后继指针为头指针
    let newNode = new Node(object, this.headNode, node);
    // 添加后继指针
    node.next = newNode;
    // 头指针的前驱为新节点
    this.headNode.prev=newNode;
    this.size = this.size + 1;
  }

  // 修改
  set(index, object) {
    let node = this.headNode;
    node = node.next;
    if (index < this.size && index > -1) {
      for (let i = 0; i < index; i++) {
        node = node.next;
      }
      node.item = object;
    }
  }

  // 移除指定index的数据、或移除最后一个数据
  remove(index = this.size - 1) {
    let node = this.headNode;
    if (index < this.size && index > -1) {
      for (let i = 0; i < index + 1; i++) {
        node = node.next;
      }
      //断开前驱指针
      if (node.prev != null) {
        node.prev.next = node.next;
      }

      //断开后继指针
      if (node.next != null) {
        node.next.prev = node.prev;
      }

      this.size = this.size - 1;
    }
  }

  // 链表长度
  size() {
    return this.size;
  }

  // 清空单向链表
  clear() {
    this.headNode = new Node(null, this, this);//头指针
    this.size = 0;
  }

  // 列表转字符串
  toArray() {
    let arrayList = [];
    let node = this.headNode;
    for (let i = 0; i < this.size; i++) {
      node = node.next;
      arrayList[arrayList.length] = node.item;
    }
    return arrayList;
  }
}

4.3、验证

简单的验证

const temp = new LinkedList();
temp.add('第一个');
temp.add('第二个');
temp.add('第三个');
temp.add('第四个');
temp.set(1, '修改第二个');
temp.remove(3)
console.log(temp.toArray())
//[ '第一个', '修改第二个', '第三个' ]

有偏薄之处欢迎指正

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流星雨在线

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值