数据结构-单链表的学习

161 阅读4分钟

「「这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战」(juejin.cn/post/702364… "juejin.cn/post/702364…")

链表和数组一样,都用于存储数据,但是他俩的存储机制却不相同。

一、认识链表

在开始学习单链表之前,我们先要知道什么是链表?它和数组的区别到底在哪里?

1. 数组
  • 数组的创建大多数需要申请一段连续的内存空间,且大小是固定的(大多数编程语言都是固定的),如果当前数组不满足容量大小,需要扩容(一般情况下是申请一个更大的数组, 比如2倍. 然后将原数组中的元素复制过去)。

  • 当我们想要再数组首位i或者中间插入一个新的元素时,需要大量元素位移。(尽管JSArray提供的方法能够帮助我们完成,但是背后的远离依旧是大量元素位移)

2. 链表
  • 不同于数组的存储机制,链表不需要申请连续的内存空间,且不需要再创建的时候就确定大小。
  • 链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者链接)组成。
  • 链表在插入和删除数据时, 时间复杂度可以达到O(1). 相对数组效率高很多。
3. 链表的缺点
  • 链表访问任何一个位置的元素时, 都需要从头开始访问.(无法跳过第一个元素访问任何一个元素)。
  • 无法通过下标直接访问元素, 需要从头一个个访问, 直到找到对应的问题。

二、熟悉单链表

在开始封装之前,我们再来熟悉一下链表的结构。

链表的结构类似于我们生活中的火车,一个火车后后面跟着一节一节的火车箱子,如下图。

在这里插入图片描述 在这里插入图片描述 这里的head就是链表的头部,node是每个节点,每个节点包括节点元素本身和指向下一个元素的引用。

三、封装单链表

尝试自己封装一个单链表

1. 创建一个Node节点用来保存节点信息
function Node(element) {
    this.element = element; //节点信息
    this.next = null; //节点指针
}
2. 封装一个单链表
//封装一个单链表
function SingleLink(element) {
    this.head = null;   //链表的第一个元素
    this.length = 0;    //链表的长度

    //操作的方法
    this.append = append;    //向链表的尾部追加元素
    this.print = print;    //打印当前的列表,仅供测试使用
}
3. 测试方式
function print() {
    let current = this.head;
    while (current != null) {
        console.log(current.element);
        current = current.next
    }
}
4. 向链表尾部追加元素:append(element)

追加元素可能有两种情况:

  1. 链表本身为空,追加的元素是唯一的节点。
  2. 链表本身不为空,需要向原链表最后一位追加元素。

实现思想:

  1. 将需要传入的心数据生成Node节点
  2. 判断链表本身是否为空,如果是空链表,直接将链表第一位设置为新节点
  3. 如果链表本身不是空链表,那就需要通过循环找到链表的最后一位,将最后一位的next指向新节点
  4. 将链表的长度+1
function append(element) {
    let node = new Node(element);
    if (this.head === null) {
        this.head = node;
    } else {
        let current = this.head;
        while (current.next) {
            current = current.next
        }
        current.next = node
    }
    this.length++
}
5. 向列表的特定位置插入一个新的项: insert(position, element)

向链表的指定位置添加元素有两种情况:

  1. positon的值小于0,或者position的值大于链表长度
  2. 可以找到插入的位置进行插入元素

实现思想:

  1. 首先判断是否有可以插入位置,position小于0或者大于数据长度直接返回,无法插入。
  2. 判断是否要插入链表的第一位,如果position等于0,将链表的第一位替换成新节点
  3. 添加到非第一位的其他位置,需要通过循环找到要插入的位置,将插入位置的上一个next指向新节点,新节点的next指向原节点的下一个位置。
  4. 最后将链表长度+1
function insert(position, element) {
    if (position < 0 || position > this.length) return false;

    let node = new Node(element);
    let current = this.head;
    let previous = null;
    let index = 0;
    if (position === 0) {
        this.head = node;
        node.next = current;
    } else {
        while(index<position){
            previous = current;
            current = current.next;
        }
        previous.next = node;
        node.next = current;
        return true
    }
    this.length++
}
6. 通过位置移出指定项:removeAt(positon)

实现思想:

  1. 首先判断是否越界:positon小于0和大于链表长度,无法处理返回false
  2. positon等于0,删除链表的第一位,直接将head指向第二个值即可
  3. 不满足以上条件,将指定节点的上一位指向下一位即可
  4. 将链表的长度减1
function removeAt(element){
    if(position < 0 || position > this.length) return false
    let current = this.head;
    let previous = null;
    let index = 0;
    if(position === 0){
        this.head = current.next;
    }else{
        while(index<position){
            previous = current;
            current == current.next
        }
        previous.next = current.next;
    }
    this.length--;
    return current.element;  //返回被删除的数据
}
7. 获取元素位置:indexOf(element)

实现思想:

  1. 设置一个临时变量index用来返回查找位置
  2. 循环整个链表找到节点element和指定element相等的值
  3. 如果没有找到指定的element,和Array放噶一样,返回-1
function indexOf(element){
    let current = this.head;
    let index = 0;
    while(current){
        if(current.element === element){
            return index
        }
        index++;
        current = current.next;
    }
    return -1;
}
8. 删除指定元素:remove(element)

实现思想:

  1. 通过indexOf(element)方法找到要删除的项的位置
  2. 最后通过removeAt(positon)方法删除
function remove = function (element) {
    let index = this.indexOf(element)
    return this.removeAt(index)
}