python 线程安全链表_你以为很熟悉的单链表,这些坑很多人都踩过......

前言

某大厂内,小莱写完代码后翘着二郎腿靠在椅子上,正嘴里哼着小曲时。突然听到不远处传来了一声,"单链表都不会,这是基本功.......接着面吧!"。听到这句话,小莱顿时打了一激灵,端直坐了起来。

回想小莱的面试经历中也曾遇到过单链表的问题,曾经还在面试某大厂时被单链表反转pass过。

单链表作为最基础的数据结构,往往被忽略。大家的潜意识里可能认为越简单的东西大家都会,也就不会被作为考察与筛选条件。但事实却恰恰相反,单链表在面试环节中被问到的频率还是挺高的。且在原来的基础上又出现了很多拓展。

比如:判断一个单链表是否有环

计算单链表环的长度

单链表整体反转

单链表间隔反转

......

但是无论单链表如何变化,追根溯源还是离不开最基础的实现方式。因此,这期小莱决定重新介绍下这个老面孔。可能有人会觉得对单链表很熟悉了,不用再看了。但是答应我接着往下看,一定有意想不到的收获!

本期主要分为以下几个部分:链表结构

创建链表

插入节点(头插法、尾插法、中间插入)

删除节点(删除头、尾、中间节点)

链表全反转

画外音:本文具体代码实现通过公众号内回复「单链表」获取,有关单链表的拓展我们下期展开(毕竟篇幅有限,望大家谅解)。

对于链表的定义,小莱还想在这里啰嗦一句:链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

接下来我们干点正事儿吧!!!

链表结构

- 链表结构 -

首先我们来看下单链表的构造。看到这张图,可能会有人疑惑,这怎么和我从别的地方看到的表结构不一样呢,这是单链表吗?没错,这是小莱受redis底层双向链表所启发实现的(就是这么骚气)。

在这个表结构里,包含了哨兵节点和链表主体两部分。其中链表主体中的普通节点包含数据域和指针域。

哨兵节点由三个元素构成,分别为指向头节点和尾节点的head、tail指针域(可以理解为分别存储头节点和尾节点的地址),还有一个length字段,用来记录链表的长度。

画外音:是不是很多人在计算链表长度的时候偷偷使用了遍历链表的方式?(小样,被我识破了吧!)

在了解了链表结构之后,那么我们就动手创建一个单链表吧!

创建链表

经过链表结构的介绍,我们知道了两个节点。一个哨兵节点,还有一个普通节点。我们可以通过以下两个结构体分别来表示。

普通节点:

typedef struct lnode {void *value;

struct lnode *next;

}lnode;

其中value用来存储数据,next用来指向下一个节点(可理解为存储下一个节点的地址)。

哨兵节点:

typedef struct list {

lnode *head;

lnode *tail;

int length;

}list;

head指向链表主体的第一个节点,tail指向最后一个节点,初始化时两个都可以设置为NULL,length表示链表长度。

创建了这两个节点后,我们就可以把哨兵节点和普通节点结合起来了。

当链表中只有一个节点的时候,那么哨兵节点的head 、tail分别用来存储普通节点的地址,此时length为1。

b32970409e12ff0e220cf28aecf8d6f5.png

list->head = list->tail = node;

list->length = 1;

画外音:可能有同学对C语言不太了解,小莱在这里建议大家学学C语言基础知识。毕竟很多开源软件底层都会涉及到,如果想长远发展这是一条必经之路。

插入节点

到现在我们的链表已经初具雏形了,那么我们如何插入其它节点呢?

这里有三种情况,分别为:头插法、尾插法、中间插入。

我们把待插入的节点定义为 m 节点。

1、头插法

头部插入时,只需将m指针域指向头节点,然后将head指向新的m节点(注意这两个顺序不能颠倒)。m->next = list->head;

list->head = m;

list->length++;

- 头插法 -

2、尾插法

尾部插入时,将尾节点指针域指向m,然后将tail指向新的m节点。

m->next = null;

list->tail->next = m;

list->tail = m;list->length++;

- 尾插法 -

3、中间插入法

中间插入时,将m节点指针域指向p的下一个节点(即q节点),将p节点指针域指向m节点(同样两个顺序不能颠倒)。

m->next = p->next;

p->next = m;list->length++;

- 中间插入 -

删除节点

1、删除头节点

这里分为两种情况:

a. 如果链表主体只有一个元素的话,直接将head、tail置为null即可。list->head = list->tail = null;list->length = 0;

b. 如果是一个以上的元素的话,需要将head指向头节点的下一个节点。

list->head = list->head->next;list->length--;

06c8276e2655feaca12d659c99ec376c.png

- 删除头节点 -

2、删除尾节点

删除尾节点时,只需将尾节点前的节点的指针域置为空即可。

p->next = null;

list->tail = p;

list->length--;

c87beeea61f3c1c9a6b257aff734d1a4.png

- 删除尾节点 -

3、删除中间节点

中间节点删除时,将p节点的指针域指向被删除节点所指向的下一个节点即可。对应图中为将p节点指针域指向m节点所指向的下一个节点。

p->next = m->next;

list->length--;

0a28f3ce260b3eaf984396401fa3e712.png

- 删除中间节点 -

链表全反转

到这里相信大家对单链表都有了一定的了解,那么我们来看一道进阶题:

题目:

将链表:10->20->30->40反转为:40->30->20->10

要求 :时间复杂度为O(n),空间复杂度为O(1)

解答:

2e1ff4a760e82ce8c3b25f44aa4a9b3a.png

39340de555136c3f8449bf0eaab09665.png

d3c4ee18727fcdfcdea54587e02a66f4.png

..............

fcb0dc22c213974fe8819fc23a2488ba.png

- 单链表反转 -

如图,我们定义三个指针变量 p、q、m。当把q指向p的时候,链表会被截断,所以需要一个临时变量m来保存当前q节点的下一个节点。当进行一次调换后,指针整体向后移动一个节点。

代码实现:

p = list->head;

q = list->head->next;

while(m != NULL) {

m = q->next;

q->next = p;

p = q;

q = m;

}

list->tail = list->head;

list->tail->next = NULL;list->head = p;

画外音:小莱再次提醒下大家,具体代码实现通过公众号回复「单链表」可以获取喔!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值