数据结构——链表之单向链表

207 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第7天,点击查看活动详情

什么是链表

相对数组来说,链表是相对复杂的一种数据结构。

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

链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的应用(有些语言称为指针)组成。

数组和链表的差异

相对于数组,链表的一些优点:

  • 内存空间不是必须连续的,可以充分利用计算机的内存,实现灵活的内存动态管理
  • 链表不必在创建时就确定大小,并且大小可以无限的延伸下去
  • 链表在插入和删除数据时,事件复杂度可以达到O(1),相对数组效率高很多。

相对于数组,链表的缺点:

  • 链表访问任何一个位置的元素时,都需要从头开始访问(无法跳过第一个元素访问任何一个元素)
  • 无法通过下标直接访问元素,需要从头一个个访问,直到找对对应的元素。

链表图例说明

类似于一个火车:有一个火车头,火车头会连接一个节点,节点上有乘客(类似于数据),并且这个节点会连接下一个节点,以此类推。

image.png

上图展示的是一个单向的链表。

  • 只能从头遍历到尾或者从尾遍历到头
  • 也就是链表相连的过程是单向的
  • 实现的原理就是上一个节点中有一个指向下一个节点的引用。

但单向链表有一个比较明显的缺点:

我们可以容易的到达下一个节点,但是回到上一个节点时很难的。但是,在实际开发中还是会经常需要回到上一个节点的情况。

实现一个链表

基本结构

// 封装链表类
  function LinkedList() {
    // 内部的类:节点类
    function Node(data) {
      this.data = data;
      this.next = null;
    }
    this.head = null;
    this.length = 0;  // 链表的长度
  }
  • 封装LinkedList的类,用于表示我们的链表结构
  • 在LinkedList类中有一个Node类,用于封装每一个节点上的信息(和优先级队列的封装一样)
  • 链表中我们保存两个属性,一个是链表的长度,一个是链表中的第一个节点

其他常见操作

  • append(element): 向列表尾部添加一个新的元素
  • insert(postion, element): 向列表的特定位置插入一个新的元素
  • get(position):获取对应位置的元素
  • indexOf(element): 返回元素在列表中的索引,如果列表中没有该元素则返回-1
  • update(position): 修改某个位置的元素
  • removeAt(position): 从列表的特定位置移除一项
  • remove(element): 从列表中移除一项
  • isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false
  • size():返回链表包含的元素个数。与数组的length属性类似
  • toString():由于列表项使用了Node类,就需要重写继承自JS对象默认的toString方法,让其只输出元素的值。

append方法

向链表尾部追加数据可能有两种情况:

  • 链表本身是空,新添加的数据是唯一的节点。
  • 链表不为空,需要向其他节点后面追加节点。
 LinkedList.prototype.append = function (data) {
      var newNode = new Node(data);
      if (this.length === 0) {
        this.head =newNode
      } else {
        var current = this.head;
        while (current.next) {
          current = current.next;
        }
        current.next = newNode;
      }
      this.length += 1;
    }

toString方法

该方法比较简单,主要是获取每一个元素, 从head开头,因为获取链表的任何元素都必须从第一个节点开头, 循环遍历每一个节点,并且取出其中的element,拼接成字符串。将最终字符串返回

LinkedList.prototype.toString = function () {
      var current = this.head;
      var listStr = '';
      while (current) {
        listStr += current.data + " ";
        current = current.next;

      }
      return listStr
    }

insert方法

在任意位置插入数据

  • 添加到第一个位置

    • 添加到第一个位置,表示新添加的节点时头,就需要将原来的头节点,作为新节点的next
    • 另外这个时候的head应该指向新节点
  • 添加到其他位置

    • 如果是添加到其他位置,就需要先找到这个节点位置了
    • 我们通过while循环,一点点向下找,并且在这个过程中保存上一个节点和下一个节点
    • 找到正确的位置后,将新节点的next指向下一个节点,将上一个节点的next指向新的节点。
LinkedList.prototype.insert = function (position, data) {
      if (position < 0 || position > this.length) return false;
      var newNode = new Node(data);
      if (position === 0) {
        newNode.next = this.head;
        this.head = newNode;
      } else {
        var index = 0;
        var current = this.head;
        var previous = null;
        while(index++ < position) {
          previous = current;
          current = current.next;

        }
        newNode.next = current;
        previous.next = newNode;
      }
      this.length += 1;
      return true;
    }

get方法

返回对应位置的元素

LinkedList.prototype.get = function(position) {
      // 1.越级判断
      if (position < 0 || position >= this.length) return null;
      // 2.获取对应的data
      var current = this.head;
      var index = 0;
      while (index++ < position) {
        current = current.next;
      }
      return current.data
    }

indexOf

返回元素在链表中的索引,如果没有就返回-1.

 LinkedList.prototype.indexOf = function(data) {
      var index = 0;
      var current = this.head;

      while(current) {
        if (current.data === data) {
          return index;
        }
        current = current.next;
        index += 1;
      }

      return -1;
    }

update方法

修改某个位置的元素

LinkedList.prototype.update = function (position, data) {
      if (position < 0 || position>= this.length) return false;
        // 2.获取对应的data
      var current = this.head;
      var index = 0;
      while (index++ < position) {
        current = current.next;
      }
      current.data = data;
      return true;
    }

removeAt方法

从链表的特定位置移除一项

LinkedList.prototype.removeAt = function (position) {
    if (position < 0 || position>= this.length) return false;
      // 判断是否删除的第一个节点
        var current = this.head;

      if(position === 0) {
        this.head = this.head.next;
      } else {
        var index = 0;
        var previous = null;
        while (index ++ < position) {
          previous = current;
          current = current.next;
        }
        previous.next =current.next;
      }
      this.length -= 1;
      return current.data;
  }

remove

从列表中移除一项

LinkedList.prototype.remove = function (data) {
      // 获取data在列表中位置
      var position = this.indexOf(data);
      // 根据位置,删除节点
      return this.removeAt(position);

    }

isEmpty

判断是否是空链表

LinkedList.prototype.isEmpty = function (data) {
      return !this.length;
    }

size

获取链表的长度

LinkedList.prototype.size = function (data) {
    return this.length;
    }

总结

本篇文章,学习了链表中的一种单向链表。链表作为一种非常基础的数据结构,在实际开发中是很常用的。

相对于数组,链表更适合插入、删除操作频繁的场景,查询的话就不太适合了。