数据结构与算法-线性表-双向链表(Double Linked List)

1 线性表

1.4 双向链表(Double Linked List)

双向链表的结点中有两个指针域,一个指向直接后继,另一个指向直接前驱,主要是为了解决前向查找的问题。

双向链表结构:

书籍和视频教程都只讲解了插入和删除的算法,这里也实现这两个算法。

一些常量和元素和前面类似,结点需要多一个前驱指针,结点结构代码如下:

c 复制代码
// 双向链表的结点结构
typedef struct DuLNode
{
    ElemType data;                // 结点数据,ElemType类型
    struct DuLNode *prior, *next; // 指向下一个结点的指针
} DuLNode, *DuLinkList;           // DuLinkList 是指向 DuLNode 结构的指针类型,表示单链表的头结点指针

另外为了方便查看插入的效果,需要初始化双向链表,和插入一些数据方便测试,这里使用尾插法进行处理,尾插法的实现和前面基本类似,就是新结点要设置相应的前驱:

c 复制代码
// 尾插法创建双向链表
Status CreateListTail(DuLinkList *L, int n)
{
    *L = (DuLinkList)malloc(sizeof(DuLNode)); // 创建头结点
    if (*L == NULL)
    {
        return OVERFLOW;
    }
    (*L)->next = NULL; // 初始化头结点的next指针为NULL

    DuLNode *p = *L; // p 指向头结点
    for (int i = 1; i <= n; i++)
    {
        // 创建一个新结点
        DuLNode *newNode = (DuLNode *)malloc(sizeof(DuLNode));
        if (!newNode) // 如果内存分配失败
            return OVERFLOW;
        newNode->data.x = i; // 将数据赋值给新结点

        // 插入新结点
        newNode->next = NULL; // 新结点的后继指针置空
        newNode->prior = p;   // 新结点的前驱指针指向当前结点
        p->next = newNode;    // 当前结点的后继指针指向新结点
        p = newNode;          // p 指向新插入的结点
    }
    return OK;
}

还有插入和删除都涉及另外一个算法------获取第 i 个结点,这里补充一下:

c 复制代码
// 获取第 i 个结点,并返回结点指针
DuLNode *GetElem_Dul(DuLinkList *L, int i)
{
    if (i < 1) // i 值不合法
        return NULL;
    DuLNode *p = (*L)->next; // 从头结点的下一个结点开始遍历
    int j = 1;               // 计数器,从1开始

    while (p != NULL && j < i) // 遍历到第i个结点 并且 当前结点不为空
    {
        p = p->next; // 移动到下一个结点
        j++;
    }

    if (p == NULL || j > i)
        return NULL;

    return p;
}

1.4.1 插入

插入的步骤要比单链表多设置几个指针指向,主要的难点在于顺序很重要,如果顺序不对,可能导致实际的指针被提前修改,无法获取到对应的结点。

【算法步骤】

  1. 将新结点的前驱指针指向当前结点的前驱结点。①
  2. 将当前结点的前驱结点的后继指针指向新结点。②
  3. 将新结点的后继指针指向当前结点。③
  4. 将当前结点的前驱指针指向新结点。④

【代码实现】

c 复制代码
// 插入结点,在 i-1 和 i 之间插入一个结点,新结点的位置就是 i
Status ListInsert(DuLinkList *L, int i, ElemType e)
{
    DuLNode *p;
    if (!(p = GetElem_Dul(L, i))) // 获取第 i 个结点
        return ERROR;

    DuLNode *newNode = (DuLNode *)malloc(sizeof(DuLNode)); // 创建新结点
    if (newNode == NULL)
        return OVERFLOW;

    newNode->data = e;         // 设置新结点的数据

    newNode->prior = p->prior; // 将新结点的前驱指针指向当前结点的前驱结点
    p->prior->next = newNode;  // 将当前结点的前驱结点的后继指针指向新结点
    newNode->next = p;         // 将新结点的后继指针指向当前结点
    p->prior = newNode;        // 将当前结点的前驱指针指向新结点

    return OK;
}

【算法分析】

时间复杂度主要受 GetElem_Dul 影响,因此为 O(n)

1.4.2 删除

【算法步骤】

  1. 将前驱结点的后继指针指向当前结点的后继结点。①
  2. 如果当前结点不是最后一个结点,将后继结点的前驱指针指向当前结点的前驱结点。②

【代码实现】

c 复制代码
// 删除第 i 个结点
Status ListDelete(DuLinkList *L, int i)
{
    DuLNode *p;
    if (!(p = GetElem_Dul(L, i))) // 获取第 i 个结点
        return ERROR;

    p->prior->next = p->next;      // 将前驱结点的后继指针指向当前结点的后继结点
    if (p->next != NULL)           // 如果当前结点不是最后一个结点
        p->next->prior = p->prior; // 将后继结点的前驱指针指向当前结点的前驱结点

    free(p); // 释放当前结点内存
    return OK;
}

【算法分析】

时间复杂度主要受 GetElem_Dul 影响,因此为 O(n)

相关推荐
CSharp精选营4 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
刘马想放假7 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠8 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
Darling噜啦啦15 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
小小工匠16 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
玖玥拾16 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
Qres82116 天前
算法复键——树状数组
数据结构·算法
牛油果子哥q16 天前
并查集(DSU)超精讲,路径压缩、按秩合并、万能模板、连通性判定、最小生成树与刷题实战全解
数据结构·c++·最小生成树·并查集
凌波粒16 天前
LeetCode--491.递增子序列(回溯算法)
数据结构·算法·leetcode
疯狂成瘾者16 天前
Java 集合 LinkedList 详解:链表结构、常用方法和队列使用
java·开发语言·链表