26考研——线性表_ 线性表的链式表示_双循环链表(2)

发布于:2025-04-06 ⋅ 阅读:(17) ⋅ 点赞:(0)

408答疑



三、 线性表的链式表示

双循环链表

单链表与双链表的比较

单链表的特点
  • 单链表结点中只有一个指向其后继的指针,使得单链表只能从前往后依次遍历。
  • 访问某个结点的前驱(插入、删除操作时),只能从头开始遍历,访问前驱的时间复杂度为 O ( n ) O(n) O(n)
  • 为了克服单链表的这个缺点,引入了双链表。
双链表的特点
  • 双链表结点中有两个指针 priornext,分别指向其直接前驱和直接后继,如下图所示。表头结点的 prior 域和尾结点的 next 域都是 NULL

在这里插入图片描述

  • 双链表在单链表结点中增加了一个指向其前驱的指针 prior,因此双链表的按值查找和按位查找的操作与单链表的相同。但双链表在插入和删除操作的实现上,与单链表有着较大的不同。这是因为“链”变化时也需要对指针 prior 做出修改,其关键是保证在修改的过程中不断链。此外,双链表可以很方便地找到当前结点的前驱,因此,插入、删除操作的时间复杂度仅为 O ( 1 ) O(1) O(1)

双链表上基本操作的实现

双链表的插入操作
  • 在双链表中 p 所指的结点之后插入结点 *s,其指针的变化过程如图所示。

在这里插入图片描述

  • 插入操作的步骤如下:

    1. s->next 指向 p->next
    2. p->next->prior 指向 s
    3. s->prior 指向 p
    4. p->next 指向 s
  • 上述代码的语句顺序不是唯一的,但也不是任意的。步骤 1 必须在步骤 4 之前,否则 *p 的后继结点的指针就会丢失,导致插入失败。

双链表的删除操作
  • 删除双链表中结点 *p 的后继结点 *q,其指针的变化过程如图所示。

在这里插入图片描述

  • 删除操作的步骤如下:

    1. p->next 指向 q->next
    2. q->next->prior 指向 p
    3. 释放 q 结点空间

双链表的代码实操

定义结点
  • 定义双链表的节点类型,包括数据域、前驱指针和后继指针。
typedef struct DCListNode {
    ElemType data;          // 数据域
    struct DCListNode *prev; // 前驱指针
    struct DCListNode *next; // 后继指针
} DCListNode, *DCLinkList;
创建一个结点
  • 创建并初始化一个新的双链表节点。
DCListNode* buyNode(ElemType x) {
    DCListNode *s = (DCListNode*)malloc(sizeof(DCListNode));
    s->data = x;
    return s;
}
带头结点的双链表初始化
  • 初始化一个带头结点的双链表。
void initDCList(DCListNode **head) {
    *head = (DCListNode*)malloc(sizeof(DCListNode));
    (*head)->prev = *head;
    (*head)->next = *head;
}
创建双链表
  • 根据给定数组创建一个双链表。
void createDCList(DCLinkList DCL, ElemType ar[], int n) {
    DCListNode *p = DCL;
    for (int i = 0; i < n; ++i) {
        DCListNode *s = (DCListNode*)malloc(sizeof(DCListNode));
        s->data = ar[i];
        s->prev = p->prev;
        s->next = p;
        p->prev->next = s;
        p->prev = s;
    }
}
打印双链表
  • 从头结点的下一个节点开始,打印链表中的每个节点数据,直到回到头结点。
void printDCList(DCLinkList DCL) {
    DCListNode *p = DCL->next;
    while (p != DCL) {
        printf("%d-->", p->data);
        p = p->next;
    }
    printf("Over.\n");
}
查找结点
  • 从头结点开始,遍历链表直到找到目标节点或到达头结点。
DCListNode* findDCNode(DCLinkList DCL, ElemType key) {
    DCListNode *p = DCL->next;
    while (p != DCL && p->data != key)
        p = p->next;

    if (p != DCL)
        return p; // 找到了节点

    return NULL;  // 没有找到节点
}
插入结点
在指定节点后插入新节点
  • 在双链表中指定节点 pos 之后插入一个新节点 x。
void insertDCNodeBack(DCLinkList DCL, DCListNode *pos, ElemType x) {
    DCListNode *s = buyNode(x);
    s->prev = pos;
    s->next = pos->next;
    s->next->prev = s;
    s->prev->next = s;
}
在指定节点前插入新节点
  • 在双链表中指定节点 pos 之前插入一个新节点 x。
void insertDCNodeFront(DCLinkList DCL, DCListNode *pos, ElemType x) {
    DCListNode *s = buyNode(x);
    s->prev = pos->prev;
    s->next = pos;
    s->next->prev = s;
    s->prev->next = s;
}
删除结点
  • 找到目标节点,调整前后节点的指针并释放目标节点。
void deleteDCNode(DCLinkList DCL, ElemType key) {
    DCListNode *p = findDCNode(DCL, key);
    if (p == NULL)
        return; // 删除失败

    p->prev->next = p->next;
    p->next->prev = p->prev;
    free(p);
}
反转链表
  • 逐个摘除链表中的节点并重新插入到链表头部。
void reverseDCList(DCLinkList DCL) {
    DCListNode *p = DCL->next;
    DCListNode *q = p->next;
    p->next = DCL;
    DCL->prev = p;

    while (q != DCL) {
        p = q;
        q = q->next;

        p->next = DCL->next;
        p->prev = DCL;
        p->prev->next = p;
        p->next->prev = p;
    }
}
排序链表
  • 逐个摘除链表中的节点并按顺序插入到链表中。
void sortDCList(DCLinkList DCL) {
    DCListNode *p = DCL->next;
    DCListNode *q = p->next;

    p->next = DCL;
    DCL->prev = p;

    while (q != DCL) {
        p = q;
        q = q->next;

        DCListNode *cur = DCL->next;
        while (cur != DCL && p->data > cur->data)
            cur = cur->next;

        p->next = cur;
        p->prev = cur->prev;
        p->prev->next = p;
        p->next->prev = p;
    }
}

循环链表

循环单链表
  • 循环单链表的特点是最后一个结点的 next 指针指向头结点,形成一个环。这种结构使得链表可以从任意位置开始遍历,而不必担心到达链表的末尾,如下图所示。

在这里插入图片描述

  • 特点
    • 表尾结点的 next 域指向头结点。
    • 判空条件不是头结点的指针是否为空,而是它是否等于头指针 L
操作描述
  • 循环单链表的插入、删除算法与单链表的几乎一样,所不同的是,若操作是在表尾进行,则执行的操作不同,以让单链表继续保持循环的性质。这是因为循环单链表是一个“环”,所以在任何位置上的插入和删除操作都是等价的,而无须判断是否是表尾。
操作特点
  • 在单链表中只能从表头结点开始往后顺序遍历整个链表,而循环单链表可以从表中的任意一个结点开始遍历整个链表。
  • 有时对循环单链表不设头指针而仅设尾指针,以使得操作效率更高。其原因是,若设的是头指针,对在表尾插入元素需要 O ( n ) O(n) O(n) 的时间复杂度,而若设的是尾指针 rr->next 即头指针,对在表头或表尾插入元素都只需要 O ( 1 ) O(1) O(1) 的时间复杂度。
循环双链表
  • 循环双链表在循环单链表的基础上增加了一个指向前驱的指针 prior,使得链表可以从任意结点向前或向后遍历,如下图所示。

在这里插入图片描述

  • 特点
    • 头结点的 prior 指针指向表尾结点。
    • 尾结点的 next 指针指向头结点。
    • 判空条件是头结点的 priornext 域都等于 L

链表的分类

  • 循环链表可以是单链表或双链表,可以带头结点或不带头结点,也可以是循环的或非循环的( 2 3 2^3 23种可能性)。这三种特性可以组合出八种不同的链表类型,如下图所示。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

五、参考资料

鲍鱼科技课件

b站免费王道课后题讲解:
在这里插入图片描述

网课全程班:
在这里插入图片描述

26王道考研书


网站公告

今日签到

点亮在社区的每一天
去签到