双链表详解

前言

在前面的文章中,我们已经学习了单链表,但是对于单链表有一个很大的问题就是,单链表只能找到后继结点,无法返回找到前驱。那么这一片文章中双链表就能很好的解决这个问题。

单链表 vs 双链表

双链表就是在单链表的基础上增加了前驱指针

typedef struct DNode{				// DNode(Double Node)
    ElemType data;					// 数据域
    struct DNode *prior,*next;		// 前驱和后继指针
}DNode,*DLinklist;

双链表的初始化

typedef struct DNode{				// DNode(Double Node)
    ElemType data;					// 数据域
    struct DNode *prior,*next;		// 前驱和后继指针
}DNode,*DLinklist;

bool InitDLinkList(DLinkList &L){
    L = (DNode *)malloc(sizeof(DNode));		// 分配一个头结点
    if(L==NULL)				// 内存不足,分配失败
        return false;
    L->prior = NULL;		// 头结点的前继指针指向NULL
    L->next = NULL;			// 头结点的后驱指针指向NULL
    
    return true;
}

void testDLinkList(){
    DLinklist L;
    InitDLinkList(L);
}

如上述代码初始化后形成的头节点如下图:

image-20220710151944130

判断双链表为空

判断链表是否为空,判断头结点后继指针是否为NULL

bool Empty(DLinklist L){
    if(L->next==NULL)
        return true;
    else
        return false;
}

双链表的插入

双链表插入新结点步骤:

  1. 将新结点的前驱指针指向上一个结点,后继指针指向下一个结点
  2. 将前继结点的后驱指针指向新结点
  3. 将后驱结点的前继指针指向新结点

image-20220710152620799

bool InsertNextDNode(DNode *p,DNode *s){
    if(p==NULL || s==NULL)		// 非法参数
        return false;
    s->next = p->next;
    if(p->next!=NULL)			// 如果p结点有后继结点
        p->next->prior=s;		// 将后继结点的前驱指针指向新结点
    s->prior = p;
    p->next = s;
    
    return true;
}

上面为什么要判断if(p->next!=NULL)呢?

因为如果我们是在链表的尾部插入结点的话,那么链表的最后一个元素的后继指针指向的是NULL(没有后继结点)那么就不存在后继指针的结点的前驱指针。

其实上述就是我们在单链表中讲述过的后插操作,只要实现了这个后插操作,就可很方便的实现其它的操作:

  1. 按位序插入
    • 找到需要插入的位置的前继结点,再对这个结点进行后插操作即可
  2. 前插操作
    • 找到给定结点的前驱结点,对前驱结点进行后插操作即可

双链表的删除

删除的步骤:

  1. 将要删除的结点的前驱结点的后继指针指向,要删除的结点的后继指针指向的结点
  2. 将要删除的结点的后继结点的前驱指针指向,要删除的结点的前驱指针指向的结点
  3. 使用free()函数释放要删除的结点

image-20220710161455420

为了加强代码的健壮性,我们还需要加入一些判断:

  1. 判断要删除的该节点是否有后继节点
bool DeleteNextNode(DNode *p){
    if(p==NULL) return false;	// 判断p结点是否存在
    DNode *q = p->next;			// 找到p结点的后继结点
    if(q==NULL) return false;	// 判断p的后继节点是否存在(q为要删除的结点)
    p->next = q->next;
    if(q->next!=NULL)			// 判断q结点是否是链表中的最后一个结点
        q->next->prior = p;
    free(q)
        
    return true;
}

销毁双链表

想要销毁双链表,首先要使用循环将链表中的结点一个个删除,最后将头结点释放。

void DestoryList(DLinklist &L){
    while(L->next!=NULL)
        DeleteNextDNode(L);
    free(L);			// 释放头结点
    L = NULL;			// 头指针指向NULL
}

双链表的遍历

前向遍历

while(p!=NULL){		// 将判断条件变为 p->prior!=NULL 则会跳过头结点
    p = p->prior;
}

后向遍历

while(p!=NULL){
    p = p->next;
}

时间复杂度

由于双链表不可进行随机存取,故按位查找、按值查找操作都只能用遍历的方式实现,时间复杂度为O(n)

结束语

已同步更新至个人博客:https://www.hibugs.net/index.php/dlinklist/

本人菜鸟一枚,仅分享学习笔记和经验。若有错误欢迎指出!共同学习、共同进步 😃

如果您觉得我的文章对您有所帮助,希望可以点个赞和关注,支持一下!十分感谢~(若您不想也没关系,只要文章能够对您有所帮助就是我最大的动力!)

下一篇文章传送门:正在更新,敬请期待…

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值