数据结构划分
数据结构分为物理结构和逻辑结构,二者相辅相成,且逻辑结构依托物理结构存在。
换句话说,任何逻辑结构都需要依赖物理结构去实现,不可独立存在。
什么是物理结构?
- 物理结构就是数据的逻辑结构在计算机中存储的方式。
- 若将逻辑结构比作房屋,物理结构就是房屋内部的框架结构。
物理结构包括哪些?
- 顺序存储结构:具有代表性的是数组
- 链式存储结构:具有代表性的是链表
什么是逻辑结构?
- 逻辑结构是指数据对象中各个数据元素之间的相互关系。
逻辑结构包括哪些?
- 集合,线性表,树,图…
数组
数组是一种内存中连续存储数据的结构,常用的操作是增删改查。数组的容量问题,不同语言实现有差异。
-
与java的数组实现不同,js是动态扩容,而且常规存储很难有撑爆数组的可能。
-
js中,数组最大容量是Math.pow(2,32)-1,也就是2的32次方再减去1。指数爆炸,这是个很大的数(4294967295)
-
new Array()方法可接受一个无符号整型数字,含4个字节,一个字节占8位,即总共占32位,2进制存储就是2的32次方。
-
原本第一位是符号位,无符号整型不需要考虑,从第一位开始计,故最大长度是Math.pow(2,32)-1。
模拟实现数组的查找方法–find
- 数组在内存中开辟的是连续空间,但有意思的是它可以根据数组下标随机访问某个元素,时间复杂度为O(1)
function find(index) {
const arr = [1, 2, 3, 4, 5]
return arr[index]
}
模拟实现数组的更新方法–update
- 直接更改某个下标对应的元素值,就是更新操作,时间复杂度为O(1)
function update(index, value) {
const arr = [1, 2, 3, 4, 5]
arr[index] = value
return arr
}
模拟实现数组的插入方法–insert
- 数组的插入分为两种情况,尾插和中间插(含头插),中间插会导致待插入位置及其后续元素位置索引改变,是比较耗性能的,时间复杂度为O(n),但如果恰好是尾插,那就是O(1),这也是js数组方法中push比unshift性能高的原因。
function insert(index, value) {
const arr = [1, 2, 3, 4, 5]
let len = arr.length - 1;
//尾插
if (index > len) {
arr[index] = value;
return arr;
} else {
//中间插
//从右往左遍历,元素右移一位
for (let i = len; i >= index; i--) {
arr[i + 1] = arr[i]
}
//插入目标元素
arr[index] = value
return arr
}
}
模拟实现数组的删除方法–remove
- 数组的删除也分为两种情况,尾删和中间删(含头删),中间删会导致待删元素位置及其后续元素位置索引改变,是比较耗性能的,时间复杂度为O(n),但如果恰好是尾删,那就是O(1),这也是js数组方法中pop比shift性能高的原因。
function remove(index) {
const arr = [1, 2, 3, 4, 5]
let len = arr.length - 1;
//尾删
if (index === len) {
arr.length = len;
return arr;
} else {
//中间删(含头删),从左往右遍历,元素左移一位
for (let i = 0; i < len; i++) {
arr[i] = arr[i + 1]
}
//长度减一,对应的元素也会删除掉
arr.length--;
return arr
}
}
数组的特点
- 访问和更新速度快,时间复杂度为O(1)
- 删除和插入速度慢,时间复杂度为O(n)
- 占用的内存空间是连续的,对分散的碎小空间无法利用
- 适用于频繁读(查找,更新)不频繁写的场景(删除和插入)
链表
根据数组特点来看,擅长读不擅长写。那如果我们不需要频繁读取,只需要频繁写呢?
数组开辟的内存空间都是连续的,对碎片化内存空间无法充分利用,那如果就想充分利用呢?
存在即合理,基于以上两点,链表诞生了。它是一种非线性,不连续的物理结构,本质是诸多节点组成。
单向链表
- 上图就是单向链表,也是链表中最简单的一种,每个节点由数据和指向下一个节点的指针两部分组成。
- 第一个节点称为头节点,最后一个节点称为尾节点,next指针指向为NULL.
- 顾名思义,单向链表就是单向的,上级可以知道下级是谁,但是下级不清楚上级是谁
双向链表
- 双向链表弥补了单向链表在访问上级的不足,它的每个节点拥有数据,前指针,后指针三项。
链表的查找
- 不论是哪种类型链表,由于结构限制,无法像数组那样支持根据下标随机访问
- 链表访问目标节点需要逐个寻找,直到找到目标节点的上一个节点,其next指针指向的就是目标节点
- 最好情况自然是第一个就是,时间复杂度为O(1),最坏的就是都找一次,时间复杂度为O(n)
链表的更新
- 若不考虑找到目标节点耗费的时间,链表也可以像数组那样直接更新(替换数据项data即可),时间复杂度为O(1)
更新前 2|next-> 更新后3|next->
链表的插入
链表的插入要考虑头插,尾插,中间插三种情况。
-
头插:将新节点next指针指向原头节点并将新节点视为插入完成后的新头节点;时间复杂度为O(1)
-
中间插:新节点next指针指向插入位置节点,插入位置的上一个节点next指针指向新节点 。时间复杂度为O(1)
- 尾插:最后一个节点next指针的指向由NULL改为指向新节点,新节点的next指针指向为NULL。时间复杂度为O(1)
链表的删除
同插入一样,链表的删除也分三种情况。头删,尾删,中间删
- 头删:直接干掉现有头节点,其后首个节点为新的头节点。时间复杂度为O(1)
- 中间删:将要删除节点上一级节点的next指针跨过要删除的节点,指向其下一个节点。时间复杂度为O(1)
- 尾删:直接将尾节点的上一级节点next指针置为NULL
链表的特点
- 若不考虑查找目标节点耗费的时间,不管是删除还是插入,时间复杂度都是O(1)
- 不是连续存储的,可充分利用内存中的碎小空间
- 适合频繁写(插入,更新和删除),不适合频繁读(查找)