双向链表

定义

我们一开始学习的链表中各节点中都只包含一个指针(游标),且都统一指向直接后继节点,通常称这类链表为单向链表。

虽然使用单向链表能 100% 解决逻辑关系为 "一对一" 数据的存储问题,但在解决某些特殊问题时,单链表并不是效率最优的存储结构。比如说,如果算法中需要大量地找某指定节点的前驱节点,使用单链表无疑是灾难性的,因为单链表更适合 "从前往后" 找,而 "从后往前" 找并不是它的强项。

为了能够高效率解决类似的问题,就发明了双向链表。从名字上理解双向链表,即链表是 "双向" 的,如下图所示:

 

从上图中可以看到,双向链表中各节点包含以下 3 部分信息(如下图所示):

●  指针域 prior:用于指向当前节点的直接前驱节点;

●  数据域 data:用于存储数据元素。

●  指针域 next:用于指向当前节点的直接后继节点;

 

因此,双链表的节点结构用 C 语言实现为:

typedef struct Node {

    struct Node *prior;//指向直接前驱节点

    ElemType data;//数据域

    struct Node *next;//指向直接后继节点

} Node;

注意:因为带头节点会更好操作,所以我的代码都有头节点。

1、双向链表的创建

同单链表相比,双链表仅是各节点多了一个用于指向直接前驱的指针域。因此,我们可以在单链表的基础轻松实现对双链表的创建。

//1、初始化双向链表(带头节点)

Status initLinkList(LinkList *list){

    //创建头节点

    *list = malloc(sizeof(Node));

    if (*list == NULL) {

        return ERROR;

    }

    (*list)->prior = NULL;

    (*list)->data = -1;

    (*list)->next = NULL;

    printf("已初始化链表~\n");

    return OK;

}

2、遍历双向链表

和单向链表遍历方式一模模一样样,这里就不多讲。我多加了一层使用prior指针逆序输出,相信有点基础的同学应该能一眼看明白。

//2、遍历双向链表

void printfLinkLisk(LinkList list){

    printf("遍历链表:\n");

    if (list == NULL || list->next == NULL) {

        printf("这是一个空链表\n");

        return;

    }

    LinkList p = list;

    //判断next是否全部正确

    printf("根据next从前往后遍历:");

    while (p->next) {

        printf("%d ",p->next->data);

        p = p->next;

    }

    printf("\n");

    //判断prior是否全部正确

    printf("根据prior从后往前遍历:");

    while (p != list) {

        printf("%d ",p->data);

        p = p->prior;

    }

    printf("\n");

}

3、根据索引位置添加节点

因为我的双向链表有头节点,所以只有两种添加情况:

●  添加至表的中间位置

同单链表添加数据类似,双向链表中间位置添加数据需要经过以下 4 个步骤(步骤中的顺序中 3 必须放到 1 和 2 后面,其它顺序可变),如下图所示:

● 将priorNode->next节点的prior指向新节点;

● 将新节点->next指向原来的priorNode->next;

● 将priorNode->next指向新节点;

● 新节点的prior指向priorNode。

 

● 添加至表尾

与添加到表中间的步骤只需要少掉步骤 1。因为priorNode->next是Null,不能用它执行操作,否则会崩溃。

//3、根据索引位置插入数据至链表中

Status insertLinkList(LinkList *list, int index, ElemType data){

    if (list == NULL || index < 0) {

        return ERROR;

    }

    int i = 0;

    LinkList priorNode = *list;

    //判断插入的位置,这里开始位置是0,index超过链表长度则插入末尾

    while (i < index && priorNode->next != NULL) {

        priorNode = priorNode->next;

        i++;

    }

    LinkList newNode = malloc(sizeof(Node));

    if (newNode == NULL) {

        return ERROR;

    }

    newNode->data = data;

    //插入操作共四步,看好了,别眨眼

    //1.将priorNode->next节点的前驱指向新节点

    if (priorNode->next) {

        priorNode->next->prior = newNode;

    }

    //2.将新节点->next指向原来的priorNode->next

    newNode->next = priorNode->next;

    //3.将priorNode->next指向新节点

    priorNode->next = newNode;

    //4.新节点的前驱指向priorNode

    newNode->prior = priorNode;

    return OK;

}

4、根据索引位置删除节点

根据索引删除节点时,只需遍历链表找到要删除的结点,更改前驱节点的next和后继节点的prior即可。

 
//4、根据索引位置删除节点

Status deleteLinkListByIndex(LinkList *list, int index, ElemType *data){

    if (*list == NULL || index < 0) {

        return ERROR;

    }

    LinkList locaNode = *list;

    int i = 0;

    while (i <= index) {

        locaNode = locaNode->next;

        if (locaNode == NULL) {

            printf("没有这个你想要删除的节点\n");

            return ERROR;

        }

        i++;

    }

    //开始删除,只需要做两步

    //1、更改前驱节点的next

    locaNode->prior->next = locaNode->next;

    //2、更改后继节点的prior。

    if (locaNode->next) {

        locaNode->next->prior = locaNode->prior;

    }

    *data = locaNode->data;

    free(locaNode);

    return OK;

}

5、根据存储的值删除节点

根据值删除节点时,只需遍历链表找到要删除的结点,更改前驱节点的next和后继节点的prior即可。

//5、根据存储的值删除节点

Status deleteLinkListByData(LinkList *list, ElemType data){

    if (*list == NULL) {

        return ERROR;

    }

    LinkList locaNode = (*list)->next;

    while (locaNode) {

        if (locaNode->data == data) {

            break;

        }

        locaNode = locaNode->next;

    }

    if (locaNode == NULL) {

        printf("没有这个你想要删除的节点\n");

        return ERROR;

    }

    //开始删除,只需要做两步

    locaNode->prior->next = locaNode->next;

    if (locaNode->next) {

        locaNode->next->prior = locaNode->prior;

    }

    free(locaNode);

    return OK;

}

6、根据值查找节点

方法同单向链表

//6、查找元素

Status selectNode(LinkList list, ElemType data, LinkList *locaNode){

    if (list == NULL) {

        return ERROR;

    }

    LinkList p = list->next;

    while (p) {

        if (p->data == data) {

            *locaNode = p;

            break;

        }

        p = p->next;

    }

    if (*locaNode == NULL) {

        printf("没有这个你想要的节点\n");

        return ERROR;

    }

    else {

        return OK;

    }

}

其它辅助代码

#include "stdlib.h"

#define OK    1

#define ERROR 0

//元素类型

typedef int ElemType;

//状态类型

typedef int Status;

//定义节点结构体

typedef struct Node {

    struct Node *prior;

    ElemType data;

    struct Node *next;

} Node;

typedef Node *LinkList;

int main(int argc, const char * argv[]) {

    LinkList list;

    initLinkList(&list);

    for (int i = 0; i < 10; i ++) {

        insertLinkList(&list, i, i);

    }

    printfLinkLisk(list);

    int index, data;

    printf("输入你想插入的位置(从0开始)和存储的值:");

    scanf("%d %d",&index,&data);

    insertLinkList(&list, index, data);

    printfLinkLisk(list);

    printf("输入你想删除的位置(从0开始):");

    scanf("%d",&index);

    deleteLinkListByIndex(&list, index, &data);

    printfLinkLisk(list);

    printf("输入你想删除的节点的值(只删最前的那个):");

    scanf("%d",&data);

    deleteLinkListByData(&list, data);

    printfLinkLisk(list);

    printf("\n");

    return 0;

}

输出结果:

 

看到这里是不是又学到了很多新知识呢~

如果你很想学编程,小编推荐我的C语言/C++编程学习基地【点击进入】!

都是学编程小伙伴们,带你入个门还是简简单单啦,一起学习,一起加油~

还有许多学习资料和视频,相信你会喜欢的!

涉及:游戏开发、常用软件开发、编程基础知识、课程设计、黑客等等......

 

 

【C语言教程】双向链表学习总结和C语言代码实现!值得学习~的更多相关文章

  1. 电脑小白学习软件开发-C#语言基础之循环重点讲解,习题

    写代码也要读书,爱全栈,更爱生活.每日更新原创IT编程技术及日常实用视频. 我们的目标是:玩得转服务器Web开发,搞得懂移动端,电脑客户端更是不在话下. 本教程是基础教程,适合任何有志于学习软件开发的 ...

  2. Swift语言教程中文文档

    Swift语言教程中文文档 Swift语言教程(一)基础数据类型 Swift语言教程(二)基础数据类型 Swift语言教程(三)集合类型 Swift语言教程(四) 集合类型 Swift语言教程(五)控 ...

  3. Swift3.0语言教程使用URL字符串

    Swift3.0语言教程使用URL字符串 Swift3.0语言教程使用URL字符串,和路径一样,URL其实也是字符串,我们可以将这些字符串称为URL字符串.本小节将讲解URL字符串的使用. 1.编码 ...

  4. Swift3.0语言教程使用路径字符串

    Swift3.0语言教程使用路径字符串 Swift3.0语言教程使用路径字符串,路径其实是字符串的一种,我们称为路径字符串.本小节将讲解如何使用路径字符串. 1.组合路径 开发者可以将数组快速的组合成 ...

  5. Swift3.0语言教程字符串大小写转化

    Swift3.0语言教程字符串大小写转化 Swift3.0语言教程字符串大小写转化,在字符串中,字符串的格式是很重要的,例如首字母大写,全部大写以及全部小写等.当字符串中字符很多时,通过人为一个一个的 ...

  6. Swift3.0语言教程替换子字符串

    Swift3.0语言教程替换子字符串 Swift3.0语言教程替换子字符串,替换子字符串其实就是将字符串中的子字符串删除,然后再进行添加.为了让这一繁琐的过程变的简单,NSString提供了替换子字符 ...

  7. Swift3.0语言教程获取C字符串

    Swift3.0语言教程获取C字符串 Swift3.0语言教程获取C字符串,为了让Swift和C语言可以实现很好的交互,开发者可以使用NSString的cString(using:)方法在指定编码格式 ...

  8. Swift3.0语言教程获取字符串长度

    Swift3.0语言教程获取字符串长度 Swift3.0语言教程获取字符串长度,当在一个字符串中存在很多的字符时,如果想要计算字符串的长度时相当麻烦的一件事情,在NSString中可以使用length ...

  9. Swift2.0语言教程之类的方法

    Swift2.0语言教程之类的方法 Swift2.0语言的方法 方法其实就是函数,只不过它被定义在了类中.在Swift中,根据被使用的方式不同,方法分为了实例方法和类型方法两种.这两种方法的定义也和O ...

  10. C++语言实现双向链表

    这篇文章是关于利用C++模板的方式实现的双向链表以及双向链表的基本操作,在之前的博文C语言实现双向链表中,已经给大家分析了双向链表的结构,并以图示的方式给大家解释了双向链表的基本操作.本篇文章利用C+ ...

随机推荐

  1. 详解 LeetCode_007_整数反转(Java 实现)

    目录 LeetCode_007_整数反转 题目描述 总体分析 解决方案 小结 LeetCode_007_整数反转 题目描述 给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转. 示 ...

  2. Nginx(二): worker 进程处理逻辑-流程框架

    Nginx 启动起来之后,会有几个进程运行:1. master 进程接收用户命令并做出响应; 2. worker 进程负责处理各网络事件,并同时接收来自master的处理协调命令: master 主要 ...

  3. [剑指Offer]30-包含min函数的栈

    题目 定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1)). 题解 辅助栈记录当前最小值. 代码 import java.util.Stack; pub ...

  4. 预科班D8

    2020.09.16星期三 预科班D8 学习内容: 一.注释 单行注释 ==>用 # 写在上一行或者该行后面 多行注释 ==>用 ''' '''或""" &q ...

  5. redis哨兵机制--配置文件sentinel.conf详解

    转载自 https://blog.csdn.net/u012441222/article/details/80751390 Redis的哨兵机制是官方推荐的一种高可用(HA)方案,我们在使用Redis ...

  6. 在Kubernetes上部署应用时我们常忽略的几件事

    根据我的经验,大多数人(使用Helm或手动yaml)将应用程序部署到Kubernetes上,然后认为他们就可以一直稳定运行. 然而并非如此,实际使用过程还是遇到了一些"陷阱",我希 ...

  7. oracle中插入一条记录后,重新登录查找不到数据

    你插入了数据,但是没有提交.其他Session也就是你再次登录后自然就看不到了(但是在当前回话可以看到插入的数据),但是你用SQLPLUS EXIT之后再次登录就可以看到插入的数据了,因为ORACLE ...

  8. Centos-重定向方式打包、备份、还原、恢复工具-cpio

    cpio 通过重定向方式将文件进行打包.备份.还原.恢复工具,扩展名为 .cpio 相关选项 -o 将文件复制.打包成文件或将将文件输出到标准输出 -i  将打包文件或者将设备上的备份还原到系统中 - ...

  9. C# Redis分布式锁(RedLock) - 多节点

    Redis单节点的分布式锁只需要注意三点就可以了: 1.加锁并设置锁的过期时间必须是原子操作; 2.锁的value值必须要有唯一性; 3.释放锁的时候要验证其value值,不是自己加的锁不能释放. 但 ...

  10. matlab中fopen 打开文件或获得有关打开文件的信息

    参考:https://ww2.mathworks.cn/help/matlab/ref/fopen.html?searchHighlight=fopen&s_tid=doc_srchtitle ...