链表
1. 链表数据结构
概念:链表存储有序数据集合,每个元素由一个存储元素本身数据的节点和指向下一个元素的引用(指针)组成。
数组:访问数据效率高,添加删除元素需要移动元素,开销大。
链表:添加删除元素时不需要移动元素,效率更高,但访问数据时要从第一个节点开始迭代,开销更大。
1.1 创建链表
为了表示链表中的元素,需要一个助手类 Node
:
javascript
class Node {
constructor(element) {
// 保持当前元素数据
this.element = element;
// 指向下一元素
this.next = undefined;
}
}
链表 LinkedList
类,包含一些方法:
push(element)
:向链表尾部添加一个元素。insert(element, position)
:向链表特定位置插入新元素。getElementAt(index)
:返回链表中特定位置的元素。如果不存在则返回undefined
。remove(element)
:从链表中移除一个元素。indexOf(element)
:返回元素在链表中的索引。不存在则返回 -1。removeAt(position)
:从链表的特定位置移除一个元素。isEmpty()
:如果链表不包含任何元素则返回true
,否则返回false
。size()
:返回链表中元素个数。toString()
:返回表示整个链表的字符串。
javascript
export default class LinkedList {
constructor() {
this.count = 0;
// 头指针
this.head = undefined;
}
// 向尾部添加元素
push(element) {
const node = new Node(element);
let current;
if(this.head == null) {
this.head = node;
} else {
current = this.head;
while(current.next != null) {
current = current.next;
}
// 将 next 赋为新元素
current.next = node;
}
this.count++;
}
// 从链表中移除元素
removeAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
// 第一项
if(index == 0) {
this.head = current.next;
} else {
const previous = this.getElementAt(index - 1)
current = previous.next;
previous.next = current.next;
}
this.count--;
return current.element;
}
return undefined;
}
// 返回链表中特定位置的元素
getElementAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
for (let i = 0; i < index; i++) {
current = current.next;
}
return current;
}
return undefined;
}
// 在任意位置插入元素
insert(element, index) {
if (index >= 0 && index <= this.count) {
const node = new Node;
if (index === 0) {
const current = this.head;
node.next = current;
this.head = node;
} else {
const previous = this.getElementAt(index - 1);
const current = previous.next;
node.next = current;
previous.next = node;
}
this.count++;
return true;
}
return false;
}
// 返回第一个匹配元素的位置
indexOf(element) {
let current = this.head;
for(let i = 0; i < this.count; i++) {
if(element === current.element) {
return i;
}
}
return -1;
}
// 从链表中移除第一个匹配元素
remove(element) {
let index = indexOf(element);
return this.removeAt(index)
}
size() {
return this.count;
}
isEmpty() {
return this.size() === 0;
}
getHead() {
return this.head;
}
toString() {
if(this.head == null) {
return ''
}
let result = this.head.element;
let current = this.head.next;
for(let i = 1; i < this.count; i++) {
result = `${result},${current.element}`
current = current.next
}
return result;
}
}
2. 双向链表
双向链表中每个节点有两个指针,一个指向下一个元素,一个指向上一个元素。
DoublyLinkedList
类:
javascript
class DoublyNode extends Node {
constructor(element, next, prev) {
super(element, next);
this.prev = prev;
}
}
class DoublyLinkedList extends LinkedList {
constructor() {
super();
this.tail = undefined;
}
}
2.1 在任意位置插入新元素
双向链表插入与单链表类似:
javascript
insert(element, index) {
if (index >= 0 && index <= this.count) {
const node = new DoublyLinkedList(element);
let current = head;
if (index == 0) {
if(this.head == null) {
this.head = node;
this.tail = node;
} else {
node.next = this.head;
current.prev = node;
this.head = node;
}
} else if(index == this.count) {
current = this.tail;
current.next = node;
node.prev = current;
this.tail = node;
} else {
const previous = this.getElementAt(index - 1);
current = previous.next;
node.next = current;
current.prev = node;
previous.next = node;
node.prev = previous;
}
this.count++;
return true;
}
return false;
}
2.2 从任意位置移除元素
javascript
removeAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
if (index == 0) {
this.head = current.next;
if (this.count == 1) {
this.tail = undefined;
} else {
this.head.prev = undefined;
}
} else if (index == this.count - 1) {
current = this.tail;
this.tail = current.prev;
this.tail.next = undefined;
} else {
current = this.getElementAt(index);
current.prev.next = current.next;
current.next.prev = current.prev;
}
this.count--;
return current.element;
}
return false;
}
3. 循环链表
循环链表与普通链表的唯一区别就是循环链表最后一个节点的 next
指向第一个节点。
CircularLinkedList
类:
javascript
class CircularLinkedList extends LinkedList {
// 不需要任何额外属性
constructor() {
super()
}
}
3.1 在任意位置插入新元素
插入逻辑跟普通链表一样,只不过需要将最后一个元素的 next
指向第一个节点:
javascript
insert(element, index) {
if(index >= 0 && index <= this.count) {
const node = new Node(element)
let current = this.head;
if(index == 0) {
if (this.head == null) {
this.head = node;
node.next = this.head;
} else {
node.next = this.head;
// 处理最后一个元素
current = this.getElementAt(this.count - 1);
this.head = node;
current.next = this.head;
}
} else {
const previous = this.getElementAt(index - 1);
node.next = previous.next;
previous.next = node;
}
this.count++;
return true;
}
return false;
}
3.2 从任意位置移除元素
javascript
removeAt(index) {
if(index >= 0 && index < this.count) {
let current = this.head;
if(index == 0) {
if(this.count == 1) {
this.head = undefined;
} else {
const removed = this.head;
current = this.getElemmentAt(this.count - 1);
this.head = this.head.next;
current.next = this.head;
current = removed;
}
} else {
const previous = this.getElementAt(index - 1);
current = previous.next;
previous.next = current.next;
}
this.count--;
return current.element;
}
return false;
}
4. 有序链表
元素插入到正确的位置来保证有序性。
SortedLinkedList
类:
javascript
const Compare = {
LESS_TAEN: -1,
BIGGER_TAEN: 1
}
function defaultCompare(a, b) {
if (a === b) {
return 0;
}
return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN;
}
class SortedLinkedList extends LinkedList {
constructor(compareFn = defaultCompare) {
super();
this.compareFn = compareFn
}
}
4.1 有序插入元素
javascript
// 重写 insert 方法
insert(element, index = 0) {
if (this.isEmpty()) {
return super.insert(element, 0);
}
const pos = this.getIndexNextSortedElement(element);
return super.insert(element, pos)
}
getIndexNextSortedElement(element) {
let current = this.head;
let i = 0;
for(; i < this.size() && current; i++) {
const comp = this.compareFn(element, current.element);
if(comp === Compare.LESS_THAN) {
return i;
}
current = current.next;
}
return i;
}
5. 创建 StackLinkedList 类
可以使用链表作为内部数据结构来创建其它数据结构,如栈、队列和双向队列。
StackLinkedList
类:
javascript
class StackLinkedList {
constructor() {
this.items = new DoublyLinkedList();
}
push(element) {
this.item.push(element);
}
pop() {
if(this.isEmpty()) {
return undefined;
}
return this.items.removeAt(this.size() - 1);
}
peek() {
if(this.isEmpty()) {
return undefined;
}
return this.items.getElementAt(this.size() - 1).element;
}
isEmpty() {
return this.items.isEmpty();
}
size() {
return this.items.size();
}
clear() {
this.items.clear();
}
toString() {
return this.items.toString();
}
}
说明
对于 StackLinkedList
类,使用双向列表 DoublyLinkedList
来存储数据,相比单链表,双向链表可以直接获取头尾元素,减少过程消耗,时间复杂度与原始栈实现相同,均为 O(1)
。