线性表之链表原理与实现

1、链表的存储结构

  之前我们讲过顺序表,顺序表的特点就是数据之间逻辑上的相邻关系在物理位置上也是相邻的,因此它可以通过寻址公式随机存取任意一个元素,但是这种特点也为元素的删除和插入带来了极大的不便。链表的特点就是:元素之间逻辑上的相邻关系在物理位置上并不一定相邻,元素的物理地址可以是任意一个合法的地址,这些元素通过指针联系在一起形成了一条链式结构,这样的结构在插入和删除操作时就不需要挪动元素位置,只是通过简单的修改指针的指向就可以了,因此理论上它的时间复杂度为O(1),同时,由于这种特性,因此每个元素的屋里地址是随机的,无法通过寻址公式来定位故而也失去了随机存取的特点。
  通过上面的描述我们可以总结出链表中的元素用任意的存储单元来存储它的信息。由于我们需要利用指针来描述元素之间的关系,因此存储单元除了存储元素本身的数据信息外,还必须存储描述数据之间关系的信息,也就是指针域,通过这些指针将每个独立的元素联系起来。我们将这些零散的内存块称之为结点。因此,链表中的结点主要包含两个信息—数据域和指针域 。链表有多种不同的结构,根据指针的指向可以将链表分为单链表、双链表和循环链表。

2、单链表

  所谓单向链表就是链表中的每一个数据项都包含数据域和一个指针域,这个指针指向下一个结点,因此这个指针域又称为后继指针。我们用一个图来形象地表示。
在这里插入图片描述
我们可以看到,链表中有两个结点比较特殊,分别为第一个结点和最后一个结点,其中第一个结点我们称之为头结点,最后一个结点称之为尾结点。头结点用于指向链表的基地址,也就是链表中第一个存储元素的地址,通过头结点我们就能遍历整条链表,通常头结点的数据域可以不存储任何数据也可以存储链表的长度等信息。尾结点用于指向一个NULL,表示此结点是整个链表的最后一个结点。
  我们来看看单链表的插入和删除操作。其实链表的插入和删除操作非常简单,它不需要搬运元素,只需要改改指针指向即可。我们也来看看一个图示
在这里插入图片描述
假如现在我们需要将p指向的结点插入到当前结点q的后面,只需做如下操作即可:p->next = q->next; q -> next = p;我们可以看到实际上插入一个元素只是简单地改改指针指向即可。删除操作也一样,只是在删除操作时需要注意的一点是如果是用像C语言这样手动管理内存的语言,在删除结点后必须手动释放掉这块内存。

3、循环链表

  在单链表中我们知道,链表的尾结点指向一个NULL,而循环链表就是尾结点的指针指向链表中第一个元素
在这里插入图片描述
其实没什么可说的,一目了然。

4、双向链表

  双向链表就是每一个结点除了数据域和后继指针外,还有一个指针这个指针用于指向前驱结点,所以又叫前驱指针,因此它的存储结构如下图所示
在这里插入图片描述
我们可以看到双向链表比单链表多了一个指针域,因此在同样多的元素的情况下,双向链表所占用的内存会比单向链表更大。但是双向链表却比但链表多一个特点就是它支持双向遍历,从结构上来看它可以在O(1)的时间复杂度找到某个给定结点的直接前驱结点。而单链表如果给定了某个结点去寻找它的直接前驱结点就需要从头遍历因此时间复杂度为O(n)。

4、再谈链表的插入和删除

  上面我们讲过链表的插入和删除操作在理论上来讲时间复杂度为O(1),为什么是理论上的复杂度呢?
  我们先来看看插入操作,在实际的开发中我们在链表中插入一个数据主要有两种:其一是将某个结点插入链表中某个给定的值,例如将5这个元素插入到4这个结点之后;其二是将某个结点插入到给定指针所指向的结点后面。对于第二种情况它确实只需要O(1)的时间复杂度,可是对于第一种情况来说就有些不同了,首先你必须遍历这个链表找到这个结点从而插入到具体的位置,那么这样一来如果是仅仅插入本身来说只需要O(1)的复杂度,但是整个过程却需要O(n)的复杂度。
  再来看看删除操作,从链表删除一个元素同样也有两种:其一是删除结点中值等于某个给定值的结点;其二是删除给定指针指向的结点。对于第一种情况,为了找到这个给定值的结点也必须先遍历这个链表,然后再通过操作指针删除这个元素结点。因此时间复杂度为O(n)。对于第二种情况,首先我们已经知道了需要删除的结点的指针,通过前面的分析,要删除这个结点就必须知道这个结点的直接前驱结点。因此对于单链表而言,为了找到这个直接前驱结点,必须还得从链表的头结点遍历从而找到待删除结点的直接前驱结点然后再通过操作指针进行删除,因而时间复杂度也为O(n)。但是对于双向链表而言就有些不同了,由于双向链表的结点存储了直接前驱结点,因此在对第二种情况删除时就不需要再遍历一遍链表,直接操作指针即可,其时间复杂度为O(1)。同样,假如我们需要在某个结点之前插入,单链表也需要从头遍历,而双向链表则可以直接操作指针插入。
  因此我们可以看出双向链表在插入和删除操作中比单链表更加灵活,高效。因而在实际的开发中,究竟选择哪种结构要根据实际情况而定,而上面的双向链表的选择虽然操作更加灵活,但是增加了内存的消耗,是一种用空间来换时间的策略。

5、数组和链表

  通过前面的分析我们已经知道了数组和链表各自的优缺点,各自在操作上的时间复杂度。但是在实际的开发中我们不能仅仅通过复杂度这唯一的因素来决定使用哪种数据结构。
  我们知道数组的有点是内存地址连续可以随机存取,由于内存连续,因此数组对于CPU而言更加友好。我们知道优于CPU的效率要高于内存,因此CPU在内存中读取数据时往往不是只读取要用的那部分内存数据,而是读取一个数据块,将相邻的部分数据也读入,因此它的效率会更高,这就是c

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值