数组是很常见的数据类型。得益于相当友好于开发者们对数据进行操作, 数组成了套路之王了!
当然,友好于开发者,肯定会失去很多的。它就不是很友好于性能优化了,当然,当今的硬件技术发展下,一般的小项目根本就跟前端层面的性能优化无瓜的。
数组的优点:
根据下标,赋值,取值,很方便,存储型的操作!查找,赋值,取值,性能强!
缺点:
扩容,很消耗性能!
数组在低层创建时是要声明长度的!JS视角只是表层,面向内存,数组声明时,内存就把相邻连续的一块区域划分给数组了,区域大小跟声明的数组长度挂钩。
问题来了,数组可以随意地push,shift,,,就需要扩容了,扩容等同于新创建一个数组,长度是扩容时设置的长度,然后把原本的数据依次拷贝过来,然后删除原数组的内存区块。太消耗性能了!
另外,
中间插入,中间删除,首部插入,首部删除,会导致操作位置后面的所有的数据在原内存地址上依次顺序改动。
我对数组的理解:
数组就是人心中的理想的数据类型,人的暴力美学,数组操作多简单!就算是扩容,就算是中间插入删除,又咋了?堂堂正正的阳谋!不就是性能差么?又不是不能用.jif!
针对优缺点,既可以思考:数组的使用场景!比如前端,,,数据量不大时,从来就没怂过!JSON.parse(JSON.stringify(xxx))
都无所畏惧,,,
当然,理性地思考,数组适合于多查找,多编辑,少中间首部插入或删除的操作。偏向于读和写的操作场景了!
对数组不适用于中间,首部的插入删除,还有扩容问题,另一种链式数据结构算是跟它完全反过来了!
链表
完全是针对数组的么!
不需要扩容!随意任何位置的元素进行插入,删除!牛皮!偏向于频繁地增删的操作场景!
这么友好于性能,,,终究是无法打破必须不完美的铁律了。它的读写性能不高,查找不如数组快。
优点
- 因为不必像数组一样是内存里的一块连续的内存区域,相对于内存很友好,充分榨取内存的剩余价值。源于链表的数据结构,存储的有下一个节点的地址。
- 插入和删除操作性能快。
- 理论上只要内存够,想存多少数据就存多少,扩啥容?
缺点
读取一个节点,就要遍历一遍链表,因为它的内存地址不连续。所以,性能不如数组直接靠下标来得快!
数据结构
很典型的因果论哲学模型!比如一个人的成长历史,从小学,到初中,高中,大学。讲究因果联系!
单链表的封装
//链表的封装
class LinkedList {
constructor(list) {
this.header = list || null;
this.length = list ? 1 : 0;
}
append(data) {
const list = new List(data);
if (this.length === 0) {
return this.header = list;
}
let current = this.header;
while (current.next) {
current = current.next;
}
current.next = list;
this.length += 1;
}
insert(data, index) {
if (index < 0 || index > this.length) {
throw new Error("index参数不合理");
}
const list = new List(data);
let oldList = this.header;
if (index === 0) {
oldList && (list.next = oldList);
this.header = list;
} else {
let i = 0;
while (i < index - 1) { //index-1是找的index-1的那个点,index点可以通过它找到
oldList = oldList.next;
i++;
}
let next = oldList.next;
oldList.next = list;
list.next = next;
}
this.length++;
}
get(position) {
if (position > this.length - 1 || position < 0) {
throw new Error("位置参数超出范围了!");
}
let current = this.header;
if (position === 0) return current;
let i = 0;
while (i < position) {
current = current.next;
i++;
}
return current;
}
update(position, data) {
this.get(position).data = data;
return true;
}
indexOf(data,key) {
let current = this.header;
if (!current) return -1;
let index;
let i = 0;
while (!index && current) {
//这里是扩展了一下如果data是复杂类型的时候,凭关键字段匹配的情况:
//在用哈希表的时候,我不得不扩展
if ( (key ? current.data[key]:current.data) === data) {
index = i;
} else {
current = current.next;
i++;
}
}
return index >= 0 ? index : -1;
}
removeAt(position) {
if (position < 0 || position > this.length - 1 || this.length === 0) {
throw new Error("参数超出范围!");
}
let current = this.header;
if (position === 0) {
this.length -= 1;
this.header = current.next;
return current;
} else {
let i = 0;
while (i < position - 1) {
current = current.next;
i++;
}
const target = current.next;
if (target) {
current.next = target.next;
this.length -= 1;
return target;
}
}
}
remove(data) {
const index = this.indexOf(data);
return this.removeAt(index);
}
size() {
return this.length;
}
isEmpty() {
return this.length === 0;
}
toString() {
let current = this.header;
let res = "";
while (current) {
res += (res ? " -> " : "") + current.data;
current = current.next;
}
return res;
}
}
class List {
constructor(data, next) {
this.data = data;
this.next = next ? next : null;
}
}
const list = new List(1);
const linkedList = new LinkedList(list);
console.log(linkedList);
linkedList.append(30);
linkedList.insert("2", 1);
linkedList.insert("0", 0);
linkedList.insert("6", 3);
linkedList.insert("20", 4);
linkedList.insert("89", 6);
console.log(linkedList, linkedList.toString(), 888, linkedList.get(3),);
console.log(linkedList.indexOf(3), linkedList.update(3, 10), linkedList.get(3));
console.log(linkedList.removeAt(3), linkedList.toString());
console.log(linkedList.remove("20"), linkedList.toString(),linkedList.size());
单链表的数据模型很清晰,像舞龙队。舞龙一堆人,其实,隔了很远,如何心意相通,互相完美配合,就是后面的人只关心前面的那一个人。一条链下来。还有一个游戏,就是传话,几个人一个次序方向传话。
由此,看出局限性了,节点只知道下一个节点是谁,只能一个方向次序操作,,,如果遇到一个节点,想要反方向操作上一个节点,怎么办?
比如文本编辑器的光标一行一行地向上向下移动。
需求是,一个节点既可以拿到下一个节点,单链表实现了。又可以拿到它的上一个节点,双链表可以的!
双向链表
其实就是在单链表的优点缺点的基础上,增加了点增删复杂的操作,增加了上一个节点的操作。增加了内存的消耗,其实也就是添加了一个prev的字段,跟功能比起来,可以看不见。
性能一样的。魔改版的单链表。
当然,双链表在性能跟单链表相同的情况下,功能更加完善了。它成了主流了!
一看,就可以知道,增删的方法时,要修改的属性会相当的多的。
双链表的实现封装:
class DoublyLinkedList extends LinkedList {
constructor(list) {
super(list);
this.tail = list || null;
}
append(data) {
const list = new DoublyList(data);
if (this.length === 0) {
list.prev = null;
this.header = list;
this.tail = list;
} else {
const last = this.tail;
last.next = list;
list.prev = last;
this.tail = list;
}
this.length += 1;
}
insert(data, index) {
if (index < 0 || index > this.length) {
throw new Error("插入的下标超出范围!");
}
const list = new DoublyList(data);
if (index === 0) {
const current = this.header;
if (current) {
this.header = list;
list.next = current;
current.prev = list;
} else {
this.tail = list;
this.header = list;
}
} else if (index === this.length) {
const last = this.tail;
last.next = list;
list.prev = last;
this.tail = list;
} else {
const test = index * 2 > this.length - 1;
let i = test ? this.length - 1 : 0;
let current = test ? this.tail : this.header;
let key = test ? "prev" : "next";
if (test) {
while (i > index) {
current = current[key];
i--;
}
} else {
while (i < index) {
current = current[key];
i++;
}
}
const last = current.prev;
list.prev = last;
last.next = list;
list.next = current;
current.prev = list;
}
this.length++;
}
get(index) {
if (index < 0 || this.length === 0 || index > this.length - 1) {
throw new Error("超出链表的长度范围");
}
const test = index * 2 > this.length - 1;
let current = test ? this.tail : this.header;
let i = test ? this.length - 1 : 0;
const key = test ? "prev" : "next";
if (test) {
while (i > index) {
current = current[key];
i--;
}
} else {
while (i < index) {
current = current[key];
i++;
}
}
return current;
}
removeAt(index) {
if (index < 0 || this.length === 0 || index > this.length - 1) {
throw new Error("操作的下标超出范围!");
}
const current = this.get(index);
const last = current.prev;
const next = current.next;
if (last) {
if (next) {
last.next = next;
next.prev = last;
} else {
this.tail = last;
last.next = null;
}
} else {
if (next) {
this.header = next;
next.prev = null;
} else {
this.header = null;
this.tail = null;
}
}
this.length -= 1;
return current;
}
remove(data) {
// const index = this.indexOf(data);
// return this.removeAt(index);
//这里可以简单套用上面的逻辑,但是我又重温了一下,各种逻辑的套路:
let current = this.header;
let ind, i = 0;
while (ind === undefined && current) {
if (current.data === data) {
ind = i;
}
current = current.next;
i++;
}
if (ind === undefined) {
throw new Error("暂无此节点,无法删除!");
}
const test = ind * 2 > this.length - 1;
let cur = test ? this.tail : this.header;
const key = test ? "prev" : "next";
let Ind = test ? this.length - 1 : 0;
if (test) {
while (Ind > ind) {
cur = cur[key];
Ind -= 1;
}
} else {
while (Ind < ind) {
cur = cur[key];
Ind += 1;
}
}
const last = cur.prev;
const next = cur.next;
if (last) {
if (next) {
last.next = next;
next.prev = last;
} else {
this.tail = last;
last.next = null;
}
} else {
if (next) {
this.header = next;
next.prev = null;
} else {
this.header = null;
this.tail = null;
}
}
this.length--;
return cur;
}
update(index, data) {
const list = this.get(index);
list.data = data;
return list;
}
backwardString() {
return this.toString();
}
forwardString() {
let current = this.tail;
let res = "";
while (current) {
res += (res ? " <- " : "") + current.data;
current = current.prev;
}
return res;
}
}
const doublyList = new DoublyList(1);
const doublyLinkedList = new DoublyLinkedList(doublyList);
doublyLinkedList.append(2);
doublyLinkedList.append(3);
doublyLinkedList.append(4);
doublyLinkedList.insert("哈哈", 2);
doublyLinkedList.update(3, "test");
console.log(doublyLinkedList,
doublyLinkedList.backwardString(),
doublyLinkedList.forwardString(),
doublyLinkedList.get(3),
doublyLinkedList.indexOf("哈哈")
);
console.log(
doublyLinkedList.removeAt(3),
doublyLinkedList.remove(4),
doublyLinkedList.toString(),
);