从0开始学习数据结构-双向链表及其操作

186 阅读3分钟

这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战

双向链表是线性表的链式存储结构的又一种形式。

双向链表也简称双链表。

循环链表的不足

image.png

双向链表的构造

双向链表:链表的每一个链结点中除了数据域以外设置两个指针域,一个指向结点的直接前驱节点,另一个指向结点的直接后继结点。

链结点的构造

image.png

双向链表的描述

typedef struct node {
    ElemType data;
    struct node *llink, *rlink;
} DNode, *DLinkList; // 定义一个双向链表类型

不带头结点的双向链表 image.png 不带头结点的双向循环链表 image.png 带头结点的双向循环链表 双向循环链表有一个特性,即若p为指向链表中某结点的指针,在表达式中有: p->llink->rlink = p->rlink->llink = p image.png

双向链表的插入与删除算法

双向链表的一些操作与线性链表的算法基本相同。双向链表与单链表不同的地方是:

  • 在双向链表中插入一个新的链结点
  • 删除双向链表中一个链结点

在双向链表中插入一个新的链结点

  • 在带有头结点的双向循环链表中第1个数据域内容为x的结点右边插入一个数据信息为item的新结点。 基本思想

image.png

image.png

算法

int INSERTD(DLinkList list, ElemType x, ElemType item)
{
    DLinkList p, q;
    q = list->rlink; // 首先q指向头结点后面的那个结点
    while(q != list && q->data != x) // 寻址第1个满足条件的结点
        q = q->rlink;
    if (q == list) {
        ERRORMESSAGE("链表中无满足条件的结点!");
        return -1; // 插入失败,返回-1
    }
    p = (DLinkList)malloc(sizeof(DNode)); // 申请一个新的链结点
    p->data = item; // 新结点的值为item
    p->llink = q; // 新结点左指针为q
    p->rlink = q->rlink; // 新结点的右指针为q的右指针
    // 下面两句赋值次序不能颠倒,如果颠倒了,就必须在对其中的一条语句做相应的修改
    q->rlink->llink = p; // q的右指针的左指针指向p
    q->rlink = p; // q的右指针指向p
    return 1; // 插入成功,返回1
}

时间复杂度

  • 问题规模:双向链表的长度n
  • 查找满足条件的结点(查找插入点位置):O(n)
  • 插入一个新结点的过程:O(1)
  • 总的时间复杂度:O(n)

删除双向链表中一个链结点

  • 从带有头结点的双向循环链表中删除第1个数据域内容为x的结点. 思想:

image.png

image.png 算法:

int DELETED(DLinkList list, ElemType x)
{
    DLinkList q;
    q = list->rlink; // 首先q指向头结点后面的那个结点
    while(q != list && q->data != x) 
        q = q->rlink;
    if (q == list) {
        ERRORMESSAGE("链表中无满足条件的结点!");
        return -1; // 删除失败,返回-1
    }
    q->llink->rlink = q->rlink; // ※ q的前驱结点的右指针指向q的右指针
    q->rlink->llink = q->llink; // ※ q的后继结点的左指针指向q的左指针
    free(q); // 释放结点q
    return 1; // 删除成功,返回1
}

image.png 时间复杂度: O(n)

若线性表的主要操作是插入和删除,尤其是表的最后那个元素之后插入一个新元素,或者删除表的最后那个元素,采用带头结点的双向循环链表存储结构最节省运算时间。