单向链表
-
链表优点:
内存空间不必是连续的,可以充分利用计算机的内存,实现灵活的内存动态管理。
链表不必在创建时就确定大小,并且大小可以无限延伸下去。
链表在插入和删除数据时,时间复杂度可以达到 O(1),相对数组效率高很多。
-
链表缺点:
访问任何一个位置的元素时,需要从头开始访问。(无法跳过第一个元素访问任何一个元素)
无法通过下标值直接访问元素,需要从头开始一个个访问,直到找到对应的元素。
虽然可以轻松地到达下一个节点,但是回到前一个节点是很难的。
单链表常见操作
append(element)向链表尾部添加一个新的项。insert(position, element)向链表的特定位置插入一个新的项。get(position)获取对应位置的元素。indexOf(element)返回元素在链表中的索引。如果链表中没有该元素就返回-1。update(position, element)修改某个位置的元素。removeAt(position)从链表的特定位置移除一项。remove(element)从链表中移除一项。isEmpty()如果链表中不包含任何元素,返回 true,如果链表长度大于 0 则返回 false。size()返回链表包含的元素个数,与数组的 length 属性类似。toString()由于链表项使用了 Node 类,就需要重写继承自 JavaScript 对象默认的 toString 方法,让其只输出元素的值。
单链表的封装
- 封装节点
export class Node {
constructor(data) {
this.data = data;
this.next = null;
}
}
-
封装单链表
export class LinkedList { constructor() { // 初始链表长度为 0 this.length = 0; // 初始 head 为 null,head 指向链表的第一个节点 this.head = null; } -
常见操作
-
append
append(data) { // 1、创建新节点 const newNode = new Node(data); // 2、追加新节点 if (this.length === 0) { // 链表长度为 0 时,即只有 head 的时候 this.head = newNode; } else { // 链表长度大于 0 时,在最后面添加新节点 let currentNode = this.head; // 当 currentNode.next 不为空时, // 循序依次找最后一个节点,即节点的 next 为 null 时 while (currentNode.next !== null) { currentNode = currentNode.next; } // 最后一个节点的 next 指向新节点 currentNode.next = newNode; } // 3、追加完新节点后,链表长度 + 1 this.length++; } -
insert
if (position === 0) { // position = 0 的情况 // 让新节点的 next 指向 原来的第一个节点,即 head newNode.next = this.head; // head 赋值为 newNode this.head = newNode; } else { // 0 < position <= length 的情况 // 初始化一些变量 let currentNode = this.head; // 当前节点初始化为 head let previousNode = null; // head 的 上一节点为 null let index = 0; // head 的 index 为 0 // 在 0 ~ position 之间遍历,不断地更新 currentNode 和 previousNode // 直到找到要插入的位置 while (index++ < position) { previousNode = currentNode; currentNode = currentNode.next; } // 在当前节点和当前节点的上一节点之间插入新节点,即它们的改变指向 newNode.next = currentNode; previousNode.next = newNode; } // 更新链表长度 this.length++; return newNode; } -
getData
getData(position) { // 1、position 越界判断 if (position < 0 || position >= this.length) return null; // 2、获取指定 position 节点的 data let currentNode = this.head; let index = 0; while (index++ < position) { currentNode = currentNode.next; } // 3、返回 data return currentNode.data; } -
indexOf(data),返回元素的index
// indexOf(data) 返回指定 data 的 index,如果没有,返回 -1。 indexOf(data) { let currentNode = this.head; let index = 0; while (currentNode) { if (currentNode.data === data) { return index; } currentNode = currentNode.next; index++; } return -1; } -
update(position, data) 修改指定位置节点的 data
update(position, data) { // 涉及到 position 都要进行越界判断 // 1、position 越界判断 if (position < 0 || position >= this.length) return false; // 2、痛过循环遍历,找到指定 position 的节点 let currentNode = this.head; let index = 0; while (index++ < position) { currentNode = currentNode.next; } // 3、修改节点 data currentNode.data = data; return currentNode; } -
removeAt(position) 删除指定位置的节点,并返回删除的那个节点
removeAt(position) { // 1、position 越界判断 if (position < 0 || position >= this.length) return null; // 2、删除指定 position 节点 let currentNode = this.head; if (position === 0) { // position = 0 的情况 this.head = this.head.next; } else { // position > 0 的情况 // 通过循环遍历,找到指定 position 的节点,赋值到 currentNode let previousNode = null; let index = 0; while (index++ < position) { previousNode = currentNode; currentNode = currentNode.next; } // 巧妙之处,让上一节点的 next 指向到当前的节点的 next,相当于删除了当前节点。 previousNode.next = currentNode.next; } // 3、更新链表长度 -1 this.length--; return currentNode; } -
remove(data) 删除指定 data 的节点,并返回删除的那个节点
remove(data) { const index = this.indexOf(data); if(index === -1) return; return this.removeAt(index); } -
其他
// isEmpty() 判断链表是否为空 isEmpty() { return this.length === 0; } // size() 获取链表的长度 size() { return this.length; } -
toString() 方法
toString() { let currentNode = this.head; let result = ''; // 遍历所有的节点,拼接为字符串,直到节点为 null while (currentNode) { result += currentNode.data + ' '; currentNode = currentNode.next; } return result; }
-
双向链表
-
既可以从头遍历到尾,也可以从尾遍历到头。
-
链表相连的过程是双向的。实现原理是一个节点既有向前连接的引用,也有一个向后连接的引用。
-
双向链表可以有效的解决单向链表存在的问题。
-
双向链表缺点:
- 每次在插入或删除某个节点时,都需要处理四个引用,而不是两个,实现起来会困难些。
- 相对于单向链表,所占内存空间更大一些。
- 但是,相对于双向链表的便利性而言,这些缺点微不足道。
双向链表的封装
-
封装双向链表
// 继承单向链表的特性 import { LinkedList, Node } from '../LinkedList/linkedList'; // 双向链表的节点类(继承单向链表的节点类) class DoublyNode extends Node { constructor(element) { // 在父类中初始化data和next super(element); this.prev = null; } } // 双向链表类(继承单向链表类) export class DoublyLinkedList extends LinkedList { constructor() { // 继承父类中的属性和方法 super(); this.tail = null; } -
双向链表常见操作
-
append(element) 往双向链表尾部追加一个新的元素
append(element) { // 1、创建双向链表节点 const newNode = new DoublyNode(element); // 2、追加元素 if (this.head === null) { this.head = newNode; this.tail = newNode; } else { // !!跟单向链表不同,不用通过循环找到最后一个节点 // 巧妙之处 this.tail.next = newNode; newNode.prev = this.tail; this.tail = newNode; } this.length++; } -
insert(position, data) 插入元素
insert(position, element) { // 1、position 越界判断 if (position < 0 || position > this.length) return false; // 2、创建新的双向链表节点 const newNode = new DoublyNode(element); // 3、判断多种插入情况 if (position === 0) { // 在第 0 个位置插入 if (this.head === null) { this.head = newNode; this.tail = newNode; } else { //== 巧妙之处:相处腾出 this.head 空间,留个 newNode 来赋值 ==// newNode.next = this.head; this.head.perv = newNode; this.head = newNode; } } else if (position === this.length) { // 在最后一个位置插入 this.tail.next = newNode; newNode.prev = this.tail; this.tail = newNode; } else { // 在 0 ~ this.length 位置中间插入 let targetIndex = 0; let currentNode = this.head; let previousNode = null; // 找到要插入位置的节点 while (targetIndex++ < position) { previousNode = currentNode; currentNode = currentNode.next; } // 交换节点信息 previousNode.next = newNode; newNode.prev = previousNode; newNode.next = currentNode; currentNode.prev = newNode; } this.length++; return true; } -
getData() 继承单向链表
getData(position) { return super.getData(position); } -
indexOf() 继承单向链表
indexOf(data) { return super.indexOf(data); } -
removeAt() 删除指定位置的节点
removeAt(position) { // 1、position 越界判断 if (position < 0 || position > this.length - 1) return null; // 2、根据不同情况删除元素 let currentNode = this.head; if (position === 0) { // 删除第一个节点的情况 if (this.length === 1) { // 链表内只有一个节点的情况 this.head = null; this.tail = null; } else { // 链表内有多个节点的情况 this.head = this.head.next; this.head.prev = null; } } else if (position === this.length - 1) { // 删除最后一个节点的情况 currentNode = this.tail; this.tail.prev.next = null; this.tail = this.tail.prev; } else { // 删除 0 ~ this.length - 1 里面节点的情况 let targetIndex = 0; let previousNode = null; // 注意循环的条件 while (targetIndex++ < position) { previousNode = currentNode; currentNode = currentNode.next; } previousNode.next = currentNode.next; currentNode.next.perv = previousNode; } this.length--; return currentNode.data; } -
update(position, data) 修改指定位置的节点
update(position, data) { // 1、删除 position 位置的节点 const result = this.removeAt(position); // 2、在 position 位置插入元素 this.insert(position, data); return result; } -
其他
// remove(data) 删除指定 data 所在的节点(继承单向链表) remove(data) { return super.remove(data); } // isEmpty() 判断链表是否为空 isEmpty() { return super.isEmpty(); } // size() 获取链表的长度 size() { return super.size(); }
-