你以为很熟悉的单链表,这些坑很多人都踩过......

前言

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

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

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

比如:

  • 判断一个单链表是否有环

  • 计算单链表环的长度

  • 单链表整体反转

  • 单链表间隔反转

......

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

本期主要分为以下几个部分:

  • 链表结构

  • 创建链表

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

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

  • 链表全反转

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

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

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

链表结构

- 链表结构 -

首先我们来看下单链表的构造。看到这张图,可能会有人疑惑,这怎么和我从别的地方看到的表结构不一样呢,这是单链表吗?没错,这是小莱受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。

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--;

 

    - 删除头节点 -

2、删除尾节点

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

p->next = null;
list->tail = p;
list->length--;

 

     - 删除尾节点 -

3、删除中间节点

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

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

 

- 删除中间节点 -

链表全反转

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

题目:

将链表:10->20->30->40

反转为:40->30->20->10

要求 :

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

解答:

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

 - 单链表反转 -

 

如图,我们定义三个指针变量 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;

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

少侠留步

看你骨骼清奇,天赋异禀!

送你一份大厂面试秘籍!

点击「面试」即可获取!

关于作者

作者:大家好,我是莱乌,BAT搬砖工一枚。从小公司进入大厂,一路走来收获良多,想将这些经验分享给有需要的人,因此创建了公众号「IT界农民工」。定时更新,希望能帮助到你。

 往期推荐

《面试官:谈谈分布式锁的实现》

《害!面试挂在了redis集群》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值