JavaScript数据结构与算法——链表(单)

109 阅读3分钟

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

为什么要使用链表?

存储多个元素,数组可能是最常用的一种数据结构,但是数组有两个缺点:

  • 在数组的开头或者中间插入/删除数据,需要大量的元素后移,内存开支较大。
  • 数组在声明时就确定了一块大小固定且空间连续的内存空间,这块空间如果用不完,就会造成浪费;如果不够用,就会进行扩容处理,翻倍扩大数组的容量。

为了解决这两个数组的痛点,引出了“链表”这种数据结构。

优点:

  • 链表不必在创建时就确定大小。
  • 内存空间不必是连续的,可以充分的利用计算机的内存,实现灵活的内存动态管理。
  • 链表在插入和删除数据时,时间复杂度很低,最低可达到O(1)

缺点:

  • 无法通过下标访问元素,也就是访问任意地方的元素,都需要按照链表顺序,从头开始顺延下去。

image.png

链表类的封装

function linkedList(){
    // head记录头指针
    this.head=null

    // 链表不同于数组,长度需要自己手动记录
    this.length=0

    // 封装一个内部类,记录节点
    function Node(data){
        // data保存节点的信息
        this.data=data

        // next保存指针,也就是如何到下一个节点
        this.next=null
    }
}

toString()

重写一下tostring方法,方便查看链表里的内容。

如果不重写toString直接打印,则是这种情况

image.png

// toString()
LinkedList.prototype.toString = function () {
    let current = this.head
    let listString = ''
    
    // 对当前节点是否存在进行判断
    while (current) {
        current.next
            ? listString += current.data + '-'
            : listString += current.data
        current = current.next
    }
    return listString
}

append()

在链表的最后增加一个节点

// 链表追加方法
LinkedList.prototype.append = function (data) {
    // 创建新的节点
    const newNode = new Node()
    
    // 判断原链表是否为空
    // 为空,直接更改头指针的指向
    if (this.length == 0) this.head = newNode
    // 不为空,则找到最后一个节点,让其指针指向新节点
    else {
        const current = this.head
        
        // 如果节点的指向不为undefined,则current变成下个节点
        while (current.next) current = current.next
        current.next = newNode
    }
    // 链表长度+1
    this.length++
}

insert()

向链表的指定位置插入元素。需要传入位置参数和data参数。

// 链表插入
LinkedList.prototype.insert = function (position, data) {
    // 对要插入的位置进行一个越界判断
    if (position < 0 || position > this.length) return false
    const newNode = new Node(data)
    
    // 如果插入的位置在第0项,则要修改this.head的指向
    if (position == 0) {
        current.next = this.head
        this.head = current
    }
    else {
        // 如果不是,则通过循环找到要插入的位置,
        // 将前一个节点的指针指向新节点
        // 将新节点的指针指向原来位置的节点
        let previous = null// 记录前一个节点
        let current = this.head
        let index = 0// 作为找到position的循环判断媒介
        while (index++ < position) {
            previous = current
            current = current.next
        }
        previous.next = newNode
        newNode.next = current
    }
    // 注意长度+1
    this.length++
    return true
}

get()

传入一个位置参数,获取该位置处的元素内容。

// 根据位置获取节点内容
LinkedList.prototype.get = function (position) {
    // 越界判断
    if (position < 0 || position >= this.length) return null
    let current = this.head
    let index = 0
    
    // 通过循环找到position处的节点
    while (index++ < position) current = current.next
    return current.data
}

indexOf()

传进去一个代表内容的参数,返回该内容在链表中第一次出现的位置。

// 根据内容查找第一次出现的位置
LinkedList.prototype.indexOf = function (data) {
    let current = this.head
    let index = 0// 记录位置信息
    while (current) {
        if (current.data == data) return index
        current = current.next
        index++
    }
    return -1//找不到就返回“-1”
}

update()

传入两个参数,一个是要更新的元素的位置,一个是更新的内容。

循环到要更新的元素位置后,更改元素的内容。

// 更新元素
LinkedList.prototype.update = function (position, content) {
    if (position < 0 || position >= this.length) return false
    let index = 0
    let current = this.head
    
    // 当找到节点后,修改当前节点的data
    while (index++ < position) current = current.next
    current.data = content
    return true
}

removeAt

传入一个位置参数,删除该位置的元素。

// 从链表某一位置移除一项元素
LinkedList.prototype.removeAt = function (position) {
    if (position < 0 || position >= this.length) return false
    let current = this.head // 保存当前项元素
    let previous = null // 保存当前项元素的上一项元素
    
    // 当移除的是第一项元素时,修改头指针的指向
    if (position == 0) this.head = current.next
    else {
        let index = 0
        while (index++ < position) {
            previous = current
            current = current.next
        }
        // 当找到要删除的元素后,将它的上一项元素的指针指向它的下一项元素
        previous.next = current.next
    }
    
    // 因为删除了一项元素,注意长度-1
    this.length--
    return true
}

remove

LinkedList.prototype.remove = function (data) {
    // 调用indexOf方法,根据data获得元素的position
    const index = this.indexOf(data)
    
    // 调用removeAt方法,根据position移除元素
    const flag = index == -1 ? false : this.removeAt(index)
    
    // 短路运算,且找假。如果移除了元素,就将长度减一
    flag && this.length--
    return flag
}

但是这里面有个误区,就是因为在remove里面调用了removeAt,所以就用不对长度进行减一的操作了。

LinkedList.prototype.remove = function (data) {
    const index = this.indexOf(data)
    return this.removeAt(index)
}

因为越界判断等操作,removeAt里面都已经做好了。