死亡飞车.jpeg

第8篇文章中我们介绍了下链式存储中链表中的一种--单链表,但是单链表有一个缺点,就是无法快速访问到前驱结点,当查找到某个元素时,如果想找前边的元素结点,需要再次从头开始遍历,这样就比较麻烦。

那么就有人会问,是否可以在结点中再增加一个指针来指向前驱结点呢?答案是肯定的,增加了指向前驱结点的指针的链表称为

双链表

什么是双链表?

双链表,顾名思义就是可以向两个方向走的链表。它的每一个结点都有两个指针,一个指向后继结点,另一个指向前驱结点,如下图所示

双链表.png

我们从上图中可以看到,第一个结点的前驱结点pre指向了NULL,当然在设计时我们也可以把它指向头结点,最后一个结点的后继结点next指向了NULL,这种和单链表是一样的。

在双链表中,通过一个结点可以找到他的后继结点,也就可以找到他的前驱结点。

双链表在结构和算法描述上和单链表相同,只是某些算法实现比单链表复杂一些。例如在插入元素时,需要同时修改两个方向的指针指向,根据上图所示双链表,我们在元素40和67中间插入新元素88,来看一下图示过程

插入步骤1.png

双链表插入元素步骤2.png

从上面的步骤图中,我们看出,在插入元素时,需要同时修改两个方向的指针指向,同理,在删除元素时,也要修改这两个指针。这里就不画图展示了,大家“脑补一下就可以了~~~”


前面已经学过了单链表的实现过程,双链表的实现与单链表的实现大同小异,接下来我们看看双链表的实现过程。

双链表的实现

学习完双链表的定义与基本操作之后,我们还是用c语言来实现一个双链表,此双链表所具有的功能如下:

  • 创建双链表

  • 获取双链表的长度

  • 判断双链表是否为空

  • 插入、删除、查找元素

  • 销毁双链表

  • 打印双链表
    在学习完单链表时,我们每一种操作算法都有详细的描述和实现代码,双链表与单链表有很多操作是相似的,这里就不在详细描述,但是会给出关键代码的解释。

    dlist.h(头文件)

    #ifndef _DLIST_H_
    #define _DLIST_H_struct Node;
    typedef struct Head * pHead;//头结点类型
    typedef struct Node * pNode;//数据结点类型
    //定义头结点
    struct Head{int length;pNode next;
    };
    //定义数据结点
    struct Node{int data;pNode pre;//指向前驱结点的指针pNode next;//指向后继结点的指针
    };pHead DlistCreate();//创建双链表方法声明
    int getLength(pHead ph);//获取链表长度方法声明
    int IsEmpty(pHead ph);//判断链表是否为空方法声明
    int DlistInsert(pHead ph,int pos,int val);//在链表的pos位置插入val元素方法声明
    pNode DlistDelete(pHead ph,int val);//删除链表中元素val方法声明
    pNode DlistFind(pHead ph,int val);//查找链表中元素val方法声明
    void DlistDestory(pHead ph);//销毁链表方法声明
    void printFront(pHead ph);//打印链表中的元素方法声明(从头开始打印)
    void printLast(pHead ph);(从尾部开始打印)
    #endif
    

dist.c(函数实现文件)

  #include "dlist.h"#include <stdio.h>#include <stdlib.h>//创建双链表pHead DlistCreate(){pHead ph=(pHead)malloc(sizeof(struct Head));//为头结点分配空间if(ph==NULL){printf("分配头结点失败!");return NULL;}//创建好头结点后,初始化头结点中的数组ph->length=0;ph->next=NULL;return ph;}//获取链表长度int getLength(pHead ph){if(ph==NULL){printf("传入的双链表有误!");}return ph->length;}//判断链表是否为空int IsEmpty(pHead ph){if(ph==NULL){printf("传入的双链表有误!");}if(ph->length==0){return 1;}else{return 0;  }}//在链表的pos位置插入元素valint DlistInsert(pHead ph,int pos,int val){pNode pval=NULL;if(ph==NULL||pos<0||pos>ph->length){printf("插入元素时,传入参数有误");}//如果参数无误,给元素分配结点空间pval=(pNode)malloc(sizeof(struct Node));pval->data=val;//判断在那个位置插入元素,先判断链表是否为空if(IsEmpty(ph)){ph->next=pval;pval->next=NULL;pval->pre=Null;}else{pNode pCur=ph->next;if(pos==0){ph->next=pval;//将头结点指向pvalpval->next=pCur;//pval的后继指针指向pCurpval->pre=NULL;//pval的前驱结点指向空pCur->pre=pval;  //让pCur的前驱结点指向pval}else{for(int i=1;i<pos;i++){pCur=pCur->next;//遍历链表,找到要插入的位置,并将pCur指针后移   }//循环结束,此时pCur指向的就是要插入的位置//下方是将指针断开,再连接的过程pval->next=pCur->next;pCur->next->pre=pval;pval->pre=pCur;pCur->next=pval;}}ph->length++;return 1;}//删除链表ph中的元素valpNode DlistDelete(pHead ph,int val){if(ph==NULL||ph->length==0){printf("参数传入有误!");}pNode pval=DlistFind(ph,val);//找到值所在的结点if(pval==NULL){return NULL;}printf("将其删除\n");pNode pRe=pval->pre;pNode pNext=pval->next;pRe->next=pNext;pNext->pre=pRe;return pval;}//查找某个元素pNode DlistFind(pHead ph,int val){if(ph==NULL){printf("参数传递有误!")}  pNode pTmp=ph->next;//此过程与单链表没有差别            do{if(pTmp->data==val){printf("有此元素!\n");return pTmp;}pTmp=pTmp->next;//如果上方判断不成立,则继续进行下一个判断}//循环条件是直到链表的结尾while(pTmp->next!=NULL);//如果上方都不成立,说明链表中没有这个元素,直接返回NULLreturn NULL;}//销毁链表void  DlistDestory(pHead ph){pNode pCur=ph->next;pNode=pTmp;if(ph==NULL){printf("参数传入有误!");}while(pCur->next!=NULL){pTmp=pCur->next;free(pCur);//将结点释放pCur=pTmp;}//回到初始化状态ph->length=0;ph->next=NULL;}//打印链表中的元素,从前往后打印void printFront(pHead ph){if(ph==NULL){printf("参数输入有误!");}  pNode pTmp=ph->next;while(pTmp->next!=NULL){printf("%d",pTmp->data);pTmp=pTmp->next;}printf("\n")}//倒序打印
void printLast(pHead ph){if(ph==NULL){printf("参数输入有误!");}  //现将指针移动到最后的位置pNode pTmp=ph->next;while(pTmp->next!=NULL){pTmp=pTmp->next;}for(int i=--ph->length;i>=0;i--){printf("%d",pTmp->data);pTmp=pTmp->pre;}
}

ok,双向链表基本的操作diamante就写完了,接下来我们看一下测试文件
main.c

  #define _CRT_SECURE_NO_WARNINGS#include "dlist.h"#include <stdio.h>#include <stdlib.h>int main(){//创建一个双链表pHead ph=NULL;ph=DlistCreate();//向链表中插入元素int num;printf("请输入要插入的元素,输入0结束:\n");while(1){scanf("%d",&num);if(num==0){break;DlistInsert(ph,0,num);//从头添加元素} }printf("双链表的长度是%d\n",getLength(ph));printFront(ph);DlistInsert(ph,3,99);//在3位置插入新元素99printFront(ph);printLast(ph);//查找元素int val;printf("请输入要查找的元素:\n");scanf("%d",&val);DlistFind(ph,val);//删除元素int del;printf("请输入要删除的元素:\n");scanf("%d",&del);DlistDelete(ph,val);printFront(ph);//销毁链表DlistDestoty(ph);printf("双链表销毁成功:\n 此时链表的长度为%d\n",ph->length);system("pause");return 0;  }

总结

我们上方的代码实现了双链表的增删改查功能几个功能。

双链表的插入、删除操作,实现起来比单链表稍微复杂一丢丢~,其他操作都无较大的改动。

双链表是可以倒序遍历的,为了测试倒序功能,我们在上方代码中实现了printLast()方法,实现从后往前打印链表的元素。

双链表中的每个结点都要记录两个指针,所以空间消耗要略多一些。不过由于它良好的对称性,是的对结点前后两个结点操作更加灵活,也使得算法的时间效率得到了提高,说到底,就是空间换时间。

多谢关注,大家一起努力~~

《数据结构》第十篇、线性表中的链式存储结构--双链表相关推荐

  1. 数据结构(二):线性表包括顺序存储结构(顺序表、顺序队列和顺序栈)和链式存储结构(链表、链队列和链栈)...

    还记得数据结构这个经典的分类图吧: 今天主要关注一下线性表. 什么是线性表 线性表的划分是从数据的逻辑结构上进行的.线性指的是在数据的逻辑结构上是线性的.即在数据元素的非空有限集中 (1) 存在唯一的 ...

  2. 链表list(链式存储结构实现)_VOL.2 如何在python中实现链式存储结构

    一.前言 链式存储作为一种重要的数据存储方式有着极强的数据组织能力.灵活型和动态性,在众多数据结构中应用非常广泛.所谓链式存储结构,就是采用分散的存储空间分别存储每一组数据再通过地址链接的形式将全部数 ...

  3. C语言 线性表的链式存储结构(链表实现)

    描述 主题:链表 功能:分别实链表的插入.删除.查找操作 提示:如果需要进入下一步操作,输入3个ctrl z即可 代码 //主题:链表 //功能:分别实链表的插入.删除.查找操作 //提示:如果需要进 ...

  4. 数据结构|-二叉查找树(二叉搜索树)的链式存储结构的实现

    二叉排序树,又称为二叉查找树. 它或者是一棵空树,或者是具有下列性质的二叉树. 若它的左子树不为空,则左子树上所有的结点的值均小于根结构的值: 若它的右子树不为空,则右字数上所有结点的值均大于它的根结 ...

  5. 线性表之顺序存储结构与链式存储结构 及 应用

    前言 我们常用的线性表是顺序存储结构和链式存储结构表示,是最基本.最简单.也是最常用的一种数据结构:一个线性表是由n个相同特性的数据的有限序列:比如java中的数组 ,链表:所以学习这两种结构表示是非 ...

  6. 【数据结构】CH2 线性表的链式存储结构

    目录 一.链表概述 1.相关定义 二.单链表 1.插入和删除节点的操作 (1)插入结点 (2)删除结点 2.建立单链表 (1)头插法 (2)尾插法 3.线性表基本运算在单链表中的实现 (1)初始化线性 ...

  7. 数据结构之线性表——(二、链式存储结构)[c语言]

    数据结构之线性表--(二.链式存储结构-单链表) 链式存储结构以及基本运算的实现 背景:由于线性表的存储特点是用物理上的相邻实现逻辑上的相邻,他要求用连续的存储单元顺序存储线性表中的各个元素,所以,对 ...

  8. 数据结构第六篇——顺序存储结构与链式存储结构的特点

    ♥注:未经博主同意,不得转载. 两者特点: 顺序表的特点是逻辑上相邻的数据元素,物理存储位置也相邻,并且,顺序表的存储空间需要预先分配. 它的优点: (1)方法简单,各种高级语言中都有数组,容易实现. ...

  9. 线性表的链式存储结构以及单链表的插入和删除原理实现

    线性表的链式存储结构 线性表中的每个元素最多只有一个前驱元素和一个后继元素(其逻辑结构),因此可以采用链式存储结构存储. 链表 线性表的链式存储结构称为链表.在链表中每个结点不仅包含有元素本身的信息( ...

  10. 数据结构第三篇——线性表的链式存储之单链表

    ♥注:未经博主同意,不得转载. 线性表的链式存储结构的特点是用一组任意的存储单元来存储线性表的数据元素,这些单元可以分散在内存中的任意位置上,其在物理上可以是连续的,也可以是不连续的.具有链式存储结构 ...

最新文章

  1. emq+mysql设置_EMQ--添加mysql认证及mysql插件访问控制
  2. JavaEE Tutorials (13) - 使用锁定控制对实体数据的并发访问
  3. 天逸ad一66da_深入解析天逸ad66d与ad66a哪个好?区别是?内幕评测吐槽
  4. 利用StringUtils工具类进行String为空的判断
  5. 十一、网络编程。TCP\UDP\socket
  6. Gulp学习笔记(黑马教程)
  7. Redis管理及监控工具treeNMS
  8. discuz内置代码
  9. 锐捷设备AC旁挂核心交换机①
  10. Netapp存储性能调优
  11. 坚果pro2刷机魔趣9(安卓9.0)的分享
  12. PTA 7-10 查询水果价格
  13. vue标签旋转_基于vue下input实现图片上传,压缩,拼接以及旋转的代码详解
  14. Java和Java大数据有什么区别?
  15. 扑克与投资哲学,活着最重要
  16. JS实现抽奖代码(0-999随机数开始暂停抽奖按钮)
  17. 使用nginx做负载均衡
  18. 计算机8G内存是ram还是rom,128G的机身储存和8G的RAM运存,哪个更重要,千万别选错...
  19. 对勾函数_对勾函数-什么是对勾函数请问什么是对勾函数?如何使用 – 手机爱问...
  20. 黑色星期五c语言程序,黑色星期五

热门文章

  1. codeup 1006
  2. 英语复数名词的变化规则
  3. google谷歌云盘_如何在酒店房间使用Google Chromecast?
  4. linux教程第六章,第六章:依赖性 - scons用户指南_Linux教程_Linux公社-Linux系统门户网站...
  5. 用asp.net写的一个购物网站
  6. javascript实现前程无忧的选择城市
  7. python实现学生座位表排表
  8. dec是几进制(oct是几进制)
  9. 布丰投针实验(如何将Geogebra动态文件嵌入博客中)
  10. 模拟器使用Fiddler代理后,浏览器报错【该网站的安全证书有问题】解决方法