【数据结构实战】双向链表头插法

一、双向链表

链式存储结构的节点中只有一个指示直接后继的指针域,由此,从某个结点出发只能顺指针向后寻查其他节点。若要寻查结点的直接前驱、则必须从表头指针出发。换句话说,在单链表中,查找直接后继的执行时间为〇(1),而查找直接前驱的执行时间为 O(n)为克服单链表这种单向性的缺点,可利用双向链表(Double Linked List)。双向链表的节点中有两个指针域,一个指向直接后继,另一个指向直接前驱。

二、双向链表结构与初始化

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

// 定义链表存储的数据类型为int
typedef int ElemType;

// 定义双向链表节点结构
typedef struct node {
    ElemType data;       // 数据域
    struct node *next;   // 后继指针,指向后一个节点
    struct node *prev;   // 前驱指针,指向前一个节点
} Node;

/**
 * 初始化双向链表(带头节点)
 * 返回值:指向头节点的指针
 */
Node* initList() {
    // 动态分配头节点内存
    Node *head = (Node*)malloc(sizeof(Node));
    if (head == NULL) {  // 内存分配失败判断
        printf("内存分配失败!\n");
        exit(1);
    }
    head->data = 0;      // 头节点数据域可忽略,一般不存储有效数据
    head->next = NULL;   // 后继指针初始化为空
    head->prev = NULL;   // 前驱指针初始化为空
    return head;
}

三、头插法核心代码(重点)

cpp 复制代码
/**
 * 双向链表头插法(在头节点后插入新节点)
 * 参数 L:链表头节点指针
 * 参数 e:待插入的数据
 * 返回值:1表示插入成功
 */
int insertHead(Node* L, ElemType e) {
    // 1. 为新节点分配内存
    Node *p = (Node*)malloc(sizeof(Node));
    if (p == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }

    // 2. 给新节点赋值
    p->data = e;         // 新节点数据域 = 待插入数据
    p->prev = L;         // 新节点的前驱 = 头节点
    p->next = L->next;   // 新节点的后继 = 原头节点的后继节点

    // 3. 如果原链表不为空(头节点后有节点),需要修改原第一个节点的前驱指针
    if (L->next != NULL) {
        L->next->prev = p;  // 原第一个节点的前驱 = 新节点
    }

    // 4. 头节点的后继指针指向新节点(完成插入)
    L->next = p;

    return 1;  // 插入成功
}
头插法核心思想:
  1. 新节点搭桥:先让新节点 p 连接到链表结构中(p->prev 指向头节点,p->next 指向原第一个节点)。

  2. 旧节点修正:如果原链表不为空,要让原第一个节点的 prev 指针指向新节点,保证双向链接完整。

  3. 头节点挂接:最后让头节点 Lnext 指针指向新节点,完成 "插入到最前面" 的操作。

  4. 顺序关键:必须先处理新节点的指针再修改原有节点的指针 ,否则会丢失原链表的引用。


三、遍历链表代码

cpp 复制代码
/**
 * 遍历并打印双向链表(从第一个有效节点开始)
 * 参数 L:链表头节点指针
 */
void listNode(Node* L) {
    Node *p = L->next;  // 从第一个有效节点开始遍历(跳过头节点)
    while (p != NULL) { // 遍历到链表末尾
        printf("%d ", p->data);  // 打印当前节点数据
        p = p->next;             // 指针后移
    }
    printf("\n");  // 遍历结束后换行
}

四、完整可运行代码(含主函数测试)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType;

typedef struct node {
    ElemType data;
    struct node *next;
    struct node *prev;
} Node;

// 初始化链表
Node* initList() {
    Node *head = (Node*)malloc(sizeof(Node));
    if (head == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }
    head->data = 0;
    head->next = NULL;
    head->prev = NULL;
    return head;
}

// 头插法插入节点
int insertHead(Node* L, ElemType e) {
    Node *p = (Node*)malloc(sizeof(Node));
    if (p == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }
    p->data = e;
    p->prev = L;
    p->next = L->next;
    if (L->next != NULL) {
        L->next->prev = p;
    }
    L->next = p;
    return 1;
}

// 遍历链表
void listNode(Node* L) {
    Node *p = L->next;
    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }
    printf("\n");
}

// 主函数:测试双向链表头插法
int main() {
    // 1. 初始化链表
    Node *list = initList();

    // 2. 头插法插入数据(注意:头插法会让数据逆序存储)
    insertHead(list, 10);
    insertHead(list, 20);
    insertHead(list, 30);

    // 3. 遍历打印链表
    printf("链表元素:");
    listNode(list);  // 输出:30 20 10

    return 0;
}

五、运行结果说明

因为头插法是将新节点插入到链表最前面 ,所以先插入的 10 会被后插入的 2030 依次挤到后面,最终遍历顺序是 30 → 20 → 10


六、补充:双向循环链表改造

如果要改成双向循环链表,只需要在初始化和插入时做两处修改:

  1. 初始化时:head->next = head; head->prev = head;
  2. 头插法中:将 NULL 判断改为 != head,并保证最后一个节点的 next 指向头节点、头节点的 prev 指向最后一个节点
相关推荐
为何创造硅基生物6 小时前
C语言 结构体内存对齐规则(通俗易懂版)
c语言·开发语言
仰泳之鹅7 小时前
【C语言】自定义数据类型2——联合体与枚举
c语言·开发语言·算法
jolimark7 小时前
C语言自学攻略:小白入门三步走
c语言·编程入门·学习路线·实践项目·自学攻略
cen__y8 小时前
Linux12(Git01)
linux·运维·服务器·c语言·开发语言·git
社交怪人9 小时前
【算平均分】信息学奥赛一本通C语言解法(题号2071)
c语言·开发语言
卢锡荣9 小时前
单芯通吃,盲插标杆 —— 乐得瑞 LDR6020,Type‑C 全场景互联 “智慧芯”
c语言·开发语言·计算机外设
Mr. zhihao10 小时前
深入解析redis基本数据结构
数据结构·数据库·redis
念何架构之路10 小时前
Go语言加密算法
数据结构·算法·哈希算法
AI科技星10 小时前
《数学公理体系·第三部·数术几何》(2026 年版)
c语言·开发语言·线性代数·算法·矩阵·量子计算·agi
失去的青春---夕阳下的奔跑10 小时前
560. 和为 K 的子数组
数据结构·算法·leetcode