js链表学习,以及对比原生数组讨论有没有必要使用链表

js链表学习,以及对比原生数组讨论有没有必要使用链表

先看一下基本概念

摘自学习JavaScript数据结构与算法(第3版)-洛伊安妮·格罗纳

要存储多个元素,数组(或列表)可能是最常用的数据结构。这种数据结构非常方便,提供了一个便利的[]语法来访问其元素。然而,这种数据结构有一个缺点:**(在大多数语言中)数组的大小是固定的,从数组的起点或中间插入或移除项的成本很高,因为需要移动元素。(尽管我们已经学过,JavaScript有来自Array类的方法可以帮我们做这些事,但背后的情况同样如此。)**链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。
相对于传统的数组,链表的一个好处在于,添加或移除元素的时候不需要移动其他元素。然而,链表需要使用指针,因此实现链表时需要额外注意。在数组中,我们可以直接访问任何位置的任何元素,而要想访问链表中间的一个元素,则需要从起点(表头)开始迭代链表直到找到所需的元素。

下图展示了一个链表的结构

实现链表

先分析链表要实现的功能

❑ push(element):向链表尾部添加一个新元素。
❑ insert(element, position):向链表的特定位置插入一个新元素。
❑ getElementAt(index):返回链表中特定位置的元素。如果链表中不存在这样的元素,则返回undefined。
❑ remove(element):从链表中移除一个元素。
❑ indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1。
❑ removeAt(position):从链表的特定位置移除一个元素。
❑ isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
❑ size():返回链表包含的元素个数,与数组的length属性类似。
❑ toString():返回表示整个链表的字符串。由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值。

代码实现

// 定义链表节点结构
class Node {
  constructor(element, next) {
    this.element = element;
    this.next = next;
  }
}
// 定义默认的排序规则
export function defaultEquals(a, b) {
  return a === b;
}
// 实现链表
class LinkedList {
  constructor(equalsFn = defaultEquals) {
    this.equalsFn = equalsFn;
    this.count = 0;
    this.head = undefined;
  }
  push(element) {
    const node = new Node(element);
    let current;
    if (this.head == null) {
      // catches null && undefined
      this.head = node;
    } else {
      current = this.head;
      while (current.next != null) {
        current = current.next;
      }
      current.next = node;
    }
    this.count++;
  }
  getElementAt(index) {
    if (index >= 0 && index <= this.count) {
      let node = this.head;
      for (let i = 0; i < index && node != null; i++) {
        node = node.next;
      }
      return node;
    }
    return undefined;
  }
  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;
  }
  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;
  }
  remove(element) {
    const index = this.indexOf(element);
    return this.removeAt(index);
  }
  indexOf(element) {
    let current = this.head;
    for (let i = 0; i < this.size() && current != null; i++) {
      if (this.equalsFn(element, current.element)) {
        return i;
      }
      current = current.next;
    }
    return -1;
  }
  isEmpty() {
    return this.size() === 0;
  }
  size() {
    return this.count;
  }
  getHead() {
    return this.head;
  }
  clear() {
    this.head = undefined;
    this.count = 0;
  }
  toString() {
    if (this.head == null) {
      return '';
    }
    let objString = `${this.head.element}`;
    let current = this.head.next;
    for (let i = 1; i < this.size() && current != null; i++) {
      objString = `${objString},${current.element}`;
      current = current.next;
    }
    return objString;
  }
}

开始测试

这里从大量数据(十万)和少量数据(一百)两个维度来对比链表和数组的数据添加删除和查找所运行的时间,使用console.time 判断运行时间。还要排除类型化数组的影响,关于类型化数组与普通数组之间的关系参考https://juejin.im/entry/59ae664d518825244d207196 。简单来说同类型数组操作更快,不同类型数组操作慢一些,这里用慢的。

测试代码

let linkedList = new LinkedList();

let arr = [];
// 插入一个对象保证数组内部不是统一数据类型
arr.push({a:  'test'});
// 测试 插入
let count = 100;
let insertIndex = 50;
console.time('链表PUSH')
for(var i =0; i < count; i++){
  linkedList.push(i);
}
console.timeEnd('链表PUSH')

console.time('数组PUSH')
for(var i =0; i< count; i++){
  arr.push(i);
}
console.timeEnd('数组PUSH')


console.time('链表insert')
linkedList.insert('test',insertIndex)
console.timeEnd('链表insert')

console.time('数组insert')
arr.splice(insertIndex,0,'test')
console.timeEnd('数组insert')


console.time('链表remove')
linkedList.removeAt(insertIndex)
console.timeEnd('链表remove')

console.time('数组remove')
arr.splice(insertIndex,1)
console.timeEnd('数组remove')

耗时如下

可以看到在基数为100时,原生Array的性能都碾压链表

现在修改基数为100000

...
let arr = [];
// 插入一个对象保证数组内部不是统一数据类型
arr.push({a:  'test'});
- let count = 100;
- let insertIndex = 50;
+ let count = 100000;
+ let insertIndex = 50000;
console.time('链表PUSH')
for(var i =0; i < count; i++){
  linkedList.push(i);
}
console.timeEnd('链表PUSH')
...

耗时如下

可见无论是大量数据还是少量数据,原生Array的性能都遥遥领先,而且原生js 数组自带的API能满足链表需要的大部分功能,循环链表,双向链表,有序链表都能通过简单的变形实现。事实上工作中确实很少用到链表。

一点点想法: 通过JS来了解数据结构和算法,有点越看越糊涂的感觉,它太高级了,内存,指针这些操作都是一个大黑盒,感觉大学课本的C语言更合适呢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值