思维导图
思路理解
链表就是用一个个节点凑成的一个数据链,每一个节点有自己的数据,还有保存下一个节点的指针,这样操作相对于数组的优势有很多:
1.链表《增删数据效率会很高》,因为数组是一块连续的内存,要增删数据就需要更改原来连续内存的大小,那么就会涉及到重新分配内存,以及中间数据删除后,其他数据位置的移动等,效率会比较低,而链表只需要改变相邻的节点的指针指向就可以做到增删。
2.链表插入时间复杂度相比数组低很多。
缺点:
1.由于不是连续的内存地址,那么就无法用下标直接访问到数据,只能从头或者从尾部向里依次查找对应下标个数,效率很低。
2.由于要保存下一个节点的地址,会多耗费很多内存。
0.链表类
function LinkList() {
}
局部实现
1.在链表类中有节点内部类,来作为每个节点的数据保存形式,将要保存的数据存成这种形式这样就能保存下一个节点的地址
//节点内部类
function Node(data) {
this.data = data;
this.next = null;
}
let head = null; //指向链表头结点
let length = 0; //保存链表长度
2.链表末尾插入(这里是只实现头结点的做法,也可以实现一个尾节点,提高尾部插入效率)
主要思路:先将要保存的数据封装成一个节点,然后使用一个临时节点,遍历当前链表,寻找next属性为空的节点,就是最后一个节点,将要插入的节点赋值给最后一个节点的next属性,即完成链表的尾部插入操作
//末尾插入
LinkList.prototype.append = function(element) {
const newEle = new Node(element);
if (length == 0) { //第一个节点
head = newEle;
} else { //不是第一个节点
let current = head;
while (current.next) {
current = current.next;
}
current.next = newEle;
}
length += 1;
}
2.指定位置插入
思路:设原数组ACD,我要将B插入第二个位置,那么就要让A节点的next改为指向B,B的next指向C,完成插入操作,链表变为ABCD
//指定位置插入
LinkList.prototype.insert = function(element, position = length) {
const newEle = new Node(element);
let current = head;
if (position <= 0) {
newEle.next = head;
head = newEle;
} else if (position >= length) {
//增加健壮性 直接插入最后
position = length;
while (current.next) {
current = current.next
}
current.next = newEle
} else {
let current = head;
for (let i = 0; i < position - 1; i++) { // 找到插入位置
current = current.next;
}
newEle.next = current.next;
current.next = newEle;
}
length += 1
}
指定位置删除
思路:设原数组ABCD,我要删除B,那么只需要让A的next指向C即可,B会在JavaScript的垃圾回收机制里,由于没有指针访问他,而被回收掉
//指定位置删
LinkList.prototype.removeAt = function(index) {
if (index < 0 || index > length - 1) {
console.log("index is error!")
return;
}
let current = head;
for (let i = 0; i < index - 1; i++) {
current = current.next
}
current.next = current.next.next;
length -= 1;
}
元素key查找
// key查元素(第一个)
LinkList.prototype.indexOf = function(ele) {
let current = head;
for (let i = 0; i < length; i++) {
if (current.data === ele) {
return i;
}
current = current.next;
}
return -1
}
下标查找
// 查下标元素
LinkList.prototype.get = function(index) {
if (index > length - 1) {
return;
}
let current = head;
for (let i = 0; i < index; i++) {
current = current.next;
}
return current.data
}
指定下标删
//指定位置删
LinkList.prototype.removeAt = function(index) {
if (index < 0 || index > length - 1) {
console.log("index is error!")
return;
}
let current = head;
for (let i = 0; i < index - 1; i++) {
current = current.next
}
current.next = current.next.next;
length -= 1;
}
指定元素删除
思路:既然已经实现了通过属性查下标和通过下标删元素,那么把它们结合封装起来就是通过元素关键字删节点
//指定元素删
LinkList.prototype.remove = (element) => {
//直接调用内部方法组合实现
const index = this.indexOf(element)
this.removeAt(index)
}
修改(思路基本和查找相同)
//指定下标改
LinkList.prototype.updateAt = function(position, element) {
if (position > length - 1) {
return
}
let current = head;
for (let i = 0; i < position; i++) {
current = current.next;
}
current.data = element;
}
//指定内容改
LinkList.prototype.update = function(oldEle, newEle) {
if (length == 0) {
return
}
let current = head;
while (current) {
if (current.data === oldEle) {
current.data = newEle
}
current = current.next
}
}
总结
链表优势是增删数据块,因为只需要改动相邻的元素,而不像数组可能会涉及到大批数据的复制改动地址。
而链表的劣势也很明显,因为不是连续的内存地址,所以查找的内部实现并不像数组那样直接通过地址访问效率高。
全部源码及测试
function LinkList() {
//节点内部类
function Node(data) {
this.data = data;
this.next = null;
}
let head = null;
let length = 0;
//末尾插入
LinkList.prototype.append = function(element) {
const newEle = new Node(element);
if (length == 0) { //第一个节点
head = newEle;
} else { //不是第一个节点
let current = head;
while (current.next) {
current = current.next;
}
current.next = newEle;
}
length += 1;
}
//指定位置插入
LinkList.prototype.insert = function(element, position = length) {
const newEle = new Node(element);
let current = head;
if (position <= 0) {
newEle.next = head;
head = newEle;
} else if (position >= length) {
//增加健壮性 直接插入最后
position = length;
while (current.next) {
current = current.next
}
current.next = newEle
} else {
let current = head;
for (let i = 0; i < position - 1; i++) {
current = current.next;
}
newEle.next = current.next;
current.next = newEle;
}
length += 1
}
//指定位置删
LinkList.prototype.removeAt = function(index) {
if (index < 0 || index > length - 1) {
console.log("index is error!")
return;
}
let current = head;
for (let i = 0; i < index - 1; i++) {
current = current.next
}
current.next = current.next.next;
length -= 1;
}
//指定元素删
LinkList.prototype.remove = (element) => {
//直接调用内部方法组合实现
const index = this.indexOf(element)
this.removeAt(index)
}
//指定下标改
LinkList.prototype.updateAt = function(position, element) {
if (position > length - 1) {
return
}
let current = head;
for (let i = 0; i < position; i++) {
current = current.next;
}
current.data = element;
}
//指定内容改
LinkList.prototype.update = function(oldEle, newEle) {
if (length == 0) {
return
}
let current = head;
while (current) {
if (current.data === oldEle) {
current.data = newEle
}
current = current.next
}
}
// 查下标元素
LinkList.prototype.get = function(index) {
if (index > length - 1) {
return;
}
let current = head;
for (let i = 0; i < index; i++) {
current = current.next;
}
return current.data
}
//查元素(第一个)
LinkList.prototype.indexOf = function(ele) {
let current = head;
for (let i = 0; i < length; i++) {
if (current.data === ele) {
return i;
}
current = current.next;
}
return -1
}
//长度
LinkList.prototype.size = function() {
return length;
}
//显示
LinkList.prototype.toString = function() {
let outString = "";
let current = head;
while (current) {
outString += current.data + " ";
current = current.next;
}
return outString
}
}
const LL = new LinkList();
LL.append("adsf")
LL.append("让我去")
LL.append("个")
LL.append("华双方都")
LL.append("函数")
// console.log(LL.toString())
// console.log(LL.size())
LL.insert("发送到发")
// console.log(LL.toString())
LL.removeAt(3);
console.log(LL.toString())
LL.remove("发送到发")
console.log(LL.toString())
LL.append("函数")
LL.append("寒")
LL.updateAt(0, "a")
// console.log(LL.toString())
LL.update("函数", "数组")
// console.log(LL.toString())
// console.log(LL.get(5))
// console.log(LL.indexOf("数组1"))