目录
2.3 insert(position, element)任意位置插入
2.5 indexOf(element)返回元素在列表中的索引
2.6 update(position, data)修改某个位置的元素
2.7 removeAt(position)从列表的特定位置移除一项
一. 认识双向链表
1.单向链表
1.1 概念
只能从头遍历到尾或者从尾遍历到头(一般从头到尾),即链表相连的过程是单向的.
1.2 实现的原理
上一个链表中有一个指向下一个的引用.
1.3 缺点
我们可以轻松的到达下一个节点, 但是回到前一个节点是很难的. 但是, 在实际开发中, 经常会遇到需要回到上一个节点的情况
举个例子:
假设一个文本编辑器用链表来存储文本. 每一行用一个String对象存储在链表的一个节点中.
当编辑器用户向下移动光标时, 链表直接操作到下一个节点即可.
但是当用于将光标向上移动呢? 这个时候为了回到上一个节点, 我们可能需要从first开始, 依次走到想要的节点上.
2.双向链表
2.1 概念
既可以从头遍历到尾, 又可以从尾遍历到头,即链表相连的过程是双向的. 双向链表可以有效的解决单向链表中提到的问题.
2.2 实现的原理
一个节点既有向前连接的引用, 也有一个向后连接的引用.
2.3 缺点
每次在插入或删除某个节点时, 需要处理四个节点的引用, 而不是两个. 也就是实现起来要困难一些
并且相当于单向链表, 必然占用内存空间更大一些.
但是这些缺点和我们使用起来的方便程度相比, 是微不足道的.
2.4 双向连接的特点图解
- head:指向第一个节点;tail:指向最后一个节点
- 每个节点node都由三个部分组成:prev:指向前一个节点;next:指向后一个节点;item:保存的元素
- 双向链表的第一个节点的prev是null,最后的节点的next是null。
二.双向链表的创建
1.创建一个双向链表的类
// 封装双向链表
function DoublyLinkedList() {
// 内部类:节点类
function Node(data) {
this.data = data;
this.prev = null;
this.next = null;
}
// 属性
this.head = null;
this.tail = null;
this.length = 0;
// 常见的操作:方法
}
代码解析:
- 基本思路和单向链表比较相似, 都是创建节点结构函数以及定义一些属性和方法.
- 只是Node中添加了一个this.prev属性, 该属性用于指向上一个节点.
- 另外属性中添加了一个this.tail属性, 该属性指向末尾的节点
2.双向链表的常见操作
双向链表的操作和单向链表的方法都是类似的.
只是在实现的过程中, 需要考虑更多节点之间的关系, 所以变得比单向链表复杂了一些.
- append(element):向列表尾部添加一个新的项
- insert(position, element):向列表的特定位置插入一个新的项。
- get(position):获取对应位置的元素
- indexOf(element):返回元素在列表中的索引。如果列表中没有该元素则返回-1。
- update(position, element):修改某个位置的元素
- removeAt(position):从列表的特定位置移除一项。
- remove(element):从列表中移除一项。
- isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false.size():返回链表包含的元素个数。与数组的length属性类似。
- size():获取链表长度。
- toString0:由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值。
- forwardString():链表从后往前遍历,返回字符串形式。
- backwordString():链表从前往后遍历,返回字符串形式。
-
gatHead():获取链表第一个元素
-
getTail()获取链表最后一个元素
2.1 append(element)尾部追加数据
封装代码:
// 1.append(element)尾部追加数据
DoublyLinkedList.prototype.append = function(data){
// 根据元素创建节点
var newNode = new Node(data)
// 判断是否添加的是第一个节点
if(this.length == 0){
this.head = newNode
this.tail = newNode
}else{
this.tail.next = newNode
newNode.prev = this.tail
this.tail = newNode
}
this.length += 1
}
代码解析:
- 代码1部分不用多讲, 还是通过元素创建新的节点.
- 代码2部分相比之前有一些复杂, 但是还是两种情况.
- 情况一: 链表原来为空
- 链表中原来如果没有数据, 那么直接让head和tail指向这个新的节点即可.
- 情况二: 链表中已经存在数据
- 因为我们是要将数据默认追加到尾部, 所以这个变得也很简单.
- 首先tail中的next之前指向的是null. 现在应该指向新的节点newNode: this.tail.next = newNode
- 因为是双向链表, 新节点的next/tail目前都是null. 但是作为最后一个节点, 需要有一个指向前一个节点的引用. 所以这里我们需要newNode.prev = this.tail
- 因为目前newNod已经变成了最后的节点, 所以this.tail属性的引用应该指向最后: this.tail = newNode即可
- 代码3部分不用多做解析, length需要+1
2.2 将链表转成字符串形式
链表的遍历:
- 之前我们在单向链表中实现了一个toString方法, 它是一种正向的遍历.
- 现在, 为了用户使用方便, 我们实现三个方法
- forwardString:向前遍历转成字符串的方法
- backwordString: 向后遍历转成字符串的方法
- toString: 正向遍历转成字符串的方法
三个方法的代码封装:
// 2.1 toString()方法
DoublyLinkedList.prototype.toString = function () {
return this.backwardString()
}
// 2.2 forwardString()方法
DoublyLinkedList.prototype.forwardString = function () {
// 定义变量
var current = this.tail;
var resultString = ""
// 依次向前遍历,获取每一个节点
while (current) {
resultString += current.data + " ";
current = current.prev; // 将current依次向前移,直至前面没有元素时,循环停止
}
return resultString;
}
// 2.3 backwardString()方法
DoublyLinkedList.prototype.backwardString = function () {
// 定义变量
var current = this.head;
var resultString = ""
// 依次向后遍历,获取每一个节点
while (current) {
resultString += current.data + " ";
current = current.next; // 将current依次向后移,直至后面没有元素时,循环停止
}
return resultString;
}
测试方法: