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

死亡飞车.jpeg
死亡飞车.jpeg

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

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

双链表

什么是双链表?

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


双链表.png
双链表.png

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

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

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

插入步骤1.png
插入步骤1.png
双链表插入元素步骤2.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位置插入元素val
  int 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;//将头结点指向pval
                    pval->next=pCur;//pval的后继指针指向pCur
                    pval->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中的元素val
  pNode 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);
      //如果上方都不成立,说明链表中没有这个元素,直接返回NULL
      return 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位置插入新元素99
            printFront(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()方法,实现从后往前打印链表的元素。

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值