基本概念
链表是线性表的⼀种,所谓的线性表包含顺序线性表和链表,顺序线性表是⽤数组实现的,在内存中有顺序排列,通过改变 数组⼤⼩实现。⽽链表不是⽤顺序实现的,⽤指针实现,在内存中不连续。意思就是说,链表就是将⼀系列不连续的内存联系起来,将那种碎⽚内存进⾏合理的利⽤,解决空间的问题。
单向链表
/**
* 链表
*/
function defaultEquals(a, b) {
return a === b;
}
// 需要添加到链表中的项,包含element和next,element是当前元素,next是当前元素的下一个元素
export class Node {
element: any;
next: any;
constructor(element: any) {
this.element = element;
this.next = null;
}
}
// 单链表
export class LinkedList {
head: any;
count: number; // 当前链表数据量(长度)
equalsFn: (a: any, b: any) => boolean;
constructor(equalsFn = defaultEquals) {
this.count = 0;
// 保存第一个元素的引用
this.head = null;
// 查找数据时需要判断数据是否相等
this.equalsFn = equalsFn;
}
// 指定位置添加一个元素
insert(element: any, index: number) {
if (index >= 0 && index <= this.count) {
const node = new Node(element);
const current = this.head;
if (index === 0) {
node.next = current;
this.head = node;
} else {
// 拿到要插入的位置的前一项
const prev = this.getElementAt(index - 1);
// 获取到插入位置的下一项
const current = prev.next;
// 将前一项的下一项指向添加的元素
prev.next = node;
// 将添加的元素的下一项指向插入位置的下一项
node.next = current;
}
this.count++;
return node;
}
return undefined;
}
// 向链表尾部添加一个元素
push(element: any) {
const node = new Node(element);
let current: Node;
// 如果当前链表为空
if (!this.head) {
this.head = node;
} else {
// 当前链表不为空
current = this.head;
// 从第一项开始循环,找到最后一个,然后添加
while (current.next !== null) {
current = current.next;
}
// 找到最后一项,进行保存
current.next = node;
}
// 元素添加完成了,需要将count进行修改
this.count++;
}
// 移除链表中的一个元素
remove(element: any) {
// 在indexOf方法完成后,remove就水到渠成了
const index = this.indexOf(element);
return this.removeAt(index);
}
// 查找链表中特定元素的位置索引
indexOf(element: any) {
// 当前链表长度为0
if (this.isEmpty()) {
return;
}
let current = this.head;
for (let i = 0; i < this.count && current !== null; i++) {
if (this.equalsFn(element, current.element)) {
return i;
}
current = current.next;
}
// 没有找到返回-1
return -1;
}
// 从链表的特定位置移除一个元素
removeAt(index: number) {
// 根据传入的index来移除元素
if (index >= 0 && index < this.count) {
// 不论移除或添加都需要从第一项开始进行查询
let current = this.head;
if (index === 0) {
// 要移除第一项
this.head = current.next;
} else {
// 获取前一个node
let prev: Node = this.getElementAt(index - 1);
current = prev.next;
// 将前一项跟后一项进行连接,跳过被移除的元素即可实现
prev.next = current.next;
}
// 记得要将count减少
this.count--;
// 删除成功后将被移除的元素返回
return current.element;
}
// 边界场景,传入的index不合法
return undefined;
}
// 获取指定元素的方法封装
getElementAt(index: number) {
if (index >= 0 && index <= this.count) {
let node = this.head;
for (let i = 0; i < index && node !== null; i++) {
node = node.next;
}
return node;
}
return undefined;
}
// 链表是否为空
isEmpty() {
return this.count === 0;
}
// 链表中的元素个数
size() {
return this.count;
}
}
双向链表
链表有多种不同的类型,本节介绍双向链表。双向链表和普通链表的区别在于,在链表中,一个节点只有链向下一个节点的链接;而在双向链表中,链接是双向的:一个链向下一个元素,另一个链向前一个元素。
// 双向链表
// 需要添加到链表中的项,包含element和next,element是当前元素,next是当前元素的下一个元素,与单向链表不同的是,添加了prev用来记录当前元素的前一个元素。所以双向链表相比于单向链表更加灵活
export class DoubleNode extends Node {
prev: DoubleNode;
constructor(element) {
super(element);
this.prev = null;
}
}
export class DoubleLinkedList extends LinkedList {
tail: DoubleNode; // 保存最后一个元素的引用
constructor(equalsFn = defaultEquals) {
super(equalsFn);
this.tail = null;
}
// 需要将insert重写,因为涉及到前后
insert(element: any, index: number): boolean {
if (index >= 0 && index <= this.count) {
let node = new DoubleNode(element);
let current = this.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;
previous.next = node;
current.prev = node;
node.next = current;
node.prev = previous;
}
this.count++;
return true;
}
return false;
}
removeAt(index: number) {
if (index >= 0 && index < this.count) {
let current = this.head;
if (index === 0) {
this.head = current.next;
if (this.count === 1) {
this.tail = null;
} else {
this.head.prev = null;
}
} else if (index === this.count - 1) {
// 删除最后一项
current = this.tail;
this.tail = current.prev;
this.tail.next = null; // 最后一项没有下一项了
} else {
// 拿到当前项
current = this.getElementAt(index);
const previous = current.prev;
previous.next = current.next; // 前一项的下一项改为当前项的下一项
current.next.prev = previous; // 下一项的前一项改为当前项的前一项
// 经过上面的步骤即可跳过当前项,从而实现移出某一项的功能
}
this.count--;
return current.element;
}
return undefined;
}
}
循环链表/双向循环链表
循环链表可以像链表一样只有单向引用,也可以像双向链表一样有双向引用。循环链表和链
表之间唯一的区别在于,最后一个元素指向下一个元素的指针(tai1.next)不是引用
undefined,而是指向第一个元素(head)
双向循环链表有指向head元素的tail.next和指向tail元素的head.prev。
在这里就不用代码来实现了,感兴趣可以自己在上面的基础上实现一下。
有序链表
有序链表是指保持元素有序的链表结构。除了使用排序算法之外,我们还可以将元素插入到
正确的位置来保证链表的有序性。