跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

数据结构:双向链表实现与算法分析

详细讲解双向链表的实现细节,包括查找、指定位置插入(前后)、删除操作及内存管理。对比了顺序表与链表在逻辑结构、存储方式及适用场景上的核心差异,指出顺序表适合随机访问,链表适合频繁增删。最后通过移除链表元素和反转链表两道经典算法题,演示了遍历构建新链表及三指针迭代反转的具体实现思路与代码逻辑。

安卓系统发布于 2026/3/30更新于 2026/10/2598 浏览
数据结构:双向链表实现与算法分析

数据结构:双向链表实现与算法分析

一、实现双向链表

1. 双向链表查找

双向链表的查找操作与单链表类似,但可利用创建一个暂时的指针实现遍历。

函数形式:

ListNode* LTFind(ListNode* h, type x);

实现:

ListNode* LTFind(ListNode* h, type x) {
    if (LTEmpty(h)) {
        return NULL;
    }
    ListNode* p = h->next;
    while (p != h) {
        if (p->data == x) {
            return p;
        }
        p = p->next;
    }
    return NULL;
}

说明: 通过遍历来实现,如果在遍历过程中找到了我们需要查找的数据就返回当前位置的节点,没有就返回空。

2. 双向链表在指定位置插入

双向链表在指定位置插入分为双向链表在指定位置之前插入与双向链表在指定位置之后插入。

双向链表在指定位置之后插入

函数形式:

void LTInsert(ListNode* pos, type x);

实现:

void LTInsert(ListNode* pos, type x) {
    assert(pos);
    ListNode* p = pos->next;
    ListNode* newnode = LTcreat(x);
    pos->next = newnode;
    newnode->prev = pos;
    newnode->next = p;
    p->prev = newnode;
}

说明: 该函数用于在双向链表的指定节点 pos 之后插入新节点,核心逻辑是通过调整指针关系实现无缝插入。需先保存后继节点,防止原链表后半部分丢失。

双向链表在指定位置之前插入

函数形式:

void LTInsertfront(ListNode* h, ListNode* pos, type x);

实现:

void LTInsertfront(ListNode* h, ListNode* pos, type x) {
    if (LTEmpty(h)) {
        return;
    }
    ListNode* p = h;
    while (p->next != h) {
        if (p->next == pos) {
            break;
        }
        p = p->next;
    }
    if (p->next == h) {
        return;
    }
    ListNode* newnode = LTcreat(x);
    ListNode* pr = p->next;
    newnode->next = pr;
    newnode->prev = p;
    p->next = newnode;
    pr->prev = newnode;
}

说明: 核心功能是在指定节点 pos 的前驱位置插入新节点(即在 pos 前面插入)。

3. 双向链表指定位置删除

删除节点需修改前驱节点的 next 和后继节点的 prev,并释放被删节点内存。关键是处理边界情况。

函数形式:

void LTErase(ListNode* pos);

实现:

void LTErase(ListNode* pos) {
    assert(pos);
    ListNode* p = pos->prev;
    p->next = pos->next;
    pos->next->prev = p;
    free(pos);
    pos = NULL;
}

说明: 步骤包括获取前驱节点 p;调整指针切断连接;释放 pos 节点内存,并置空局部指针。

4. 总代码展示

头文件 (1.h)

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int type;
typedef struct ListNode {
    type data;
    struct ListNode* prev;
    struct ListNode* next;
} ListNode;

void LTInit(ListNode** h);
void LTPushBack(ListNode* h, type x);
ListNode* LTcreat(type x);
void LTPushFront(ListNode* h, type x);
void LTPopBack(ListNode* h);
void LTPopFront(ListNode* h);
void LTDestory(ListNode* h);
void print(ListNode* h);
ListNode* LTFind(ListNode* h, type x);
void LTInsert(ListNode* pos, type x);
void LTInsertfront(ListNode* h, ListNode* pos, type x);
void LTErase(ListNode* pos);

实现文件 (1.cpp)

#include"1.h"

void LTInit(ListNode** h) {
    ListNode* ph = (ListNode*)malloc(sizeof(ListNode));
    if (ph == NULL) {
        perror("malloc fail!");
        exit(1);
    }
    *h = ph;
    (*h)->data = -1;
    (*h)->next = *h;
    (*h)->prev = *h;
}

ListNode* LTcreat(type x) {
    ListNode* ph = (ListNode*)malloc(sizeof(ListNode));
    if (ph == NULL) {
        perror("malloc fail!");
        exit(1);
    }
    ph->data = x;
    ph->next = ph;
    ph->prev = ph;
    return ph;
}

void LTPushBack(ListNode* h, type x) {
    ListNode* p = LTcreat(x);
    p->next = h;
    p->prev = h->prev;
    h->prev->next = p;
    h->prev = p;
}

void LTPushFront(ListNode* h, type x) {
    ListNode* p = LTcreat(x);
    p->next = h->next;
    p->prev = h;
    h->next->prev = p;
    h->next = p;
}

bool LTEmpty(ListNode* phead) {
    assert(phead);
    return phead->next == phead;
}

void LTPopBack(ListNode* h) {
    if (LTEmpty(h)) {
        return;
    }
    ListNode* p = h->prev;
    h->prev = p->prev;
    p->prev->next = h;
    free(p);
}

void LTPopFront(ListNode* h) {
    if (LTEmpty(h)) {
        printf("链表为空,无法头删\n");
        return;
    }
    ListNode* p = h->next;
    h->next = p->next;
    p->next->prev = h;
    free(p);
}

void LTDestory(ListNode* h) {
    if (LTEmpty(h)) {
        free(h);
        return;
    }
    ListNode* p = h->next;
    while (p != h) {
        ListNode* pr = p;
        p = p->next;
        free(pr);
    }
    free(h);
    h = NULL;
}

void print(ListNode* h) {
    if (LTEmpty(h)) {
        return;
    }
    ListNode* p = h->next;
    while (p != h) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}

ListNode* LTFind(ListNode* h, type x) {
    if (LTEmpty(h)) {
        return NULL;
    }
    ListNode* p = h->next;
    while (p != h) {
        if (p->data == x) {
            return p;
        }
        p = p->next;
    }
    return NULL;
}

void LTInsert(ListNode* pos, type x) {
    assert(pos);
    ListNode* p = pos->next;
    ListNode* newnode = LTcreat(x);
    pos->next = newnode;
    newnode->prev = pos;
    newnode->next = p;
    p->prev = newnode;
}

void LTInsertfront(ListNode* h, ListNode* pos, type x) {
    if (LTEmpty(h)) {
        return;
    }
    ListNode* p = h;
    while (p->next != h) {
        if (p->next == pos) {
            break;
        }
        p = p->next;
    }
    if (p->next == h) {
        return;
    }
    ListNode* newnode = LTcreat(x);
    ListNode* pr = p->next;
    newnode->next = pr;
    newnode->prev = p;
    p->next = newnode;
    pr->prev = newnode;
}

void LTErase(ListNode* pos) {
    assert(pos);
    ListNode* p = pos->prev;
    p->next = pos->next;
    pos->next->prev = p;
    free(pos);
    pos = NULL;
}

测试代码 (main.cpp)

#include"1.h"

void test() {
    ListNode* h;
    LTInit(&h);
    LTPushBack(h, 10);
    LTPushBack(h, 15);
    LTPushBack(h, 111);
    print(h);
    LTPushFront(h, 2);
    LTPushFront(h, 12);
    print(h);
    LTPopBack(h);
    print(h);
    LTPopFront(h);
    print(h);
    ListNode* p = LTFind(h, 10);
    LTInsert(p, 100);
    LTInsert(p, 200);
    LTErase(p);
    print(h);
    LTDestory(h);
}

int main() {
    test();
}

二、顺序表与链表的分析

相同点

  • 逻辑结构一致:均为线性表,数据元素之间呈一对一的顺序关系。
  • 核心操作相同:都支持插入、删除、查找、遍历等基本线性表操作。
  • 存储数据类型:均可存储相同类型的数据元素。

不同点(核心差异)

  • 存储方式:顺序表占用连续内存,链表占用离散内存。
  • 访问效率:顺序表支持随机访问,链表只能顺序访问。
  • 增删效率:顺序表增删涉及大量移动,链表仅需修改指针。

关键结论

  • 顺序表:适合频繁随机访问、数据量固定的场景。
  • 链表:适合频繁插入删除、数据量动态变化的场景。

三、链表算法题

一、移除链表元素

题目描述:移除值为 val 的节点,返回新的头结点。

解题思路: 创建新链表,遍历原链表,将值不等于 val 的节点拼接至新链表尾部。

代码实现:

typedef struct ListNode Node;
struct ListNode* removeElements(struct ListNode* head, int val) {
    Node *h = NULL, *pr = NULL;
    Node *p = head;
    while (p) {
        if (p->val != val) {
            if (h == NULL) {
                h = p;
                pr = p;
            } else {
                pr->next = p;
                pr = p;
            }
        }
        p = p->next;
    }
    if (pr) pr->next = NULL;
    return h;
}

说明:

  1. 初始化 h 为新链表头,pr 为尾节点。
  2. 遍历原链表,若当前节点值不等于 val,则将其加入新链表。
  3. 遍历结束后,将新链表尾节点 next 置为 NULL,避免野指针。

二、反转链表

题目描述:反转给定的单链表。

解题思路: 使用三个指针迭代法,逐次反转节点指向。

  • s1:指向已反转部分的头节点(初始为 NULL)。
  • s2:指向当前待反转节点(初始为 head)。
  • s3:临时保存 s2 的下一个节点。

代码实现:

typedef struct ListNode node;
struct ListNode* reverseList(struct ListNode* head) {
    node *s1 = NULL;
    node *s2 = head, *s3 = NULL;
    if (s2) {
        s3 = s2->next;
    }
    while (s2) {
        s2->next = s1;
        s1 = s2;
        s2 = s3;
        if (s3) {
            s3 = s3->next;
        }
    }
    return s1;
}

说明: 循环中依次执行:反转当前节点指向 -> 更新已反转头 -> 移动当前节点 -> 移动暂存节点。最终 s1 指向原链表尾节点,即新链表头。

目录

  1. 数据结构:双向链表实现与算法分析
  2. 一、实现双向链表
  3. 1. 双向链表查找
  4. 2. 双向链表在指定位置插入
  5. 双向链表在指定位置之后插入
  6. 双向链表在指定位置之前插入
  7. 3. 双向链表指定位置删除
  8. 4. 总代码展示
  9. 二、顺序表与链表的分析
  10. 相同点
  11. 不同点(核心差异)
  12. 关键结论
  13. 三、链表算法题
  14. 一、移除链表元素
  15. 二、反转链表

更多推荐文章

查看全部
  • 大模型产品经理转型指南:从零开始的全面路径
  • 机器人底盘无刷电机 PWM 调速板驱动 75mm 麦克纳姆轮
  • AI+AR智能眼镜赛道年终观察
  • LeetCode 92 链表区间反转:递归反转与哨兵技巧
  • ERNIE-4.5-0.3B 轻量模型部署指南与实战测评
  • 使用 AI 视觉编程模型优化 Unity 编辑器插件:从功能实现到界面美化
  • WebPShop 插件:Photoshop 支持 WebP 格式的解决方案
  • Antigravity 配置指南:打造类 VS Code 的 AI IDE
  • Linux 系统安装、故障排除、权限管理与安全配置指南
  • Stable Diffusion 完整训练与推理流程详解(附伪代码)
  • 2025 广西网安竞赛 AWD Web 部分赛后复盘与漏洞分析
  • AI 辅助 9·1 软件安装:环境检测与问题修复方案
  • C++ 面向对象编程核心:深入解析继承机制
  • C++显性契约与隐性规则:类型转换
  • Vue 实例劫持解除 Web 编辑器粘贴限制
  • 大数据领域 HDFS 在医疗行业的数据存储实践
  • PostgreSQL 18 本地安装与配置指南
  • Python 基于 Transformer 的时序数据建模与实现详解
  • FLAC3D 边坡地震稳定性动力分析:基线校正与模拟实现
  • C++ 红黑树:原理、旋转与完整实现

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online