0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

周立功新著内容分享:双向链表是什么?

电子工程师 来源:互联网 作者:佚名 2017-09-22 18:24 次阅读

近日周立功教授公开了数年的心血之作《程序设计与数据结构》,电子版已无偿性分享到电子工程师与高校群体下载,经周立功教授授权,特对本书内容进行连载。

>>>>1.1 双向链表

单向链表的添加、删除操作,都必须找到当前结点的上一个结点,以便修改上一个结点的p_next指针完成相应的操作。由于单向链表的结点没有指向其上一个结点的指针,因此只有从头结点开始遍历链表。当某一结点的p_next指向当前结点时,表明它为当前结点的上一个结点。显然每次都要从头开始遍历,其效率极为低下。在单向链表中,之所以可以直接获取单向链表中当前结点的下一个结点,是因为结点中包含了指向下一个结点的指针p_next。如果在双向链表的结点中再增加一个指向它的前一个结点的前向指针p_prev,则一切问题将迎刃而解。那么,既有指向下一个结点的指针,又有指向前一个结点的指针的链表称之为双向链表,示意图详见图3.15

图3.15 双向链表示意图

与单向链表一样,双向链表也定义了一个头结点,基于单向链表将应用数据与链表结构相关数据完全分离的设计思想,则双向链表结点仅保留p_next和p_prev指针。其数据结构定义如下:

typedef struct _dlist_node{

struct _dlist_node *p_next;

struct _dlist_node *p_prev;

}dlist_node_t;

其中,dlist是double list 的缩写,表明该结点是双向链表结点。由此可见,虽然前向指针使得寻找链表的上一个结点变得非常容易,但由于结点中新增了一个指针,因此其内存开销将会是单向链表的两倍。在实际应用中,应该权衡效率与内存空间,在内存资源非常紧缺的场合,如果结点的添加、删除操作很少,一点效率的影响可以接受,则选择使用单向链表。而不是一味地追求效率,认为双向链表比单向链表好,始终选择使用双向链表。

图3.15中,头结点的p_prev和尾结点的p_next直接被设置为了NULL,此时,如果要直接由头结点找到尾结点,或者由尾结点找到头结点,都必须遍历整个链表。可以对这两个指针稍加利用,使头结点的p_prev指向尾结点,尾结点的p_next指向头结点,此时,该双向链表就成了一个循环双向链表,示意图详见图3.16

图3.16 循环双向链表示意图

由于循环双向链表的效率更高,可以直接从头结点找到尾结点,或者从尾结点找到头结点,且没有额外的内存空间消耗,仅仅是使用了两个不打算使用的指针,算是废物利用,因此下面介绍的双向链表均视为循环双向链表。

类似于单向链表,虽然头结点与普通结点的内容完全相同,但它们的含义却有所区别,头结点是链表的头,代表了整个链表,拥有此头结点,就表示其拥有了整个链表。为了便于区分头结点与普通结点,可以单独定义一个头结点类型。比如:

typedef dlist_node_t dlist_head_t;

当需要使用双向链表时,首先需要使用该类型定义一个头结点。比如:

dlist_head_t head;

由于此时还没有添加其它任何结点,仅存在一个头结点,因此该头结点既是第一个结点(头结点),又是最后一个结点(尾结点)。按照循环链表的定义,尾结点的p_next指向头结点,头结点的p_prev指向尾结点,仅有一个结点的示意图详见图3.17

图3.17 空链

显然,仅有头结点时,其p_next和p_prev都指向本身。即:

head.p_next = &head;

head.p_prev = &head;

为了避免用户直接操作成员,需要定义一个初始化函数,专门用于初始化链表头结点中各个成员的值,其函数原型(dlist.h)为:

int dlist_init(dlist_head_t *p_head);

其中,p_head指向待初始化的链表头结点。其调用形式如下:

dlist_head_t head;

dlist_init(&head);

dlist_init()函数的实现详见程序清单3.33

程序清单3.33双向链表初始化函数

1 int dlist_init(dlist_head_t *p_head)

2 {

3 if (p_head == NULL){

4 return -1;

5 }

6 p_head -> p_next = p_head;

7 p_head -> p_prev = p_head;

8 return 0;

9 }

与单向链表类似,将提供一些基础的操作接口,它们的函数原型如下:

dlist_node_t *dlist_prev_get (dlist_head_t *p_head, dlist_node_t *p_pos); //寻找某一结点的前一结点

dlist_node_t *dlist_next_get (dlist_head_t *p_head, dlist_node_t *p_pos); //寻找某一结点的后一结点

dlist_node_t *dlist_tail_get (dlist_head_t *p_head); //获取尾结点

dlist_node_t *dlist_begin_get (dlist_head_t *p_head); //获取开始位置第一个用户结点

dlist_node_t *dlist_end_get (dlist_head_t *p_head); //获取结束位置尾结点下一个结点的位置

对于dlist_prev_get()和dlist_next_get(),在链表结点中已经存在指向前驱后后继的指针,详见程序清单3.34

程序清单3.34得到结点前驱和后继的函数实现

1 dlist_node_t *dlist_prev_get(dlist_head_t *p_head, dlist_node_t *p_pos)

2 {

3 if (p_pos != NULL){

4 return p_pos -> p_prev;

5 }

6 return NULL;

7 }

8

9 dlist_node_t *dlist_next_get(dlist_head_t *p_head, dlist_node_t *p_pos)

10 {

11 if (p_pos != NULL){

12 return p_pos -> p_next;

13 }

14 return NULL;

15 }

dlist_tail_get()函数用于得到链表的尾结点,在循环双向链表中,头结点的p_reev即指向了尾结点详见程序清单3.35

程序清单3.35 dlist_tail_get()函数实现

1 dlist_node_t *dlist_tail_get(dlist_head_t *p_head)

2 {

3 if (p_head != NULL) {

4 return p_head->p_prev;

5 }

6 return NULL;

7 }

dlist_begin_get()函数用于得到第一个用户结点,详见程序清单3.36

程序清单3.36 dlist_begin_get()函数实现

1 dlist_node_t *dlist_begin_get(dlist_head_t *p_head)

2 {

3 if (p_head != NULL){

4 return p_head->p_next;

5 }

6 return NULL;

7 }

dlist_end_get()用于得到链表的结束位置,当双向链表设计为循环双向链表时则头结点的p_prev和尾结点的p_next都被有效地利用了任何有效结点的p_nextp_prev都不再为NULL。显然,不能再以NULL作为结束位置了,当从第一个结点开始顺序访问链表的各个结点时,尾结点的下一个结点就是链表头结点(head),因此结束位置就是头结点本身。dlist_end_get()的实现详见程序清单3.37

程序清单3.37 dlist_end_get()函数实现

1 dlist_node_t *dlist_end_get(dlist_head_t *p_head)

2 {

3 if (p_head != NULL) {

4 return p_head->p_prev;

5 }

6 return NULL;

7 }

在公众号后台回复关键字“程序设计”,即可在线阅读《程序设计与数据结构》;回复关键字“编程”,即可在线阅读《面向AMetal框架与接口的编程(上)》。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • mcu
    mcu
    +关注

    关注

    146

    文章

    16009

    浏览量

    343524
  • 周立功
    +关注

    关注

    38

    文章

    130

    浏览量

    37076
  • 链表
    +关注

    关注

    0

    文章

    80

    浏览量

    10464

原文标题:周立功:双向链表是什么?

文章出处:【微信号:Zlgmcu7890,微信公众号:周立功单片机】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    立功阐释高效的双向链表如何用

    实际上循环链表,无论是头结点、尾结点还是普通结点,其本质上都是一样的。
    的头像 发表于 09-25 14:14 6356次阅读

    立功大师EASY FPGA原理图

    本帖最后由 eehome 于 2013-1-5 09:47 编辑 立功EASYFPGA原理图立功大师经典力作,FPGA原理图。欢迎大家下载学习
    发表于 03-16 11:02

    立功的NIOS视频

    立功的NIOS视频
    发表于 07-19 09:55

    立功labview can

    立功 u*** can2 读取动态数据有点慢,一秒钟只有150组数据,测试要求1秒钟1500组数据,太慢了。labview是不是就是慢呀!
    发表于 09-12 10:18

    立功C51教程

    立功C51教程,里面有很多c51使用的技巧以及单片机使用的技巧[hide]http://pan.baidu.com/share/link?shareid=351871&uk=85110142[/hide]
    发表于 12-08 15:32

    立功单片机方案

    ` 本帖最后由 立功单片机 于 2016-8-15 11:57 编辑 MCUM0M3M4ARM9无线MCU汽车电子接口CAN_LINI2C总线232/485接口保护ESD/TVS智能安全非接
    发表于 08-15 11:04

    立功CANTest软件

    立功CANTest软件
    发表于 01-15 16:52

    立功CANTest软件

    立功CANTest软件
    发表于 02-27 09:26

    LABVIEW程序接收不到立功CAN卡的CAN消息

    D2P(相当于单片机)和PC通过立功CAN卡连接,D2P上编写的程序发送CAN消息,经立功CAN卡送到PC,希望在LABVIEW程序中处理收到的CAN数据,并把数据在前面板中用量表
    发表于 11-05 21:06

    【HarmonyOS】双向循环链表

    了一个个双向循环链表,把指针的高效能运用到了极致,这也许就是编程的艺术吧!致敬鸿蒙内核开发者贡献了如此优秀的源码,鸿蒙内核源码可作为大学C语言,操作系统,数据结构三门课的教学项目
    发表于 10-20 15:39

    立功Labvie demo分离ID和CAN内容

    请教,立功 Labview demo中怎么将CAN 的ID和内容分别读取出来。
    发表于 04-04 17:16

    算法与数据结构——双向链表

    第三章为算法与数据结构,本文为3.3 双向链表
    的头像 发表于 09-19 17:56 7066次阅读
    算法与数据结构——<b class='flag-5'>双向</b><b class='flag-5'>链表</b>

    了解Linux通用的双向循环链表

    在linux内核中,有一种通用的双向循环链表,构成了各种队列的基础。链表的结构定义和相关函数均在include/linux/list.h中,下面就来全面的介绍这一链表的各种API。
    发表于 05-07 10:44 561次阅读

    双向循环链表函数是什么?如何去实现它?

    双向循环链表结点内部有2个指针prev和next分别指向前后的结点,结点定义代码如下。
    的头像 发表于 06-17 12:50 1384次阅读

    双向循环链表的创建

    需要注意的是,虽然双向循环链表成环状,但本质上还是双向链表,因此在双向循环链表中,依然能够找到头
    的头像 发表于 05-24 16:27 1836次阅读