链表
一种数据结构:每个节点保存一个数据,节点之间通过指针引用组成链表
相对于数组
优点:增删链表中的节点简单,只需要修改前后节点的引用,而数组则需要调整操作的节点之后的每个节点的位置
缺点:查询节点比数组慢,需要从链表头开始找,而数组可以通过下标定位
代码实现
- 首先得有一个创建节点的方法
function createNode(data) {
return {
prev: null,
data,
next: null,
};
}
- 还得有一个创建链表结构的方法
function createDoublyLinkedList(...items) {
const initLinkedList = {
head: null,
tail: null,
length: 0,
append,
insert,
del,
splice,
get,
toString,
};
items.forEach((item) => {
const node = createNode(item);
if (!initLinkedList.head) {
initLinkedList.head = node;
}
if (initLinkedList.tail) {
node.prev = initLinkedList.tail;
initLinkedList.tail.next = node;
}
initLinkedList.tail = node;
initLinkedList.length++;
});
return initLinkedList;
}
- 然后是实现链表的基本api,先实现一个获取指定位置节点的方法,提供给其他api用(链表就这缺点,通过索引查找元素麻烦)
// 查询目标节点,接受负数,-1就是最后一个节点(内部方法)
function getNode(position) {
if (Math.abs(position) > this.length || position % 1 !== 0) {
return false;
}
let currentIndex;
let current;
if (position >= 0) {
currentIndex = 0;
current = this.head;
while (currentIndex !== position) {
current = current.next;
currentIndex++;
}
} else {
currentIndex = this.length - 1;
current = this.tail;
while (currentIndex !== this.length + position) {
current = current.prev;
currentIndex--;
}
}
return current;
}
- get方法---查询指定位置元素
// 查询指定位置的数据,接受负数
function get(position) {
const current = getNode.call(this, position);
if (current === false) {
return false;
}
return current.data;
}
- append方法---往链表后面添加元素
// 添加节点
function append(data) {
const node = createNode(data);
if (this.length === 0) {
this.head = node;
} else {
let current = this.tail;
current.next = node;
node.prev = current;
}
this.tail = node;
this.length++;
}
- insert方法---在链表指定位置插入元素
// 插入节点,接受负数
function insert(position, data) {
const node = createNode(data);
const current = getNode.call(this, position);
if (current === false) {
return false;
}
// 修改节点指向
const prevNode = current ? current.prev : this.tail;
node.prev = prevNode;
node.next = current;
if (current) {
current.prev = node;
} else {
this.tail = node;
}
if (prevNode) {
prevNode.next = node;
} else {
this.head = node;
}
this.length++;
return true;
}
- del方法---删除指定位置的链表元素
// 删除节点
function del(position) {
// position 等于链表长度的位置没有节点可以删除
if (position >= this.length) {
return false;
}
const current = getNode.call(this, position);
if (!current) {
return false;
}
const prevNode = current.prev;
const nextNode = current.next;
if (prevNode) {
prevNode.next = nextNode;
} else {
this.head = nextNode;
}
if (nextNode) {
nextNode.prev = prevNode;
} else {
this.tail = prevNode;
}
this.length--;
return true;
}
- splice方法---和数组splice方法一样,可以在指定位置删除、替换元素
// 与数组的splice方法相同,可以删除,替换数据
function splice(position, count, ...items) {
let current = getNode.call(this, position);
// 保存真实删除的节点数
let delCount = 0;
// 找到删除count元素后要链接的上下节点
const prevNode = current ? current.prev : this.tail;
let nextNode = current;
/** 删除操作 start */
if (current) {
// 删除count个元素,如果有的话
if (typeof count === "number") {
while (delCount < count && nextNode) {
nextNode = nextNode.next;
delCount++;
}
} else {
// count不是数字,就从position删到最后
nextNode = null;
delCount = this.length - position;
}
}
/** 删除操作 end */
/** 添加操作 start */
// 把需要添加的数据先组成一个链表
const sonLinkedList = createDoublyLinkedList(...items);
// 将子链表连接到主链表
if (prevNode) {
prevNode.next = sonLinkedList.head;
sonLinkedList.head.prev = prevNode;
} else {
this.head = sonLinkedList.head;
}
if (nextNode) {
nextNode.prev = sonLinkedList.tail;
sonLinkedList.tail.next = nextNode;
} else {
this.tail = sonLinkedList.tail;
}
/** 添加操作 end */
this.length = this.length - delCount + sonLinkedList.length;
}
- toString方法---打印链表数据
// 打印链表
function toString() {
const dataArr = [];
let current = this.head;
while (current) {
dataArr.push(current.data);
current = current.next;
}
return dataArr.toString(); // 这里暂时没考虑节点data为引用数据类型的tostring展示
}
这样一个具备基本增删改查的双向链表就完成了
注意事项:节点是链表的节点,元素是节点存的值,也是toString出来的值,使用者操作元素而不会感知节点