集合列表和数组
集合
列表
数组
数组是由有限个相同类型的变量所组成的有序集合。
数组在内存中的存储方式
数组中的元素在内存中是连续存储的,且每个元素占用相同大小的内存。而不同类型的数组,每个元素所占的字节个数也不同。
总结: 物理存储方式是顺序存储, 访问方式是随机访问
数组的基本操作
a. 读取元素
由于数组在内存中顺序存储,所以只要给出一个数组下标,就可以读取到对应的数组元素,下标一般从 0 开始。
示例:
var array = [3, 1, 2, 5, 4, 9, 7, 2];
//输出数组中下标为3的元素
console.log(array[3]);
注意输入的下标必须在数组的长度范围之内,否则会出现数组越界。
时间复杂度:O(1)
b. 查找元素
与读取元素类似,由于我们只保存了索引为 0 处的内存地址,因此在查找元素时,只需从数组开头逐步向后查找就可以了。如果数组中的某个元素为目标元素,则停止查找;否则继续搜索直到到达数组的末尾。
时间复杂度:O(n)
c. 更新元素
要把数组中某一个元素的值替换为一个新值,直接利用数组下标,就可以把新值赋给该元素。
var array = [3, 1, 2, 5, 4, 9, 7, 2];
// 给数组下标为5的元素赋值
array[5] = 10;
//输出数组中下标为5的元素
console.log(array[5]);
时间复杂度:O(1)
d. 插入元素
e. 删除元素
数组的删除操作和插入操作的过程相反,当我们删除掉数组中的某个元素后,数组中会留下空缺的位置,而数组中的元素在内存中是连续的,这就使得后面的元素需对该位置进行填补操作。如果删除的元素位于数组中间,其后的元素都需要向前挪动 1 位。
时间复杂度:O(n)
数组的优劣势
二维数组
二维数组是一种结构较为特殊的数组,只是将数组中的每个元素变成了一维数组。
![image.png](https://img-blog.csdnimg.cn/img_convert/bd09f14edc2c20cf7e2446a774308e90.png#clientId=ua98ff845-cabe-4&from=paste&height=201&id=u732a90c4&margin=[object Object]&name=image.png&originHeight=401&originWidth=735&originalType=binary&ratio=1&size=39142&status=done&style=none&taskId=u5c123556-c726-4607-b272-6f30b95605f&width=367.5)
所以二维数组的本质上仍然是一个一维数组,内部的一维数组仍然从索引 0 开始,我们可以将它看作一个矩阵,并处理矩阵的相关问题。
示例
类似一维数组,对于一个二维数组 A = [[1, 2, 3, 4],[2, 4, 5, 6],[1, 4, 6, 8]],计算机同样会在内存中申请一段 连续 的空间,并记录第一行数组的索引位置,即 A[0][0] 的内存地址,它的索引与内存地址的关系如下图所示。
![image.png](https://img-blog.csdnimg.cn/img_convert/9eb27f734e435fa86ca147590f29e4dc.png#clientId=ua98ff845-cabe-4&from=paste&height=211&id=uf79f4a67&margin=[object Object]&name=image.png&originHeight=421&originWidth=1172&originalType=binary&ratio=1&size=153086&status=done&style=none&taskId=u9019c46d-c5c7-4e6d-848a-1749e04a6b8&width=586)
注意,实际数组中的元素由于类型的不同会占用不同的字节数,因此每个方格地址之间的差值可能不为 1。
实际题目中,往往使用二维数组处理矩阵类相关问题,包括矩阵旋转、对角线遍历,以及对子矩阵的操作等。
数组常用解题方法
什么是链表
链表(linked list)是一种在物理上非连续、非顺序的数据结构,由若干节点(node)所组成。
链表在内存中的存储方式
链表采用了见缝插针的方式,链表的每一个节点分布在内存的不同位置,依靠 next 指针关联起来。这样可以灵活有效地利用零散的碎片空间
总结:物理存储方式是随机存储, 访问方式是顺序访问
链表的分类
单向链表
单向链表的每一个节点又包含两部分,一部分是存放数据的变量 data,另一部分是指向下一个节点的指针 next。
![image.png](https://img-blog.csdnimg.cn/img_convert/032e481b68d0dfe61a9322ed2ec3d92a.png#clientId=u97c2adef-1b0f-4&from=paste&height=84&id=uaf4e3255&margin=[object Object]&name=image.png&originHeight=168&originWidth=1000&originalType=binary&ratio=1&size=87259&status=done&style=none&taskId=ud4cf89b0-69eb-47f3-a09f-52b06dcd38b&width=500)
双向链表
双向链表比单向链表稍微复杂一些,它的每一个节点除了拥有 data 和 next 指针,还拥有指向前置节点的 prev 指针
![image.png](https://img-blog.csdnimg.cn/img_convert/3b9f5f586e9454ac3237df31ad7c9df6.png#clientId=u97c2adef-1b0f-4&from=paste&height=68&id=ud9f292c4&margin=[object Object]&name=image.png&originHeight=136&originWidth=979&originalType=binary&ratio=1&size=69437&status=done&style=none&taskId=u11851a4e-4ce1-4035-90e9-cbd5e2f13ec&width=489.5)
链表的基本操作
a. 查找节点
在查找元素时,链表不像数组那样可以通过下标快速进行定位,只能从头节点开始向后一个一个节点逐一查找。
链表中的数据只能按顺序进行访问,最坏的时间复杂度是 O(n)。
b. 更新节点
如果不考虑查找节点的过程,链表的更新过程会像数组那样简单,直接把旧数据替换成新数据即可。
![image.png](https://img-blog.csdnimg.cn/img_convert/76f625697f06cf1abf9ccc218b8f3683.png#clientId=ufc51d92b-d3c2-4&from=paste&height=123&id=u434d048b&margin=[object Object]&name=image.png&originHeight=246&originWidth=994&originalType=binary&ratio=1&size=143606&status=done&style=none&taskId=uf83d9542-288d-4f46-a2ba-34d099e1c3d&width=497)
c. 插入节点
尾部插入
把最后一个节点的 next 指针指向新插入的节点即可。
![image.png](https://img-blog.csdnimg.cn/img_convert/10e2298688e4757ca73820719ac7c4cd.png#clientId=ufc51d92b-d3c2-4&from=paste&height=139&id=u1679b0ca&margin=[object Object]&name=image.png&originHeight=278&originWidth=925&originalType=binary&ratio=1&size=142455&status=done&style=none&taskId=uf626d03e-ef12-4448-81a8-111ada6fb9e&width=462.5)
头部插入
第 1 步:把新节点的 next 指针指向原先的头节点。
第 2 步:把新节点变为链表的头节点。
![image.png](https://img-blog.csdnimg.cn/img_convert/6c988fa66d20b9b4effd617d5cff3007.png#clientId=ufc51d92b-d3c2-4&from=paste&height=196&id=u5533fbc2&margin=[object Object]&name=image.png&originHeight=392&originWidth=956&originalType=binary&ratio=1&size=196973&status=done&style=none&taskId=u9e33ff00-2277-41e6-9dbb-25f03d0a18b&width=478)
中间插入
第 1 步:新节点的 next 指针,指向插入位置的节点。
第 2 步:插入位置前置节点的 next 指针,指向新节点。
![image.png](https://img-blog.csdnimg.cn/img_convert/3c9c88ce5aa7a7a2e59239026d910977.png#clientId=ufc51d92b-d3c2-4&from=paste&height=241&id=u17f9bb6d&margin=[object Object]&name=image.png&originHeight=482&originWidth=935&originalType=binary&ratio=1&size=190401&status=done&style=none&taskId=u3130a7a9-0206-48cf-acc4-e49b492bb94&width=467.5)
d. 删除节点
尾部删除
把倒数第 2 个节点的 next 指针指向空即可。
![image.png](https://img-blog.csdnimg.cn/img_convert/b2690abf0037f12558c57baaf8646e5f.png#clientId=ufc51d92b-d3c2-4&from=paste&height=160&id=uaad29749&margin=[object Object]&name=image.png&originHeight=319&originWidth=959&originalType=binary&ratio=1&size=176066&status=done&style=none&taskId=udd65c45d-4d31-4dc8-a1b9-5406d95a797&width=479.5)
头部删除
把链表的头节点设为原先头节点的 next 指针即可。
![image.png](https://img-blog.csdnimg.cn/img_convert/e74e9404d97f39b598ee0b2a2fb68315.png#clientId=ufc51d92b-d3c2-4&from=paste&height=170&id=u8e0e0e20&margin=[object Object]&name=image.png&originHeight=340&originWidth=1047&originalType=binary&ratio=1&size=171752&status=done&style=none&taskId=ua55d0dc4-ac55-4dfd-9455-0226605cd68&width=523.5)
中间删除
把要删除节点的前置节点的 next 指针,指向要删除元素的下一个节点即可。
![image.png](https://img-blog.csdnimg.cn/img_convert/e97eedf0a979dcb87001b5f73f19232d.png#clientId=ufc51d92b-d3c2-4&from=paste&height=177&id=uc6c97fcf&margin=[object Object]&name=image.png&originHeight=353&originWidth=1056&originalType=binary&ratio=1&size=174956&status=done&style=none&taskId=u4fc0c226-c649-4e19-9e7f-53144a956e7&width=528)
链表常用解题方法
数组 VS 链表
查找 | 更新 | 插入 | 删除 | |
---|---|---|---|---|
数组 | O(1) | O(1) | O(n) | O(n) |
链表 | O(n) | O(1) | O(1) | O(1) |
从表格可以看出,数组的优势在于能够快速定位元素,对于读操作多、写操作少的场景来说,用数组更合适一些。
相反地,链表的优势在于能够灵活地进行插入和删除操作,如果需要在尾部频繁插入、删除元素,用链表更合适一些。