5.单向链表
链表以及数组的缺点
链表和数组一样,可以用于存储一系列的元素,但是链表和数组的实现机制完全不同。
这一章中,我们就来学习一下另外一种非常常见的用于存储数据的线性结构:链表。
数组:
要存储多个元素,数组(或称为列表)可能是最常用的数据结构。
我们之前说过,几乎每一种编程语言都有默认实现数组结构。
但是数组也有很多缺点:
数组的创建同城需要申请一段连续的内存空间(一整块的内容),并且大小是固定的(大多数编程语言数组都是固定的),所以当当前数组不能满足容量需求时,需要扩容。
(一般情况下是申请一个更大的数组,比如2倍。然后将原数组中的元素赋值过去)
而且在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移。
尽管我们已经学过的JavaScript的Array类方法可以帮我们做这些事,但背后的原理依然是这样。
链表的优势
要存储多个元素,另外一个选择就是链表。
但不用于数组,链表中的元素在内存中不必事连续的空间。
链表的每个元素有一个存储元素本身的节点和一个指向下一个元素的引用(有些语言称为指针或者连接)组成。
相对于数组,链表有一些优点:
内存空间不是必须连续的,可以充分利用计算机的内存,实现灵活的内存动态管理。
链表不必再创建时就确定大小,并且大小可以无限的延伸下去。
链表在插入和删除数据时,时间复杂度可以达到O(1).相对数组效率高很多。
相对于数组,链表有一些缺点:
链表访问任何一个位置的元素时,都需要从头开始访问。(无法跳过第一个元素访问任何一个元素)。
无法通过下标直接访问元素,需要从头一个个访问,知道找到对应的元素。
链表到底是什么?
什么是链表呢?
其实上面我们已经简单的提过了链表的结构,我们这里更加详细的分析一下。
链表类似于火车:有一个火车头,火车头会链接一个节点,节点上有乘客(类似于数据),并且这个节点会连接下一个节点,以此类推。
链表的火车结构:
链表结构的封装
我们先来创建一个链表类
链表结构封装代码
<script> // 封装链表类 function LinkedList() { // 内部的类:节点类 function Node(data) { this.data = data; this.data = null; } // 属性 this.head = null; this.length = 0; } </script>
链表常见操作:
我们先来认识一下,链表中应该有哪些常见的操作
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方法,让其只输出元素的值。
整体你会发现操作方法和数组非常类似,因为链表本身就是一种可以代替数组的结构。
append方法
向链表尾部追加数组可能有两种情况:
链表本身为空,新添加的数组时唯一的节点。
链表不为空,需要向其他节点后面追加节点。
append方法代码
<script> // 封装链表类 function LinkedList() { // 内部的类:节点类 function Node(data) { this.data = data; this.data = null; } // 属性 this.head = null; this.length = 0; // 追加方法 LinkedList.prototype.append = function(data) { // 1.创建新的节点 var newNode = new Node(data); // 判断是否添加的是第一个节点 if (this.length == 0) { // 是第一个节点 this.head = newNode; } else { // 不是第一个节点 // 找到最后一个节点 var current = this.head; while (current.next) { current = current.next; } // 最后节点的next指向新的节点 current.next = newNode; } // length+1 this.length += 1; } } </script>
toString方法
我们先来实现一下链表的toString方法,这样会方便测试上面的台南佳代码
该方法比较简单,主要是获取每一个元素。
还是从head开头,因为获取链表的任何元素都必须从第一个节点开头。
循环遍历每一个节点,并且取出其中的element,拼接成字符串。
将最终字符串返回
insert方法
接下来实现另外一个添加数据的方法:在任意位置插入数据。
添加到第一个位置:
添加到第一个位置,表示新添加的节点是头,就需要将原来的头节点,作为新节点的next
另外这个时候的head应该指向新节点。
添加到其他位置:
如果是添加到其他的位置,就需要先找到这个节点的位置。
我们通过while循环,一点点向下找。并且在这个过程中保存上一个节点和下一个节点
找到正确的位置后,将新节点的next指向下一个节点,将上一个节点的next指向新的节点。
insert方法代码:
// inset方法 LinkedList.prototype.insert = function(position, data) { // 对position进行月结判断 if (position < 0 || position > this.length) return false // 根据data创建newNode var newNode = new Node(data) // 判断插入的位置是否是第一个 if (position == 0) { newNode.next = this.head this.head = newNode } else { var index = 0 var previous = null while (index++ < position) { previous = current current = current.next } newNode.next = current previous.next = newNode } // length + 1 this.length += 1 return true }
get(position):获取对应位置的元素
// get方法 LinkedList.prototype.get = function(position) { // 越界判断 if (position < 0 || position >= this.length) return null // 获取对应的data var current = this.head var index = 0 while (index++ < position) { current = current.next } return current.data }
indexOf(element):返回元素在列表中的索引。如果列表中没有该元素则返回-1
// indexOf方法 LinkedList.prototype.indexOf = function(data) { // 定义变量 var current = this.head var index = 0 // 开始查找 while (current) { if (current.data == data) { return index } current = current.next index += 1 } // 找到最后没有找到,返回-1 return -1 }
update(position):修改某个位置的元素
// updata方法 LinkedList.prototype.update = function(position, newData) { // 判断越界 if (position < 0 || position >= this.length) return false // 查找正确的节点 var current = this.head var index = 0 while (index++ < position) { current = current.next } // 将position位置的node的data修改程newData current.data = newData return true }
removeAt(position):从列表的特定位置移除一项。
// removeAt方法 LinkedList.prototype.removeAt = function(position) { // 判断越界 if (position < 0 || position >= this.length) return null // 判断是否删除的是第一个节点 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 } // 前一个节点的next指向,current的next即可 previous.next = current.next } // length -1 this.length -= 1 return current.data }
remobe(element):从列表中移除一项。
// remove方法 LinkedList.prototype.remove = function(data) { // 获取data在列表中的位置 var position = this.indexOf(data) // 根据位置信息,删除节点 return this.removeAt(position) }
isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false
// isEmpty方法 LinkedList.prototype.isEmpty = function() { return this.length == 0 }
size():返回链表包含的元素个数。与数组的length属性类似
// size方法 LinkedList.prototype.size = function() { return this.length }