我又来了学习算法了,距离看完初级入门<<我的第一本算法书>>已经过去了小半年,因此,再次从数据结构入手,这一次,我相信光~
不定时更新,如有错误,还请指正。
数组
查询:O(1)
增删:O(n)
从 Chrome v8 源码看,JSArray 继承自 JSObject,且有两种存储模式 FastElements 和 SlowElements。
- FastElements:快数组存储结构 FixedArray,开辟一段连续的内存,直接使用索引定位,会根据申请的容量进行动态扩容减容。
- SlowElements:慢数组存储结构 HashTable,不需要连续的内存,但是需要维护一个额外的哈希表,性能相对较差。
转换:
/* -------------------------- 快 -> 慢 -------------------------- */
// 1. 加入索引值 index 与当前容量 capacity 大小的差值大于 1024
// 2. 新容量是扩容后容量的 3 倍之多时'
let arr = [1, 2, 3];
arr[2000] = 4;
/* -------------------------- 慢 -> 快 -------------------------- */
// 慢数组的元素可以存放在快数组中且长度在Smi之间且仅节省50%的空间
动态扩容:
// 扩容算法 capacity + capacity/2 + 16
let arr = [1, 2, 3, 4];
arr.push(5);
// 4 + 2 + 16 = 22
// ps:数组的 push 使⽤汇编实现的, pop 使⽤ c++ 实现的
链表
在内存中分散存储,前一个节点的指针指向下一个节点的内存地址。可以衍生出循环链表、双向链表
查询:O(n)
增删:O(1)
单链表模拟实现:
function LinkedList() {
let Node = function (element) {
this.element = element;
this.next = null;
};
// 如同竹子一样,可以把链表也是一节一节的,每一节就是一个node,通过next连接,从head开始,长度也随之增长
let head = null;
let length = 0;
this.getList = function () {
return head;
};
this.size = function () {
return length;
};
// 末尾加入
this.append = function (element) {
let node = new Node(element),
p = head;
if (!p) {
head = node;
} else {
// 找到最后一个元素,边界条件: p.next === null
while (p.next) {
p = p.next;
}
p.next = node;
}
length += 1;
};
this.search = function (element) {
let p = head;
if (!p) return false;
// 对每一个指针进行遍历, 直到最后一个null为止:即 p === null
while (p) {
if (p.element === element) return true;
p = p.next;
}
return false;
};
this.insert = function (position, element) {
let node = new Node(element);
if (position < 0 || position > length) return null;
let prev = head,
curr = head,
index = 0;
if (position == 0) {
node.next = head;
head = node;
} else {
// 找出要插入位置的前后元素
while (index < position) {
prev = curr;
curr = curr.next;
index++;
}
prev.next = node;
node.next = curr;
}
length += 1;
};
// 删除 找到删除元素的位置,把前一个元素的next指向后一个元素
this.remove = function (element) {
if (!head) return;
let p = head,
prev = head;
while (p) {
if (p.element === elment) {
p = p.next;
prev.next = p;
}
prev = p;
p = p.next;
}
length -= 1;
};
}
不仅仅是链表,我将在所有需要指针的地方默认使用 p 来代表指针(pointer)
双链表模拟实现:
// 对比单链表其实就是有两个指针,一个从前往后,一个从后往前
function DoubleLinkedList() {
let Node = function (element) {
this.element = element;
// 前驱指针
this.prev = null;
// 后驱指针
this.next = null;
};
// 头尾
let head = null,
tail = null,
length = 0;
// 主要实现一下增删
this.insert = function (position, element) {
if (position < 0 || position > length) return null;
let node = new Node(element);
let prev = head,
curr = head,
index = 0;
// 首位
if (position == 0) {
// 首位为空
if (!head) {
head = node;
tail = node;
} else {
node.next = head;
head.prev = node;
head = node; // head指向新的node
}
} else if (position === length) {
// 尾节点
curr = tail;
curr.next = node;
node.prev = curr;
tail = node;
} else {
// 定位到插入位置的元素及后一个元素
while (index < position) {
prev = curr;
curr = curr.next;
index++;
}
prev.next = node;
nodex.next = curr;
curr.prev = node;
node.prev = prev;
}
length += 1;
};
this.remove = function (position) {
if (position < 0 || position > length) return false;
let prev = head,
curr = head,
index = 0;
if (position === 0) {
if (length === 1) {
head = null;
tail = null;
} else {
head = head.next;
head.prev = null;
}
} else if (position === length - 1) {
// 找到删除元素和前一个元素
while (index < position) {
prev = curr;
curr = curr.next;
index++;
}
prev.next = curr.next;
curr.next.prev = prev;
}
length -= 1;
};
}
循环链表
function CircleLinkedList() {
let Node = function (element) {
this.element = element;
this.next = null;
};
let head = null;
let length = 0;
this.insert = function (position, element) {
if (position < 0 || position > length) return null;
let node = new Node(element);
let prev = head,
curr = node,
index = 0;
if (position === 0) {
// 找到第一个
while (index++ < length) {
prev = curr;
curr = curr.next;
}
prev.next = ndoe;
node.next = curr;
} else {
while (index++ < position) {
prev = curr;
curr = curr.next;
}
prev.next = ndoe;
node.next = curr;
}
length += 1;
};
}```