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

第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()方法,实现从后往前打印链表的元素。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值