很多前端的同学对数据结构和算法这块没有太多的概念,很多leetcode的题目看不懂,有时候可能看了题解也不知道是什么意思。那么在做题之前了,首先需要对一些基本的数据结构有了解。这篇文章咱们来简单的谈一谈链表。
什么是链表
定义:链表是一种递归的数据结构,它或者空(null),或者是指向一个结点(node)的引用,该结点含有一个泛型的元素和一个指向另一条链表的引用。(摘抄自《算法第四版》)不是很好的理解?下面来简单的解释一下。
数组大家肯定都知道。我们想要存储多个元素的时候,数组可能是比较常见的一种数据结构。但是不知道大家有没有考虑过这一点,数组的大小是固定的,如果想要给数组中随意的插入或者移除元素的时候,则需要移动其他的元素,那么成本是很高的。
而链表是有序的元素集合,与数组不同的是,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和一个指向下一个元素的引用(也称指针或链接)组成。那么链表相对于数组而言它的好处在于,添加或者移除元素的时候,不需要移动其他的元素。
那么再来说说查找,数组中可以通过索引直接访问任何位置的元素,但是链表则需要从起点开始迭代链表直到找到要查找的元素。下面我们用一张图来标示链表:
JS实现一个链表
下面我们用js实现一个链表 大家在刷leetcode链表的题目的时候,是不是都会看到这样的代码
这个其实就是创建链表每个节点的构造函数。这里我们用类来实现
Node类
js
class Node {
constructor(val) {
this.val = val;
this.next = null;
}
}
创建链表类
js
class LinkNodeList {
constructor() {
this.head = null;
this.count = 0;
}
}
head表示头节点,count表示链表节点个数。 我们需要实现哪些方法呢?
- push(element):向链表尾部添加一个新元素。
- insert(element, position):向链表的特定位置插入一个新元素。
- getVal(index):返回链表中特定位置的元素。如果链表中不存在这样的元素,则返回undefined。
- remove(position):从链表的特定位置移除一个元素。
- isEmpty():如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
- size():返回链表包含的元素个数,与数组的length属性类似。
- toString():返回表示整个链表的字符串。
size
js
class LinkNodeList {
constructor() {
this.head = null;
this.count = 0;
}
···
size() {
return this.count;
}
···
}
用count记录节点个数,直接返回count即可
isEmpty
js
class LinkNodeList {
constructor() {
this.head = null;
this.count = 0;
}
···
isEmpty() {
return this.size() === 0;
}
···
}
直接判断size 是否为0即可
push
向链表尾部添加一个新元素。
js
class LinkNodeList {
constructor() {
this.head = null;
this.count = 0;
}
···
push(element) {
const node = new Node(element);
if (this.head === null) {
this.head = node;
} else {
let current; = this.head;
while (current.next) {
current = current.next;
}
current.next = node;
}
this.count++;
}
···
}
如果想链表的尾部添加一个元素,那么首先需要通过上面实现的Node类来创建一个节点。如果当前的head还不存在,则说明当前的链表还是一个空,所以直接将head节点赋值即可。否则就找到链表的最后一个节点,将它的next指向新push的节点即可。并且将count增加
getVal
返回链表中特定位置的元素
js
class LinkNodeList {
constructor() {
this.head = null;
this.count = 0;
}
···
getVal(index) {
if (index > 0 && index <= this.count) {
let node = this.head;
for (let i = 0; i < index; i++) {
node = node.next;
}
return node;
}
return undefined;
}
···
}
给getVal传入我们想要查找的位置,首先判断要查找的位置是否存在,如果不存在则直接返回undefined.
存在的话,则从头节点开始循环查找,直到找到目标index。结束循环时,node元素就是index位置元素的引用。
insert(element, position)
向链表的特定位置插入一个新元素。
js
class LinkNodeList {
constructor() {
this.head = null;
this.count = 0;
}
···
insert(ele, index) {
if (index >= 0 && index <= this.count) {
const node = new Node(ele);
if (index === 0) {
const current = this.head;
node.next = current;
this.head = node;
} else {
const prev = this.getVal(index - 1);
node.next = prev.next;
prev.next = node;
}
this.count++;
return true;
}
return false;
}
···
}
首先需要判断要插入的位置是否存在,如果不存在则直接返回false,表示不能插入
如果存在,则说明可以插入,那么创建一个节点。如果需要插入的位置为头节点,那么需要将新创建的节点的next指向头节点即可。
如果需要插入的是其他位置,则获取需要插入位置的前一个节点prev,将当前新插入的节点的next,指向prev的下一个节点。然后再将prev的next重新指向需要插入的节点node即可。
只要是有效的插入元素,都需要对count更新
remove(position)
从链表中移除一个元素。
js
class LinkNodeList {
constructor() {
this.head = null;
this.count = 0;
}
···
remove(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
// 移除第一项
if (index === 0) {
this.head = current.next;
} else {
const prev = this.getVal(index - 1);
current = prev.next;
prev.next = current.next;
}
this.count--;
return current.val;
}
return undefined;
}
···
}
首先要判断需要移除的值是否存在,如果不存在则返回undefined
如果移除的是第一个节点,则直接将头节点指向下一个节点即可。
如果是其他位置的节点,则首先获取需要移除位置节点的前面一个节点prev,将prev的next,指向下一个,再下一个节点即可
只要是有效的移除元素,都需要对count更新
toString()
返回表示整个链表的字符串。由于列表项使用了Node类,就需要重写继承自JavaScript对象默认的toString方法,让其只输出元素的值。
js
class LinkNodeList {
constructor() {
this.head = null;
this.count = 0;
}
···
toString() {
if (this.head === null) {
return "";
}
let objString = `${this.head.val}`;
let current = this.head.next;
for (let i = 1; i < this.size() && current !== null; i++) {
objString = `${objString}=>${current.val}`;
current = current.next;
}
return objString;
}
···
}
如果头节点不存在,则说明是个空链表则直接返回空
接下来要做的事情就是将链表的每个元素通过"=>"字符串拼接。下面我们看下效果。
欢迎大家点赞评论,大家一起学习一起进步!!!