链表
1. 概念:
链表(Linked list)是一种上一个元素的引用指向下一个元素的存储结构,链表通过指针来连接元素与元素;
链表是线性表的一种,所谓的线性表包含顺序线性表和链表,顺序线性表是用数组实现的,在内存中有顺序排列,通过改变数组大小实现。而链表不是用顺序实现的,用指针实现,在内存中不连续。意思就是说,链表就是 将一系列不连续的内存联系起来,将那种碎片内存进行合理的利用,解决空间的问题。
所以,链表允许插入和删除表上任意位置上的节点,但是不允许随即存取。链表有很多种不同的类型: 单向链表、双向链表及循环链表。
2. 与数组的区别:
- 相同:
两种结构均可实现数据的顺序存储,构造出来的模型呈线性结构。
- 不同:
- 链表是链式的存储结构;数组是顺序的存储结构。
- 链表通过指针来连接元素与元素,数组则是把所有元素按次序依次存储。
- 链表的插入删除元素相对数组较为简单,不需要移动元素,且较为容易实现长度扩充,但是寻找某个元素较为困难。
- 数组寻找某个元素较为简单,但插入与删除比较复杂,由于最大长度需要再编程一开始时指定,故当达到最大长度时,扩充长度不如链表方便。
数组和链表一些操作的时间复杂度对比:
数组:
- 查找复杂度:O(1)
- 添加/删除复杂度:O(n)
链表:
- 查找复杂度:O(n)
- 添加/删除复杂度:O(1)
3. 生活中的案例:
火车,是由一些列车厢连接起来
寻宝游戏,每个线索都是下一个线索地点的指针。
单向链表的实现
// 单向链表
class Node {
constructor(element) {
this.element = element; //当前节点的元素
this.next = null; //下一个节点链接
}
}
class LinkedList {
constructor() {
this.head = new Node("head");
}
// 寻找节点
find(item) {
var currNode = this.head;
while (currNode.element != item) {
currNode = currNode.next;
}
return currNode;
}
// 在 item 之后插入 newElement 节点
insert(newElement, item) {
var newNode = new Node(newElement);
var currNode = this.find(item);
newNode.next = currNode.next;
currNode.next = newNode;
}
// 显示链表
display() {
var currNode = this.head;
while (currNode.next !== null) {
console.log(currNode.next.element);
console.log(t);
currNode = currNode.next;
}
}
// 找前一个
findPrev(item) {
var currNode = this.head;
while (!(currNode.next == null) && currNode.next.element != item) {
currNode = currNode.next;
}
return currNode;
}
// 移除
remove(item) {
var prevNode = this.findPrev(item);
if (!(prevNode.next == null)) {
prevNode.next = prevNode.next.next;
}
}
}
var fruits = new LinkedList();
fruits.insert("Apple", "head");
fruits.insert("Banana", "Apple");
fruits.insert("Pear", "Banana");
console.log(fruits);
双向链表的实现
双向链表和普通链表的区别在于,在链表中, 一个节点只有链向下一个节点的链接,而在双向链表中,链接是双向的:一个链向下一个元素, 另一个链向前一个元素
// 链表节点
class Node {
constructor(element) {
this.element = element;
this.prev = null;
this.next = null;
}
}
// 双向链表
class DoublyLinkedList {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
// 任意位置插入元素
insert(position, element) {
if (position >= 0 && position <= this.length) {
const node = new Node(element);
let current = this.head;
let previous = null;
let index = 0;
// 首位
if (position === 0) {
if (!head) {
this.head = node;
this.tail = node;
} else {
node.next = current;
this.head = node;
current.prev = node;
}
// 末位
} else if (position === this.length) {
current = this.tail;
current.next = node;
node.prev = current;
this.tail = node;
// 中位
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
node.next = current;
previous.next = node;
current.prev = node;
node.prev = previous;
}
this.length++;
return true;
}
return false;
}
// 移除指定位置元素
removeAt(position) {
if (position > -1 && position < this.length) {
let current = this.head;
let previous = null;
let index = 0;
// 首位
if (position === 0) {
this.head = this.head.next;
this.head.prev = null;
if (this.length === 1) {
this.tail = null;
}
// 末位
} else if (position === this.length - 1) {
this.tail = this.tail.prev;
this.tail.next = null;
// 中位
} else {
while (index++ < position) {
previous = current;
current = current.next;
}
previous.next = current.next;
current.next.prev = previous;
}
this.length--;
return current.element;
} else {
return null;
}
}
// 其他方法...
}