数据结构之单向链表

链表以及数组的缺点

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

  • 这一章中,我们就来学习一下另外种非常常见的用于存储数据的线性结构: 链表

  • 数组:

    • 要存储多个元素,数组(或称为列表)可能是最常用的数据结构。
    • 我们之前说过,几乎每一种编程语言都有默认实现数组结构
    • 但是数组也有很多缺点:
    1. 数组的创建通常需要申请一段连续的内存空间(一整块的内存), 并且大小是固定的(大多数编程语言数组都是固定的),所以当当前数组不能满足容量需求时,需要扩容(-般情况下是申请一个更大的数组,比如2倍.然后将原
      数组中的元素复制过去)
    2. 而且在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移.
    3. 尽管我们已经学过的JavaScript的Array类方法可以帮我们做这些事,但背后的原理依然是这样。

链表的优势

  • 要存储多个元素,另外一个选择就是 链表.
  • 但不同于数组,链表中的元素在内存中 不必是连续的空间 .
  • 链表的每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者连接)组成
  • 相对于数组,链表有一些优点:
    1. 内存空间不是必须连续的.可以充分利用计算机的内存,实现灵活的内存动态管理.
    2. 链表不必在创建时就确定大小,并且大小可以无限的延伸下去.
    3. 链表在插入和删除数据时,时间复杂度可以达到O(1),相对数组效率高很多.
  • 相对于数组,链表有一些缺点:
    1. 链表访问任何个位置的元素时,都需要从头开始访问.(无法跳过第一个元素访问任何一 个元素).
    2. 无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的元素.

链表到底是什么?

  • 什么是链表呢?
    • 其实上面我们已经简单的提过了链表的结构,我们这里更加详细的分析一下.
    • 链表类似于火车:有一个火车头,火车头会连接一个节点节点上有乘客(类似于数据),并且这个节点会连接下一
      个节点以此类推.

链表的火车结构:
请添加图片描述
请添加图片描述
请添加图片描述

链表结构的封装

我们来先创建一个链表类

  // 封装链表类
  function LinkedList() {
    // 内部的类:节点类
    function Node(data) {
      this.data = data
      this.next = null
    }

    // 属性
    this.head = null
    this.length = 0
    
	// 链表中的方法
  }

代码解析:

  • 封装LinkedList的类,用于表示我们的链表结构. (和Java中的链表同名,不同Java中的这个类是一个双向链表,下一篇内容讲到双向链表)
  • 在LinkedList类中有一个Node类 用于封装每一个节点上的信息.(和优先级队列的封装样)
  • 链表中我们保存两个属性一个是链表的长度一个是链表中第一个节点
  • 当然还有很多链表的操作方法.我们在下文中一起学习
链表的常见操作

我们先来认识一下, 链表中应该有哪些常见的操作

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

整体你会发现操作方法和数组非常类似,因为链表本身就是一种可以代替数组的结构.

1.append方法
  • 向链表尾部追加数据可能有两种情况:
    • 链表本身为空,新添加的数据时唯一的节点.
      请添加图片描述
    • 链表不为空,需要向其他节点后面追加节点
      请添加图片描述
    // 1.追加方法
    LinkedList.prototype.append = function (data) {
      // 1.创建新节点
      let newNode = new Node(data);

      // 2.判断是否添加的是第一个节点
      if (this.length == 0){  // 2.1是第一个节点
        this.head = newNode
      }else {                 // 2.2.不是第一个节点
        // 找到最后一个节点
        let current = this.head		// 指针
        while (current.next) {
          current = current.next
        }
        // 最后节点的next指向新的节点
        current.next = newNode
      }
      // 3.length+1
      this.length += 1
    }
2.toString方法

我们先来实现一下链表的toString方法 这样会方便测试上面的添加代码

  • 该方法比较简单主要是获取每个元素
  • 还是从head开头,因为获取链表的任何元素都必须从第一个节点开头
  • 循环遍历每一个节点,并且取出其中的element,拼接成字符串.
  • 将最终字符串返回.
    // 2.toString方法
    LinkedList.prototype.toString = function () {
      // 定义变量
      let current = this.head
      let listString = ''

      while (current) {
        listString += current.data + ' '
        current = current.next
      }
      return listString
    }
3.inser方法

接下来实现另外一个添加数据的方法:在任意位置插入数据

  • 添加到第一个位置:
    • 添加到第一个位置表示新添加的节点是头,就需要将原来的头节点作为新节点的next
    • 另外这个时候的head应该指向新节点
      请添加图片描述
  • 添加到其他位置:
    • 如果是添加到其他位置,就需要先找到这个节点位置了.
    • 我们通过while循环,一点点向 下找并且在这个过程中保存上一个节点和下一个节点
    • 找到正确的位置后,将新节点的next指向下一个节点将上一个节点的next指向新的节点
      请添加图片描述
      请添加图片描述

请添加图片描述

3.其他操作

其他方法方式差不多,所以就不多讲了:

  // 封装链表类
  function LinkedList() {
    // 内部的类:节点类
    function Node(data) {
      this.data = data
      this.next = null
    }

    // 属性
    this.head = null
    this.length = 0

    // 1.追加方法
    LinkedList.prototype.append = function (data) {
      // 1.创建新节点
      let newNode = new Node(data);

      // 2.判断是否添加的是第一个节点
      if (this.length == 0){  // 2.1是第一个节点
        this.head = newNode
      }else {                 // 2.2.不是第一个节点
        // 找到最后一个节点
        let current = this.head
        while (current.next) {
          current = current.next
        }
        // 最后节点的next指向新的节点
        current.next = newNode
      }
      // 3.length+1
      this.length += 1
    }

    // 2.toString方法
    LinkedList.prototype.toString = function () {
      // 定义变量
      let current = this.head
      let listString = ''

      while (current) {
        listString += current.data + ' '
        current = current.next
      }
      return listString
    }

    // 3.insert方法
    LinkedList.prototype.insert = function (position, data) {
      // 1.对position进行越界判断
      if (position < 0 || position > this.length) return false

      // 2.根据data创建newNode
      let newNode = new Node(data)

      // 3.判断插入的位置是否是第一个
      if (position == 0) {
        newNode.next = this.head
        this.head = newNode
      }else {
        let index = 0
        let current = this.head
        let previous = null
        while (index++ < position) {
          previous = current
          current = current.next
        }
        newNode.next = current
        previous.next = newNode
      }
      // 4.length+1
      this.length += 1

      return true
  }


  // 4.get方法
    LinkedList.prototype.get = function(position) {
      // 1.越界判断
      if (position < 0 || position >= this.length) return null

      // 2.获取对应的data
      let current = this.head
      let index = 0
      while (index++ < position) {
        current = current.next
      }
      return current.data
    }


    // 5.indexOf方法
    LinkedList.prototype.indexOf = function (data)  {
      // 1.定义变量
      let current = this.head
      let index = 0
      // 2.开始查找
      while (current) {
        if (current.data == data) {
          return index
        }
        index++
        current = current.next
      }
      // 3.找到最后没找到,返回-1
      return -1
    }

    // 6.update方法
    LinkedList.prototype.update = function (position, newDate) {
      // 1、越界判断
      if (position < 0 || position >= this.length) return false
      // 2.查找正确的节点
      let current = this.head
      let index = 0
      while (index++ < position) {
        current = current.next
      }
      // 3.更新数据
      current.data = newDate
      return true
    }

    // 7.removeAt方法
    LinkedList.prototype.removeAt = function (position) {
      // 越界判断
      if (position < 0 || position >= this.length) return false
      // 2.判断删除的是不是第一个节点
      if (position == 0) {
        this.head = this.head.next
      } else {
        let index = 0
        let current = this.head
        let previous = null
        while (index++ < position) {
          previous = current
          current = current.next
        }
        // 前一个next指向current的next即可
        previous.next = current.next
      }
      // 3.length-1
      this.length -= 1
      return true
    }
    
    // 8.remove方法
    LinkedList.prototype.remove = function (data) {
      // 获取列表的对应位置
      let position = this.indexOf(data)
      // 根据位置删除元素
      return this.removeAt(position)
    }

    // 9.isEmpty
    LinkedList.prototype.isEmpty = function () {
      return this.length == 0
    }

    // 10.size()
    LinkedList.prototype.seze = function () {
      return this.length
    }

  }





  // 测试代码
  // 创建linkList
  let list = new LinkedList()

  list.append('aaa')
  list.append('bbb')
  list.append('ccc')
  // alert(list)

  // 测试append方法
  // list.insert(0, 'abc')
  // list.insert(3, 'cba')
  // list.insert(5, 'jjj')
  alert(list)

  // 测试get方法
  // alert(list.get(3))
  // alert(list.get(0))
  // alert(list.get(5))

  // 测试indexOf方法
  // alert(list.indexOf('aaa'))

  // 测试update方法
  // list.update(0, 'nnn')
  // list.update(2, 'mmm')
  // alert(list)
  //
  // list.removeAt(0)
  // alert(list)
  // list.removeAt(3)
  // alert(list)
  //
  // list.remove('aaa')
  // alert(list)
  // list.remove('mmm')
  // alert(list)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值