从零学习数据结构(4)- 认识链表

231 阅读3分钟

「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

对比链表和数组结构

链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同

数组

  • 要存储多个元素,数组可能是最常见的数据结构
  • 几乎每种语言都由默认实现数组结构

缺点:

  • 数组的创建通常需要申请一段连续的内存空间(一整块的内存),并且长度是固定的(在大部分编程语言中,不包括JS)。所以当数组不能满足容量要求时,需要进行扩容(一般是申请一个更大的数组,然后将原数组的元素复制过去)

  • 在数组的开头或中间位置,插入数据的成本很高,需要进行大量元素的位移

链表

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

  • 不同于数组,链表中的元素在内存中不必是连续的

  • 链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用组成

优点:

  • 内存空间不必是连续的。可以充分利用计算机的内存,实现灵活的动态内存管理

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

  • 链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组的效率高很多

缺点:

  • 链表访问任何一个位置的元素时,都需要从头开始访问(无法跳过第一个元素去访问后面的元素)。而数组可以通过下标值访问任意位置的元素

  • 无法通过下标直接访问元素,需要从头一个个访问,直到找到响应元素

什么是链表?(单向链表)

链表可以类比为火车,有一个火车头,火车头连接下一个车厢,车厢上有乘客(相当于是数据),这个车厢又连接了下一个车厢,依次类推。

示意图

链表可以类比为火车,有一个火车头,火车头连接下一个车厢,车厢上有乘客(相当于是数据),这个车厢又连接了下一个车厢,依次类推。

image.png

单向链表的常见操作

  • append(element):向列表尾部添加一个新的项

  • insert(position, element):向列表特定位置插入一个新的项

  • get(position):获取对应位置的元素

  • indexOf(element):返回元素在列表中的索引,如果没有,就返回 -1

  • update(position, element):修改某个位置的元素

  • removeAt(position):从列表的特定位置删除一项

  • remove(element):从列表中移除一项

  • isEmpty():判断链表是否为空

  • size():返回链表中的元素个数

  • toString()

封装单向链表对象

代码

class LinkedListItem {
  data = undefined;
  next = null;

  constructor(data) {
    this.data = data;
  }
}

class LinkedList {
  head = null;
  length = 0;

  append(data) {
    const newItem = new LinkedListItem(data);
    if (this.length === 0) {
      this.head = newItem;
    } else {
      let currentItem = this.head;
      while (currentItem.next) {
        currentItem = currentItem.next;
      }
      currentItem.next = newItem;
    }

    this.length += 1;
  }

  insert(position, data) {
    if (position < 0 || position > this.length) {return false;}
    const newItem = new LinkedListItem(data);

    if (position === 0) {
      newItem.next = this.head;
      this.head = newItem;
    } else {
      let currentItem = this.head;
      for (let i = 0; i < position - 1; i++) {
        currentItem = currentItem.next;
      }
      newItem.next = currentItem.next;
      currentItem.next = newItem;
    }

    this.length += 1;
    return true;
  }

  get(position) {
    if (position < 0 || position >= this.length) {return null;}

    let currentItem = this.head;
    for (let i = 0; i < position; i++) {
      currentItem = currentItem.next;
    }
    return currentItem.data;
  }

  indexOf(data) {
    let currentItem = this.head;
    for (let i = 0; i < this.length; i++) {
      if (data === currentItem.data) {
        return i;
      } else {
        currentItem = currentItem.next;
        if (currentItem === null) {return -1;}
      }
    }
  }

  update(position, newData) {
    if (position < 0 || position >= this.length) return;

    let currentItem = this.head;
    for (let i = 0; i < position; i++) {
      currentItem = currentItem.next;
    }
    currentItem.data = newData;
  }

  removeAt(position) {
    if (position < 0 || position >= this.length) return;

    let currentItem = this.head
    if( position === 0) {
      this.head = this.head.next
    } else {
      for(let i = 0; i < position - 1; i ++) {
        currentItem = currentItem.next
      }
      currentItem.next = currentItem.next.next
    }
    this.length -= 1
  }

  remove(data) {
    const index = this.indexOf(data)
    this.removeAt(index)
  }

  isEmpty() {
    return this.length === 0
  }

  size() {
    return this.length
  }

  toString() {
    let currentItem = this.head;
    let resultString = '';
    while (currentItem) {
      resultString += currentItem.data + ' ';
      currentItem = currentItem.next;
    }
    return resultString;
  }
}

测试代码

const linkedList = new LinkedList();
console.log(linkedList.isEmpty())                  // true
linkedList.append('abc');             
linkedList.append('cba');
linkedList.append('nba');
linkedList.append('ban');
console.log('linkedList', linkedList.toString());  // 'abc cba nba ban'
linkedList.update(2, '111')                        // 'abc cba'
console.log(linkedList.indexOf('cba'));            // 1
linkedList.insert(3, 'ccc')                       
linkedList.removeAt(3)       
console.log(linkedList.isEmpty())                  // false
console.log(linkedList.size())                     // 4
console.log(linkedList.remove('nba'))            
console.log('linkedList', linkedList.toString());  // 'abc cba 111 ban'
console.log(linkedList.get(2));                    // '111'