【数据结构与算法】数组与链表

1,165 阅读5分钟

数组

概念: 数组是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。

线性表就是数据排成像一条线一样的结构,每个线性表只有前和后两个方向。链表、队列、栈等都是线性表。

无标题-2021-08-06-1709.png

优点:

  • 方便随机访问数组元素,适合查找操作,根据下标随机访问的时间复杂度为O(1)。

    注意点: 只是根据下标随机访问的时间复杂度是O(1),而查找表示从数组中找出某个元素(比如有一个数组,需要从这个数组中查找到某个符合条件的元素),那么它的时间复杂度就不是O(1)。

缺点:

  • 插入和删除操作比较低效,时间复杂度为O(n)。

为什么数组适合随机访问元素和查找操作?

数组的存储地址是连续的,所以数组元素的地址是有规律可循的,寻址公式如下:

a[i]_address = base_address + i * data_type_size

其中base_address是数组的初始地址,data_type_size表示数组中每个元素的大小。

注意点: 从JavaScript的角度我没理解这个相同类型data_type_size的意思。但从Java的定义数组的角度就理解一些了,int a[10]定义了一个int类型name为a长度为10的数组,所以类型都是相同的,而int类型都是4个字节的,因此data_type_size的值也是固定的。

综上,数组的地址是有规律的,可以根据公式计算的,所以很容易的就能访问到对应下标的元素。从而也方便了查找。

为什么数组的插入和删除比较低效?

数组的地址是连续的,如果有一个拥有10个元素的的数组,删除它的第1项,那么其他9项的位置都要往前移,假如是在第一项的位置插入一个元素,那么剩余的元素都要往后移。

总的来说就是为了保证数组的连续性。

链表

概念: 链表跟数组一样是一种线性结构,它不像数组一样需要连续的内存空间,它是通过指针将一组零散的内存块串联起来。

单链表: 包含头节点,尾节点,头节点存储链表的基地址,而尾节点指向NULL,表示最后一个节点。每个节点存储着data和下一个节点的地址。因此链表不需要连续的内存空间。

单链表.png

除了单链表之外,还有循环链表,双向链表,双向循环链表等。

循环链表: 将尾节点原本指向NULL的指针指向了头节点。相比单链表,循环链表的有点是从链尾到链头比较方便,适合处理具有环形结构的问题。如约瑟夫问题

lianbiao2.png

啥是约瑟夫问题?

N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3。

双向链表: 每个节点不止有指向下一个节点地址的指针,还有指向前一个节点的指针。 lianbiao3.png

双向链表对比单链表:

  • 每个节点多了一个存储前驱节点地址的空间,相比单链表来说存储相同多的数据,双向链表会占用更多的存储空间。
  • 双向链表支持双向遍历,操作更加灵活。

在进行对已知某个给定值的删除操作时,双向链表跟单链表都需要依次遍历,直到找到给定值的节点,才能进行删除操作,在这种情况下其删除操作的时间复杂度是O(1),但是其所需遍历的时间复杂度都是O(n)。

而在进行对已知指针指向的节点进行删除操作时,单链表需要通过遍历找到所需删除节点的前驱节点,而双向链表的前驱节点不需要遍历获取(因为已经存储在节点中了),所以它们各自所需的时间复杂度为O(n)和O(1)。

双向循环链表: 循环链表和双向链表的结合。

lianbiao7.png

链表的数据插入与删除:

链表的数据插入与删除只要改变其指针指向就行了,不用像数组那样,删除或插入一个元素还需要移动其他元素的位置。

lianbiao1.png

小总结

  1. 在数组进行插入、删除操作时,为了保持内存的连续性,需要做数据搬移,其时间复杂度为O(n),而在链表中插入或者删除一个数据时,只需要改变相邻节点的指针指向就可以了,其时间复杂度为O(1)。

  2. 其实对于数组或者链表来说,其遍历的时间复杂度都是O(n),只有在删除和插入操作时,数组的时间复杂度为O(n)而链表为O(1)。

  3. 数组简单易用,在实现上使用的是连续的内存空间。链表本身没有大小的限制,支持动态扩容。而对数组扩容就需要重新申请一块更大的内存空间,并拷贝原数组,所以比较麻烦。