笔者表达能力不太行,不会描述更多硬知识,但会解释每一步代码的操作逻辑,更多的是做个记录方便在外面看, 持续更新…
4.1 —— 简述
- 链表存储有序的元素集合,链表中的元素在内存中并不是连续放置的,每个元素由一个存储元素本身的节点和一个指向下一个元素的引用组成.
- 相对于数组,链表的添加或移除元素的操作不需要移动其他元素
- 链表需要指针,想要访问链表中的一个元素,需要从头节点(起点)开始迭代链表查找
【第三点很重要,刷力扣时传的头节点】
4.2 —— 实现
4.3:链表节点
链表里的元素都是存储在节点中的,形象的说,一个节点就是一个容器,里面存放了数据,和一个对外的链子,链子连着下一个容器
class Node {
constructor(value) {
this.value = value;
this.next = null;
}
}
4.4:单向链表
链表的第一个节点称为头节点,用head标记头节点,count记录节点的数量
class LinkedList {
constructor() {
this.head = null;
this.count = 0;
};
}
4.4.1:size 方法
返回链表包含的元素个数,返回count值
size() {
return this.count;
}
4.4.2:isEmpty 方法
判断链表是否为空, 链表为空返回true,不为空返回false
isEmpty() {
return this.size() === 0;
}
4.4.3:indexOf 方法
查找特定的元素的位置,首先判断链表是否有节点,如果为空返回-1; 创建一个指针current, 默认指向头节点,判断当前节点的元素是否为传入的element,不是则指向当前节点的下一个节点,以此类推,找到了返回目标元素在链表中的位置,不存在返回-1;
indexOf(element) {
if (this.head) {
let current = this.head;
for (let i=0; i<this.count; i++) {
if (element === current.value) {
return i;
};
current = current.next;
}
}
return -1;
}
4.4.4:push方法
添加元素到链表尾部: 首先将要添加的元素element放在node容器中,判断头节点是否为空,为空则直接将element作为头节点插入,因为node节点的next指向null,所以最后一个节点的指针不需要操作,默认指向null; 如果不为空,从头节点开始往下找,直到找到next指向null的节点,那必定是最后一个节点,然后将最后节点的next指针指向要插入的node节点即可。
push(element) {
const node = new Node(element);
if (!this.head) {
this.head = node;
} else {
let current = this.head;
while (current.next) {
current = current.next;
}
current.next = node;
}
this.count++;
}
4.4.5:getElement 方法
该方法接收一个索引index,返回链表中index对应的node节点, node不存在返回null
- 首先越界判断,判断index是否符合条件,index应该大于0,且小于节点数量;
- 创建一个指针node指向头节点,从头节点依次往下传递指针,直到找到index对应的节点,返回该节点供调用者接收
getElementAt(index) {
if (index >= 0 && index <= this.count) {
let node = this.head;
for (let i=0; i<index; i++) {
node = node.next;
}
return node;
}
return null;
}
4.4.6:insert方法
向链表中任意位置插入元素:insert方法接收两个参数: 要插入的值element和要插入的位置index;
- 首先判断index是否符合条件,如果index不合理则直接返回false表示插入失败;
- 第一种情况,
index === 0 在头部插入节点,标记当前节点(头节点)将要插入的节点的指针指向标记的节点(头节点)再将要插入的节点作为头节点插入; - 第二种情况,
index !== 0 在头部意外的任意地方插入, 首先找到目标位置index的前一个节点index - 1 标记为previous,目标位置的节点标记为current,然后将要插入的节点node指向目标位置的节点(插入后目标current的位置会往右挪一位),最后将previous的指针指向要插入的节点node 【例:现在有两个人A连着B;现在要插入C,首先将C连接B, 由于A还是连接着B所以指针不会丢失,再将A连接C,这时A不再与B连接,A -> C -> B 】
insert(element, index) {
if (index >= 0 && index <= this.count) {
const node = new Node(element);
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;
}
4.4.7:removeAt 方法
该方法接收一个索引index,移除index对应的节点的元素 【如果节点直接没有指针连接,则会自动被js垃圾回收机制回收,说白了,就是自动删除】
- 首先判断index是否符合条件,如果index不合理则直接返回false表示插入失败;
- 创建一个指针current, 默认指向头节点 (从头节点开始)
- 第一种情况,
index === 0 移除头节点,直接将头节点指向current的下一个节点(头节点的下一个节点) - 第二种情况,
index !== 0 移除头节点外的任意节点, 首先找到当前位置index对应的节点current 以及前一个节点previous (index - 1),再将previous的指针指向current指向的节点, 【例:现在有三个人依次连接A -> B -> C ;删除B后,A -> C;即 A相当于previous ,B相当于current ,C相当于current.next 】
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.value;
}
return -1;
}
4.4.8:remove 方法
该方法接收一个值element,移除链表中的element 使用现成的方法,
- 首先调用indexOf方法找到element在链表中的位置,
- 然后根据返回的索引index调用removeAt方法删除对应的元素,接收removeAt返回的值,
- 最后判断返回的值,操作成功返回elelment对应的索引值,操作失败返回-1;
remove(element) {
const index = this.indexOf(element);
const value = this.removeAt(index);
return value == -1 ? -1 : index;
}
4.5 双向链表
4.5.1 双向链表的节点
相对于单向链表的节点,双向链表的节点需要两个指针,分别指向头部和尾部的节点
class DoublyNode {
constructor(value, next, prev) {
this.value = value;
this.next = next;
this.prev = prev;
}
}
4.5.2 创建双向链表
这里使用es6中的继承,除了插入和删除操作,其他方法与单向链表一样,可以复用,双向链表的构造函数多了一个tail属性,它总是指向链表的最后一个节点
class DoublyLinkedList extends LinkedList {
constructor() {
this.head = null;
this.count = 0;
this.tail = null;
}
}
4.5.3 双向链表的insert方法
双向链表麻烦在于处理两个指针,操作不当容易指针容易丢失,在纸上画图会清晰一点,直接上注释
insert(element, index) {
if (index >= 0 && index <= thiscount) {
const node = new DoublyNod(element);
let current = this.head;
if (index == 0) {
if (!this.head) {
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;
previous.next = node;
current.prev = node;
node.prev = previous;
};
this.count++;
return true;
}
return false;
}
4.5.4 双向链表的removeAt方法
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 = 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.getElementA(index);
const previous = current.prev;
previous.next = current.next;
current.next.prev = previous;
}
this.count--;
return current.value;
}
return null;
}
4.6 循环链表
循环链表的结构与双向链表结构一样也是有两个节点,直接继承,不同的是,循环链表的尾指针指向head头节点,不再指向null
class CircularLinkedList extends LinkedList {}
原理跟双向链表差不多,画一画就清晰了
4.6.1 循环链表的 insert 方法
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 = current; /node节点指针指向头节点
current = this.getElementAt(this.size();
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;
}
4.6.2 循环链表的 removeAt方法
removeAt(index) {
if (index >= 0 && index < this.count) {
let current = this.head;
if (index == 0) {
if (this.size() == 1) {
this.head = null;
} else {
const removed = this.head;
current = this.getElementAt(this.size();
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.value;
}
return null;
}
下一章,将会写一些js的操作方法,做个归类
|