链表结构
链表(Linked List)是一种常见的线性数据结构,用于存储和组织数据。与数组不同,链表中的元素在内存中可以不连续存储,而是通过每个元素中的指针链接彼此。
链表由一系列节点(Node)组成,每个节点包含两部分:数据域(存储数据的部分)和指针域(指向下一个节点的指针)。数据域可以存储任意类型的数据,而指针域用于链接下一个节点。
常见的链表类型包括单向链表、双向链表和循环链表:
- 单向链表(Singly Linked List):每个节点只有一个指针,指向下一个节点。最后一个节点的指针指向空值(null)表示链表的结束。
与数组的不同
- 要存储多个元素,数组(或选择链表)可能是最常用的数据结构。一般编程语言都有默认实现数组结构。
但是数组也有很多缺点:
-
数组的创建通常需要申请一段连续的内存空间(一整块的内存),并且大小是固定的(大多数编程语言数组都是固定的),所以当当前数组不能满足容量需求时,需要扩容。 (一般情况下是申请一个更大的数组,比如2倍。 然后将原数组中的元素复制过去)
-
而且在数组开头或中间位置插入数据的成本很高,需要进行大量元素的位移。
-
尽管JavaScript的Array底层可以帮我们做这些事,但背后的原理依然是这样。
链表的优势
要存储多个元素,另外一个选择就是 链表
-
内存空间不是必须连续的。 可以充分利用计算机的内存,实现灵活的内存动态管理。
-
链表不必在创建时就确定大小,并且大小可以无限的延伸下去。
-
链表在插入和删除数据时,时间复杂度可以达到O(1)。 相对数组效率高很多。
相对于数组,链表缺点:
- 随机访问低效:链表中的元素并不是连续存储的,因此无法通过索引直接访问元素,需要从头节点开始遍历,直到找到目标节点,这导致随机访问的效率较低。
- 额外的空间开销:链表需要额外的指针来存储节点之间的链接关系,这增加了一定的空间开销。
- 不支持常数时间的随机访问和索引操作:与数组不同,链表不支持在常数时间内根据索引访问元素,需要遍历链表直到目标位置。
封装链表
// 创建 Node节点
class Node<T> {
// 定义value 以及 next方法
value: T | null = null;
next: Node<T> | null = null;
constructor(value: T) {
this.value = value;
}
// 创建 LinkedList 的类
class LinkedList<T> {
head: Node<T> | null = null;
private size: number = 0;
get length() {
return this.size;
}
链表中常见的方法
链表中常用的方法
append(element):向链表尾部添加一个新的项
insert(position,element):向链表的特定位置插入一个新的项。
get(position) :获取对应位置的元素
indexOf(element):返回元素在链表中的索引。如果链表中没有该元素则返回-1。
update(position,element) :修改某个位置的元素
removeAt(position):从链表的特定位置移除一项。
remove(element):从链表中移除一项。
isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
size():返回链表包含的元素个数。与数组的length属性类似。
链表的遍历方法(traverse)
为了可以方便的看到链表上的每一个元素,我们实现一个遍历链表每一个元素的方法:
-
这个方法首先将当前结点设置为链表的头结点。
-
然后,在while循环中,我们遍历链表并打印当前结点的数据。
-
在每次迭代中,我们将当前结点设置为其下一个结点,直到遍历完整个链表。
traverse() {
const values: T[] = [];
let current = this.head;
while (current) {
values.push(current.value!);
current = current.next;
}
console.log(values.join("->"));
}