一、普通链表
链表存储有序的元素集合,但不同于数组,链表中的元素在内存中并不是连续放置的。每个元素由一个存储元素本身的节点和指向下一个元素的引用(也称指针或链接)组成。
创建链表,并实现一些方法:
- push(element): 向链表尾部添加一个新元素。
- insert(element, position): 向链表的特定位置添加一个新元素。
- getElementAt(index): 返回链表中特定位置的元素。如果链表中不存在这样的元素则返回undefined。
- remove(element): 从链表中移除一个元素。
- indexOf(element): 返回元素在链表中的索引。
- removeAt(position): 从链表的特定位置移除一个元素。
- isEmpty(): 如果链表中不包含任何元素,返回true,如果链表长度大于0则返回false。
- size(): 返回链表包含的元素个数,与数组的length属性类似。
- toString(): 返回整个链表的字符串。
// 比较两个值是否相等
function defaultEquals(a, b) {
return a === b;
}
// Node表示要添加到链表中的项
class Node {
constructor(element) {
// element 要加入链表元素的值
this.element = element;
// next 指向链表下一个元素的指针
// 链表最后一个节点的下一个元素始终是undefined或null
this.next = undefined;
}
}
class LinkedList {
constructor(equalsFn = defaultEquals) {
this.count = 0;
this.head = undefined;
this.equalsFn = equalsFn;
}
/* 添加分为两个场景:
* 1. 向空列表添加元素
* 2. 向不为空的链表尾部添加元素
*/
push(element) {
// 把element作为值传入,创建Node项
const node = new Node(element);
// 指向链表current项
let current;
// head元素为undefined或null,表示列表为空
if(this.head == null) {
// 向空列表添加元素,让head指向node,下一个node元素会自动成为undefined
this.head = node;
}else {
// 向一个不为空的链表尾部添加元素
current = this.head;
// 循环访问链表,当current.next元素为undefined或null时,找到链表尾部
while(current.next != null) {
current = current.next;
}
// 将最后一个元素的next指针指向想要添加到链表的节点
current.next = node;
}
this.count++;
}
/* 移除,两种场景:
* 1. 移除第一个元素
* 2. 移除第一个元素之外的元素
*/
removeAt(index) {
// 检查越界值
if(index >= 0 && index < this.count) {
// current作为链表第一个元素的引用
// current是要移除节点的引用
let current = this.head;
// 移除第一项
if(index === 0) {
// 将head指向链表的第二个元素
this.head = current.next;
}else {
// 指向当前元素的前一个元素
let previous;
for(let i=0;i<index;i++) {
previous = current;
current = current.next;
}
// 将previous与current的下一项链接起来,跳过current,从而删除它
previous.next = current.next;
}
this.count--;
return current.element;
}
// 布置有效位置返回undefined
return undefined;
}
getElementAt(index) {
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;
}
remove(index) {
// 检查越界值
if(index >= 0 && index < this.count) {
// current作为链表第一个元素的引用
// current是要移除节点的引用
let current = this.head;
// 移除第一项
if(index === 0) {
// 将head指向链表的第二个元素
this.head = current.next;
}else {
const previous = this.getElementAt(index-1);
current = previous.next;
previous.next = current.next;
}
this.count--;
}
}
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);
// current是想要插入元素的之后的一个元素的引用
const current = previous.next;
node.next = current;
previous.next = node;
}
}
}
indexOf(element) {
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;
}
return -1;
}
size() {
return this.count;
}
isEmpty() {
return this.size() === 0;
}
getHead() {
return this.head;
}
toString() {
if(this.head == null) {
return '';
}
let objString = `${this.head.element}`;
let current = this.head.next;
for(let i=0; i<this.size() && current != null; i++) {
objString = `${objString},${current.element}`;
current = current.next;
}
return objString;
}
}
const myLinked = new LinkedList();
myLinked.push(1);
myLinked.push(2);
myLinked.push(3);
myLinked.push(4);
myLinked.push(5);
console.log(myLinked.toString()); // 1,2,3,4,5
myLinked.remove(2);
console.log(myLinked.toString()); // 1,2,4,5
console.log(myLinked.removeAt(2)); // 4
console.log(myLinked.toString()); // 1,2,5
myLinked.insert(7, 2);
console.log(myLinked.toString()); // 1,2,7,5
console.log(myLinked.indexOf(7)); // 2
二、双向链表
双向链表和普通链表的 区别在于,在链表中,一个节点只有链向下一个节点的链接;而在双向链表中,链接是双向的;一个链向下一个元素,另一个链向前一个元素。
实现一个双向链表类DoublyLinkedList,它是一种特殊的LinkedList类,故我们需要扩展LinkedList类,使DoublyLinkedList继承LinkedList中所有的属性和方法。
class DoublyNode extends Node {
constructor(element, next, prev) {
super(element, next);
// 用来追踪每个节点的前一个节点
this.prev = undefined;
}
}
class DoublyLinkedList extends LinkedList {
constructor(equalsFn = defaultEquals) {
super(equalsFn);
// 用来保存链表最后一个元素的引用
this.tail = undefined;
}
// 向任意位置插入一个新元素
insert(element, index) {
if(index >= 0 && index <= this.count) {
const node = new DoublyNode(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;
node.next = current;
previous.next = node;
current.prev = node;
node.prev = previous;
}
this.count++;
return true;
}
return false;
}
// 从任意位置移除元素
removeAt(index) {
if(index >= 0 && index <= this.count) {
let current = this.head;
if(index === 0) {
this.head = current.next;
// 如果只有一项,更新tail
if(this.count === 1) {
this.tail = undefined;
}else {
this.head.prev = undefined;
}
}else if(index === this.count-1) {
current = this.tail;
this.tail = current.prev;
this.tail.prev = undefined;
}else {
current = this.getElementAt(index);
const previous = current.prev;
// 将previous与current的下一项链接起来——跳过current
previous.next = current.next;
current.next.prev = previous;
}
this.count--;
return current.element;
}
return undefined;
}
}
三、循环链表
循环链表可以向链表一样只有单向引用,也可以向双向链表一样有双向引用。循环链表与链表唯一的区别在于,最后一个元素指向下一个元素的指针(tail.next)不是引用undefined,而是指向第一个元素(head)。
实现一个循环链表类CircularLinkedList,继承LinkedList类。不需要新增属性,我们重写一下insert方法和removeAt 方法。
class CircularLinkedList extends LinkedList {
constructor(equalsFn = defaultEquals) {
super(equalsFn);
}
insert(element) {
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;
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;
}
removeAt(index) {
if(index >= 0 && index <= this.count) {
let current = this.head;
if(index === 0) {
if(this.size() === 1) {
this.head = undefined;
}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.get(index-1);
current = previous.next;
previous.next = current.next;
}
this.count--;
return current.element;
}
return undefined;
}
}
四、有序链表
有序链表是指保持元素有序的链表结构。
实现一个有序链表类SortedLinkedList类,继承LinkedList类。
const Compare = {
LESS_THAN: -1,
BIGGER_THAN: 1
}
function defaultCompare(a, b) {
if (a === b) {
return 0;
}
return a < b ? Compare.LESS_THAN : Compare.BIGGER_THAN;
}
class SortedLinkedList extends LinkedList {
constructor(equalsFn = defaultEquals, compareFn = defaultCompare) {
super(equalsFn);
this.compareFn = compareFn;
}
insert(element, index = 0) {
if(this.isEmpty()) {
return super.insert(element, 0);
}
const pos = this.getIndexNextSortedEle(element);
return super.insert(element, pos);
}
// 获取元素的正确位置
getIndexNextSortedEle(element) {
let current = this.head;
let i=0;
for(;i<this.size() && current;i++) {
const comp = this.compareFn(element, current.element);
if(comp === Compare.LESS_THAN) {
return i;
}
current = current.next;
}
return i;
}
}
本文详细介绍了JavaScript中的四种链表数据结构:普通链表、双向链表、循环链表和有序链表。分别阐述了它们的特点和实现方法,包括插入、删除、查找等操作。同时,还讨论了如何通过继承扩展链表类,以实现更复杂的数据结构功能。
1506

被折叠的 条评论
为什么被折叠?



