链表也是一种数据结构,相比较于数组,略显复杂。链表和数组都是非常基础、非常常用的数据结构。
1. 数组与链表的区别
从底层的存储结构上看,二者申请的内存空间不一样:
-
数组需要一块连续的内存空间来存储,对内存要求较高。
-
链表不需要一块连续的内存空间,它通过"指针"将一组零散的内存块串联起来。
例如,当我们申请一个100MB大小的数组,当内存空间中没有连续的、足够大的存储空间时,即便内存的剩余总可用空间大于100MB,仍然会申请失败。但如果我们申请的是100MB大小的链表时,就可以申请成功。
对比图如下:
2. 三种常见的链表结构
链表结构有很多种,但最常见的主要有如下三种:
-
单链表
-
双向链表
-
循环链表
2.1 单链表
链表通过指针将一组零散的内存块串联在一起,我们将内存块称为链表的“结点”。为了将所有的结点串联在一起,每个链表的结点除了要存储数据之外,还需要记录链上的下一个结点的地址,如下图所示,将记录一个结点地址的指针叫作“后继指针next”。
从上面单链表的图中可以看到有两个结点比较特殊,分别是第一个结点和最后一个结点。我们习惯性的把第一个结点叫作头结点,最后一个结点叫作尾结点。
头结点用来记录链表中的基地址,可以根据头结点遍历得到整个链表。
尾结点的指针不是指向下一个结点,而是指向一个空地址NULL,表示这是链表上最后一个结点。
在链表中进行数据的插入和删除操作要比数组中高效。因为在数组中进行插入、删除操作时,为了保持内存数据的连续性,需要做大量的数据移动,时间复杂度是O(n)。而在链表中存储空间本身就不是连续的,不需要为了保持内存的连续性而移动大量的数据。
为了方便理解,看一张链表中数据插入和删除操作的图:
从图中可以看出,在链表的插入和删除操作中,我们只需要考虑相邻结点的指针改变,所以对应的时间复杂度是O(1)。
链表中数据的插入和删除比数组高效,但当需要随机访问第k个数据时,就没有数组高效。因为链表中的数据不是连续存储的,无法像数组那样,根据首地址和下标,通过寻址公式直接计算出对应的内存地址,而是需要根据指针一个结点一个结点依次遍历,直到找到对应的结点。
2.2 循环链表
循环链表的本质是一种特殊的单链表,与单链表的区别就是在尾结点。单链表中的尾结点指针指向空地址,表示这就是最后的结点。而循环链表的尾结点指针指向链表的头结点。
从上面的图中可以看出循环链表像一个环一样首尾相连,因此称作“循环”链表。
与单链表相比,循环链表的优点是从链尾到链头比较方便。当需要处理的数据具有环形结构特点时