前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据结构---单向链表

数据结构---单向链表

作者头像
用户4793865
发布2023-01-12 11:59:51
6410
发布2023-01-12 11:59:51
举报
文章被收录于专栏:前端小菜鸡yym前端小菜鸡yym

theme: channing-cyan

链表和数组

image.png
image.png

二者都用于存储一系列的元素,但是实现机制又完全不同。

数组

数组是我们存储多个元素时,最常用的数据结构。但是有以下缺点

  • 创建时,需要申请一段连续的内存空间,并且大小是固定的。当不能满足容量需求时,需要扩容。(一般情况下是申请爱你改一个更大的数组,然后将原数组复制过去)
  • 在数组开头和中间插入数据成本高,需要进行大量元素的位移。链表
  • 不同于数组,链表中的元素在内存中不必是连续的空间。
  • 链表的每个元素由一个存储元素本身的节点 和指向下一个元素的引用(有些语言称为指针或者连接)组成。

相对于数组的优点

  • 内存空间不必是连续的,可以充分的利用计算机的内存,实现灵活的内存动态管理。
  • 不必在创建时就确定大小,并且大小可以无限延伸下去。
  • 插入和删除数据时,时间复杂度可以达到O(1)。
  • 相对于数组的缺点 访问任何位置的元素,都需要从头开始访问。并且无法通过下标直接访问元素,需要从头一个个访问,直到找到对应的元素。

如果我频繁的在头部或中间插入数据,此时选择链表。但频繁使用下标操作时,就选择数组。

链表到底是什么?

这里有个形象的比喻:火车🚄

有一个火车头,火车头会连接一节车厢【元素】,车厢上有乘客【类似于数据】,这节车厢连接下一节车厢,以此类推。

如图

从头部header开始,指向第一个节点,item是具体的数据、next是指向下一个节点的指针。以此类推,最后一个节点的指针指向null。如果是一个空链表,那么header直接指向null

image.png
image.png

实现链表结构

封装链表结构

  • 在LinkedList内部定义一个节点类,节点中包含数据data、和指针next(我们这里没有传next参数,所以赋值null)。
  • 链表头部赋初始值null
  • 最后的length用于记录链表的长度
代码语言:javascript
复制
   <script>
        function LinkedList() {
            // 内部节点类
            function Node(data) {
                this.data = data;
                this.next = null
            }
            this.header = null
            //  记录链表的长度
            this.length = 0
   </script>        

那么定义完一个链表结构了,我们再看一下链表都需要哪些常见的操作。我们再去完善这些操作方法。

链表常见操作

  • isEmpty():如果链表中不包含任何元素,返回true,长度大于0返回false。
  • size():返回链表的元素个数,对应数组中的length。
  • toString():由于列表使用了Node类,就需要重写继承自js对象的默认的toString方法,让其只输出元素的值。

然后,以下的操作方法。可以按照增删改查的顺序来看:

  • append(element):向列表尾部插入新的项
  • insert(position,element):向列表指定位置插入新的项

  • removeAt(position):从列表的特定位置移除一项(给的是位置信息)
  • remove(element):从列表中移除给定元素项(给的元素信息)

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

  • get(position): 根据位置获得元素
  • indexOf(element):根据元素获得位置,没有的话返回 -1

append()

主要实现 向末尾添加元素。

梳理一下过程

1.刚开始,header并没有指向任何节点,所以指向null。

image.png
image.png

2.加入第一个节点。

使用Node方法(上面链表结构中定义的)创建节点,此时我们判断其是第一个节点,然后就需要把header的指针指向这个节点。 我们这里对第一个节点的next并不需要做指向null的处理,因为我们在初始化链表结构的时候next就是null。

image.png
image.png

3.添加第二个节点

使用Node方法创建节点,然后找到上一个节点,并把上一个节点的指针指向此节点。

image.png
image.png

coding

追加方法

LinkedList原型上添加append方法

代码语言:javascript
复制
  LinkedList.prototype.append = function (data) {
              
  }

判断添加的是否是第一个节点,(我们通过length是否为0,来判断是否是第一个节点)。然后创建节点,并将header指向这个节点

代码语言:javascript
复制
  //  如果是第一个节点
 if (this.length == 0) {
    // 创建节点
   var newNode = new Node(data)
   //  head指针指向 第一个节点
   this.header = newNode
   } else {
   
  }

当节点不是第一个节点,也就是else中的内容: 首先创建一个节点,我们要做的是把我们没添加这个节点前的最后一个节点的next指向我们创建的这个节点。

这里通过一个current变量,首先将header赋值给它(此时的header指向第一个节点),然后向下寻找,当current.next不为空,则说明就不是最后一个节点,我们就把current.next赋值给current。直到 current.next为空(指向null),我们再把最后一个节点的next指向 我们 新创建的节点。

代码语言:javascript
复制
else {
       // 创建节点
        var newNode = new Node(data)
        var current = this.header
        // 判断current.next不为空,不为空就说明不是最后一个节点。因为最后一个节点的next指向null
        while (current.next) {
                current = current.next
          }
             current.next = newNode
   }

最后不要忘记给length+1

代码语言:javascript
复制
 this.length += 1

append完整代码

这里在判断是第一个节点和不是第一个节点的方法中都创建了节点,所以我们把创建节点的代码 var newNode = new Node(data)提取出来了。

  • 创建节点
  • 如果是第一个节点,将header指向它
  • 如果不是第一个节点,找到最后一个节点,把最后一个节点的next指向新节点。这里的最后一个节点指的是没添加我们这个节点前的最后一个节点
代码语言:javascript
复制
       function LinkedList() {
            // 内部节点类
            function Node(data) {
                this.data = data;
                this.next = null
            }
            this.header = null
            //  记录链表的长度
            this.length = 0
            LinkedList.prototype.append = function (data) {
                // 1.创建新节点
                var newNode = new Node(data)
                //  2.1 如果是第一个节点
                if (this.length == 0) {
                    //  header指针指向 第一个节点
                    this.header = newNode
                    // 2.2 不是第一个节点 
                } else {
                    // 2.3 找到最后一个节点
                    // 定义一个变量current,将header赋值给它,此时header是已经指向了第一个节点
                    var current = this.header
                    // 判断current.next不为空,不为空就说明不是最后一个节点。因为最后一个节点的next指向null
                    while (current.next) {
                        current = current.next
                    }
                    current.next = newNode
                }
                this.length += 1
            }
        }

toString()

思路

  • 在原型上添加toString方法,我们这里要做的是找到所有节点的数据,然后将其拼接成字符串。
  • 定义两个变量,current当前节点和listString用于拼接字符串。
  • 循环节点 首先,判断一下current是否为空,为空直接返回字符串listString。不为空的话就将内容拼接到我们的listString上。并且current指向current.next

coding

代码语言:javascript
复制
  /**
             *  toString() 找到所有节点
             * */
            LinkedList.prototype.toString = function () {
                // 1.定义变量
                var current = this.header
                var listString = ""
                // 2.循环获取一个个节点
                while (current) {
                    listString += current.data + " "
                    // 依次向后面找
                    current = current.next
                }
                return listString
            }

测试一下

这里用到了我们之前写好的append方法,写好toString方法后就可以测试其他方法是否有问题了。

代码语言:javascript
复制
        var list = new LinkedList()
        list.append('abc')
        list.append('fgd')
        list.append('dds')
        console.log(list.toString())
image.png
image.png

insert()

定义方法

我们需要两个参数:位置(position)和数据(data)

代码语言:javascript
复制
 LinkedList.prototype.insert = function (position,data) {

  }

position越界判断

我们需要对position进行越界的判断,比如 -100,正常对于数组来说负数代表从数组尾部开始计算。这里我们就不做这种处理了。我们这里只要是负数,就返false。

代码语言:javascript
复制
    LinkedList.prototype.insert = function (position, data) {
                if (position < 0) {
                    return fasle
                }
            }

并且,比如现在有abc cba nba fbi四个元素,此时的length是4、下标是3。我们再插入新的节点,可以插到下标是 0 1 2 3 4的位置。

image.png
image.png

所以再添加一个判断条件

代码语言:javascript
复制
LinkedList.prototype.insert = function (position, data) {
                // 对position进行越界判断
                if (position < 0 || position > this.length) {
                    return fasle
                }
            }

根据data创建newNode

代码语言:javascript
复制
 // 创建新节点
  var newNode = new Node(data)

判断插入位置

判断插入位置是否是第一个

如果是第一个如下图:先将这个节点的next指向原来的第一个节点。然后再将header指向我们的新节点。那么,怎么获得原来的第一个节点呢?header指向的就是原来的第一个节点。

image.png
image.png
代码语言:javascript
复制
 // 判断插入的位置
                if (position == 0) {
                    // 新节点的next指向原来的第一个节点
                    newNode.next = this.header
                    // 再把header指向新节点
                    this.header = newNode
                }

插入其他的位置

这里以下标为2为例。

我们首先要做的就是找到下标为2的节点,如下从头依次寻找,直到第三次找到了下标为2的节点(这里定义了两个变量previous和current分别记录目标节点的上一个节点和目标节点)。然后我们将新节点的next指向我们之前下标是2的节点。但是现在我们发现,之前下标是1的节点的next也指向着原来下标是2的节点。

image.png
image.png
image.png
image.png
代码语言:javascript
复制
   // position = 2
                    var index = 0;
                    var current = this.header
                    var previous = null
                    while (index++ < position) {
                        // 记录当前节点的上一个节点
                        previous = current
                        //  current.next指向的是下一个元素
                        current = current.next
                    }
                    // 新节点指向原来的节点
                    newNode.next = current
                    // 原来节点的上一个节点的next指向新节点
                    previous.next = newNode
                }

完整代码

最后不要忘了给length+1

代码语言:javascript
复制
  LinkedList.prototype.insert = function (position, data) {
                // 对position进行越界判断
                if (position < 0 || position > this.length) {
                    return fasle
                }
                // 创建新节点
                var newNode = new Node(data)
                // 判断插入的位置
                if (position == 0) {
                    // 新节点的next指向原来的第一个节点
                    newNode.next = this.header
                    // 再把header指向新节点
                    this.header = newNode
                } else {
                    // position = 2
                    var index = 0;
                    var current = this.header
                    var previous = null
                    while (index++ < position) {
                        // 记录当前节点的上一个节点
                        previous = current
                        //  current.next指向的是下一个元素
                        current = current.next
                    }
                    // 新节点指向原来的节点
                    newNode.next = current
                    // 原来节点的上一个节点的next指向新节点
                    previous.next = newNode
                }
                this.length += 1
                return true
            }

theme: channing-cyan

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

get(position)

根据位置获取相应的元素。

思路

  • 首先,做一下越界判断,不能小于零并且不能大于等于长度(索引比长度小一)
  • 定义两个变量:当前值和一个index用于记录索引
  • 当index小于我们传入的参数position时,让current指向current.next向下寻找,同时增长index。
  • 直到index==position时停止,并返回current的值。

coding

代码语言:javascript
复制
             /**
             *   4.get()
             * */
            LinkedList.prototype.get = function (position) {
                // 越界判断
                if (position < 0 || position >= this.length) {
                    return null
                }
                // 获取相应的data
                var current = this.header
                var index = 0
                while (index < position) {
                    current = current.next
                    // 为了让current后移
                    index++
                }
                return current.data
            }
复制代码

测试一下

代码语言:javascript
复制
        console.log(list.toString())
        console.log(list.get(0))
        console.log(list.get(3))
image.png
image.png

indexOf(element)

根据元素,获取元素所在位置的索引。

思路

  • 定义两个变量:current(存放当前值)和 index(存放当前索引)。
  • 然后开始查找,通过判断current是否为空,最后一个节点的next指向空,为空则说明已经遍历完了整个链表。
  • 如果没有找到返回-1
  • 如果current的data和我们传入的data相等,则返回其索引。如果不相等让current指向current的next,向下寻找,并且让index+1。

如下图,传入的data是dfg

  • 经历了两次查找,第一次开始:current指向第一个节点,data是'abc',index则是0.然而current的data和我们传入的data并不相等。所以向下查找。
  • 第二次:现在current指向了第一个节点的next,也就是第二个节点。index也进行了+1,此时也是第二个元素的索引,比较发现,第二个节点的data和我们传入的data相等,停止查找返回index
image.png
image.png

还是上面的,假如传入data是hhh

image.png
image.png

一直查找,直到current指向null,说明链表中没有此元素,返回-1

coding

代码语言:javascript
复制
             /**
             *  5.indexOf()
             * */
            LinkedList.prototype.indexOf = function (data) {
                // 定义变量
                var current = this.header
                var index = 0
                // 开始查找
                while (current) {
                    if (current.data == data) {
                        return index
                    }
                    current = current.next
                    index += 1
                }
                // 向后查找,没找到返回-1
                return -1
            }

测试一下

代码语言:javascript
复制
      console.log(list.indexOf('fgd'))
      console.log(list.indexOf('hhh'))
image.png
image.png

update()

更改相应位置的元素,传入位置和新的元素数据。传入两个参数:位置索引和新的数据

思路

  • 判断越界位置不能小于零,并且不能超过当前长度。
  • 定义变量current和index。index小于当传入的position参数时将current.next赋值给current。并且index自增。
  • index==position,将newData赋值给current的data

coding

代码语言:javascript
复制
             /**
             *  6.update()
             * */
            LinkedList.prototype.update = function (position, newData) {
                // 越界判断
                if (position < 0 || position >= this.length) {
                    return false
                }
                // 查找正确节点
                var current = this.header
                var index = 0
                while (index++ < position) {
                    current = current.next
                }
                //将position位置的node的data改为newData
                current.data = newData
                return true
            }

测试

代码语言:javascript
复制
        list.update(0, 'mmm')
        list.update(3, 'nnn')
        console.log(list.toString())
image.png
image.png

removeAt()

从链表中移除

思路

当position是0的时候

只需要将header指向下一个节点(索引为1)即可。然后,之前的第一个节点就会因为没有指针指向它而被垃圾回收机制回收。

image.png
image.png

当position不是0的时候

我们拿2举例子,找到索引是2的元素。定义两个变量previous和current分别记录当前元素的上一个和当前元素。然后将索引是2的元素的上一个元素的next指向索引是2的元素的下一个元素。

image.png
image.png

coding

  • 越界判断:position不能小于0,并且不能大于等于当前链表的长度(根本不存在)。
  • 把current变量放到了最外面是为了返回删除的具体数据
  • 判断是否是索引为0的位置,如果是索引为0的位置,将header指向第一个元素的next(也就是新的第一个元素)
  • 如果,索引不是0,定义变量index(用于记录索引)和previous,包括之前定义的current。通过循环找到索引值和position参数相等的节点。在这个过程中记录着previous和current。(将current赋值给previous,current的next赋值给current)。
  • 直到,找到索引值和position参数相同的节点,将前一个节点的next指向当前节点的下一个节点(也就是当前节点的next)
  • 最后,返回current的data(也就是删除的数据),还有别忘了删除后,链表的长度减一。
代码语言:javascript
复制
             /**
             *  7.removeAt()
             * */
            LinkedList.prototype.removeAt = function (position) {
                // 越界判断
                if (position < 0 || position >= this.length) {
                    return false
                }
                // 放到外面 为了能返回删除的元素
                var current = this.header
                // 是否删除的是第一个节点
                if (position == 0) {
                    this.header = this.header.next
                } else {
                    var index = 0
                    // 前一个 默认是null
                    var previous = null
                    while (index++ < position) {
                        previous = current
                        current = current.next
                    }
                    // 让前一个节点的next指向,current的next
                    previous.next = current.next
                }
                // length不要忘记-1
                this.length -= 1
                return current.data
            }
复制代码

测试一下

image.png
image.png
代码语言:javascript
复制
  list.removeAt(0)
  console.log(list.toString())

remove()

移除传入的元素。

思路

这里,我们先通过之前写好的indexOf()找到这个元素所在的位置。然后再通过removeAt(position)方法来移除这个元素。

coding

代码语言:javascript
复制
             /**
             *   8.remove() = indexOf + removeAt
             * */
            LinkedList.prototype.remove = function (data) {
                // 获取data在链表中的位置
                var position = this.indexOf(data)
                // 根据位置信息删除节点
                return this.removeAt(position)
            }

测试一下

这里用的是之前的数据

代码语言:javascript
复制
        list.remove('nnn')
        console.log(list.toString())
image.png
image.png

isEmpty()

如果链表中不包含任何元素,返回true,否则返回false。

coding

返回 this.length==0,如果不为0,则返回false,为0则返回true。

代码语言:javascript
复制
             /**
              *  9.isEmpty
              * */
            LinkedList.prototype.isEmpty = function () {
                return this.length == 0
            }

测试一下

代码语言:javascript
复制
  console.log(list.isEmpty())     // false

size()

返回链表中元素的个数,也就是length。

coding

直接返回长度

代码语言:javascript
复制
              /**
                *  10.size()
                * */
            LinkedList.prototype.size = function () {
                return this.length
            }

测试一下

代码语言:javascript
复制
  console.log(list.size())     // 5

全部代码

代码语言:javascript
复制
<script>
        function LinkedList() {
            // 1.内部节点类
            function Node(data) {
                this.data = data;
                this.next = null
            }
            this.header = null
            //  记录链表的长度
            this.length = 0
            LinkedList.prototype.append = function (data) {
                // 1.创建新节点
                var newNode = new Node(data)

                //  2.1 如果是第一个节点
                if (this.length == 0) {
                    //  header指针指向 第一个节点
                    this.header = newNode
                    // 2.2 不是第一个节点 
                } else {
                    // 2.3 找到最后一个节点
                    // 定义一个变量current,将header赋值给它,此时header是已经指向了第一个节点
                    var current = this.header
                    // 判断current.next不为空,不为空就说明不是最后一个节点。因为最后一个节点的next指向null
                    while (current.next) {
                        current = current.next
                    }
                    current.next = newNode
                }
                this.length += 1
            }
            /**
             *  2.toString() 找到所有节点
             * */
            LinkedList.prototype.toString = function () {
                // 1.定义变量
                var current = this.header
                var listString = ""
                // 2.循环获取一个个节点
                while (current) {
                    listString += current.data + " "
                    // 依次向后面找
                    current = current.next
                }
                return listString
            }
            /**
             *  3.insert( )
             * */
            LinkedList.prototype.insert = function (position, data) {
                // 对position进行越界判断
                if (position < 0 || position > this.length) {
                    return fasle
                }
                // 创建新节点
                var newNode = new Node(data)
                // 判断插入的位置
                if (position == 0) {
                    // 新节点的next指向原来的第一个节点
                    newNode.next = this.header
                    // 再把header指向新节点
                    this.header = newNode
                } else {
                    // position = 2
                    var index = 0;
                    var current = this.header
                    var previous = null
                    while (index++ < position) {
                        // 记录当前节点的上一个节点
                        previous = current
                        //  current.next指向的是下一个元素
                        current = current.next
                    }
                    // 新节点指向原来的节点
                    newNode.next = current
                    // 原来节点的上一个节点的next指向新节点
                    previous.next = newNode
                }
                this.length += 1
                return true
            }
            /**
             *   4.get()
             * */
            LinkedList.prototype.get = function (position) {
                // 越界判断
                if (position < 0 || position >= this.length) {
                    return null
                }
                // 获取相应的data
                var current = this.header
                var index = 0
                while (index < position) {
                    current = current.next
                    // 为了让current后移
                    index++
                }
                return current.data
            }
            /**
             *  5.indexOf()
             * */
            LinkedList.prototype.indexOf = function (data) {
                // 定义变量
                var current = this.header
                var index = 0
                // 开始查找
                while (current) {
                    if (current.data == data) {
                        return index
                    }
                    current = current.next
                    index += 1
                }
                // 向后查找,没找到返回-1
                return -1
            }
            /**
             *  6.update()
             * */
            LinkedList.prototype.update = function (position, newData) {
                // 越界判断
                if (position < 0 || position >= this.length) {
                    return false
                }
                // 查找正确节点
                var current = this.header
                var index = 0
                while (index++ < position) {
                    current = current.next
                }
                //将position位置的node的data改为newData
                current.data = newData
                return true
            }
            /**
             *  7.removeAt()
             * */
            LinkedList.prototype.removeAt = function (position) {
                // 越界判断
                if (position < 0 || position >= this.length) {
                    return false
                }
                // 放到外面 为了能返回删除的元素
                var current = this.header
                // 是否删除的是第一个节点
                if (position == 0) {
                    this.header = this.header.next
                } else {
                    var index = 0

                    // 前一个 默认是null
                    var previous = null
                    while (index++ < position) {
                        previous = current
                        current = current.next
                    }
                    // 让前一个节点的next指向,current的next
                    previous.next = current.next
                }
                // length不要忘记-1
                this.length -= 1
                return current.data
            }
            /**
             *   8.remove() = indexOf + removeAt
             * */
            LinkedList.prototype.remove = function (data) {
                // 获取data在链表中的位置
                var position = this.indexOf(data)
                // 根据位置信息删除节点
                return this.removeAt(position)
            }
            /**
             *  9.isEmpty
             * */
            LinkedList.prototype.isEmpty = function () {
                return this.length == 0
            }
            /**
             *  10.size()
             * */
            LinkedList.prototype.size = function () {
                return this.length
            }
        }
        var list = new LinkedList()
        list.append('abc')
        list.append('fgd')
        list.append('dds')


        list.insert(0, 'insert')
        list.insert(3, 'bbb')
        list.insert(5, 'cccc')
        // console.log(list.toString())
        // console.log(list.get(0))
        // console.log(list.get(3))
        // console.log(list.indexOf('fgd'))
        // console.log(list.indexOf('hhh'))
        list.update(0, 'mmm')
        list.update(3, 'nnn')
        console.log(list.toString())

        list.remove('nnn')
        console.log(list.size())
    </script>
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-07-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 链表和数组
    • 数组
      • 相对于数组的优点
    • 链表到底是什么?
      • 如图
    • 实现链表结构
      • 封装链表结构
    • 链表常见操作
      • append()
        • 梳理一下过程
        • coding
        • append完整代码
      • toString()
        • 思路
        • coding
        • 测试一下
      • insert()
        • 定义方法
        • position越界判断
        • 根据data创建newNode
        • 判断插入位置
        • 完整代码
      • theme: channing-cyan
        • get(position)
          • 思路
          • coding
          • 测试一下
        • indexOf(element)
          • 思路
          • coding
          • 测试一下
        • update()
          • 思路
          • coding
          • 测试
        • removeAt()
          • 思路
          • coding
          • 测试一下
        • remove()
          • 思路
          • coding
          • 测试一下
        • isEmpty()
          • coding
          • 测试一下
        • size()
          • coding
          • 测试一下
        • 全部代码
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档