【数据结构实战】双向链表:删除节点

一、双向链表结构与基础函数

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;
}

/**
 * 获取双向链表的尾节点
 * 参数 L:链表头节点指针
 * 返回值:尾节点指针
 */
Node* get_tail(Node *L) {
    Node *p = L;
    // 遍历链表,直到找到next为NULL的节点(即尾节点)
    while (p->next != NULL) {
        p = p->next;
    }
    return p;
}

/**
 * 双向链表尾插法(在链表末尾插入新节点)
 * 参数 tail:当前尾节点指针
 * 参数 e:待插入的数据
 * 返回值:新的尾节点指针
 */
Node* insertTail(Node *tail, ElemType e) {
    // 1. 为新节点分配内存
    Node *p = (Node*)malloc(sizeof(Node));
    if (p == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }

    // 2. 给新节点赋值
    p->data = e;         // 新节点数据域 = 待插入数据
    p->prev = tail;      // 新节点前驱指针 = 当前尾节点
    tail->next = p;      // 当前尾节点的后继指针 = 新节点
    p->next = NULL;      // 新节点作为新尾节点,后继指针置空

    // 3. 返回新的尾节点指针
    return p;
}

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

二、指定位置插入函数

cpp 复制代码
/**
 * 双向链表指定位置插入(在第pos个位置后插入新节点,pos从1开始)
 * 参数 L:链表头节点指针
 * 参数 pos:插入位置(1为第一个有效节点的位置)
 * 参数 e:待插入的数据
 * 返回值:1表示插入成功,0表示插入失败
 */
int insertNode(Node *L, int pos, ElemType e) {
    Node *p = L;
    int i = 0;
    // 1. 遍历找到第pos-1个节点(即插入位置的前驱节点)
    while (i < pos - 1) {
        p = p->next;
        i++;
        // 如果遍历到链表末尾仍未找到目标位置,说明pos非法
        if (p == NULL) {
            return 0;
        }
    }

    // 2. 为新节点分配内存
    Node *q = (Node*)malloc(sizeof(Node));
    if (q == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }

    // 3. 给新节点赋值
    q->data = e;         // 新节点数据域 = 待插入数据
    q->prev = p;         // 新节点前驱 = 第pos-1个节点
    q->next = p->next;   // 新节点后继 = 原第pos个节点

    // 4. 如果原第pos个节点存在,修改其前驱指针
    if (p->next != NULL) {
        p->next->prev = q;
    }

    // 5. 修改前驱节点的后继指针,完成插入
    p->next = q;

    return 1;  // 插入成功
}

三、删除节点核心代码(重点)

cpp 复制代码
/**
 * 双向链表删除指定位置节点(删除第pos个节点,pos从1开始)
 * 参数 L:链表头节点指针
 * 参数 pos:删除位置(1为第一个有效节点的位置)
 * 返回值:1表示删除成功,0表示删除失败
 */
int deleteNode(Node *L, int pos) {
    Node *p = L;
    int i = 0;
    // 1. 遍历找到第pos-1个节点(即要删除节点的前驱节点)
    while (i < pos - 1) {
        p = p->next;
        i++;
        // 如果遍历到链表末尾仍未找到目标位置,说明pos非法
        if (p == NULL) {
            return 0;
        }
    }

    // 2. 判断要删除的节点是否存在
    if (p->next == NULL) {
        printf("要删除的位置错误\n");
        return 0;
    }

    // 3. 用指针q记录要删除的节点
    Node *q = p->next;

    // 4. 重新建立双向链接,跳过要删除的节点
    p->next = q->next;          // 前驱节点的后继 = 要删除节点的后继
    // 如果要删除的节点不是尾节点,修改后继节点的前驱
    if (q->next != NULL) {
        q->next->prev = p;
    }

    // 5. 释放要删除节点的内存,避免内存泄漏
    free(q);

    return 1;  // 删除成功
}
删除节点核心思想:
  1. 定位前驱节点:先遍历到要删除节点的前一个节点pos-1位置),保证能安全修改链表结构。
  2. 记录待删节点:用指针 q 暂存要删除的节点,避免后续指针修改后丢失引用。
  3. 重建双向链接
    • 让前驱节点 pnext 直接指向待删节点的后继节点。
    • 如果后继节点存在,让它的 prev 指向前驱节点 p,保证双向链表结构完整。
  4. 释放内存:调用 free(q) 释放待删节点的内存,避免内存泄漏。
  5. 边界处理:必须判断 p->next 是否为 NULL(即删除位置超出链表长度),避免对空指针解引用。

四、释放链表函数

cpp 复制代码
/**
 * 释放双向链表所有节点内存(包括头节点)
 * 参数 L:链表头节点指针
 */
void freeList(Node *L) {
    Node *p = L->next;  // 从第一个有效节点开始
    Node *q;
    // 遍历释放所有有效节点
    while (p != NULL) {
        q = p->next;  // 暂存下一个节点
        free(p);      // 释放当前节点
        p = q;        // 指针后移
    }
    free(L);  // 最后释放头节点
}

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

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;
}

// 获取双向链表的尾节点
Node* get_tail(Node *L) {
    Node *p = L;
    while (p->next != NULL) {
        p = p->next;
    }
    return p;
}

// 双向链表尾插法
Node* insertTail(Node *tail, ElemType e) {
    Node *p = (Node*)malloc(sizeof(Node));
    if (p == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }
    p->data = e;
    p->prev = tail;
    tail->next = p;
    p->next = NULL;
    return p;
}

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

// 双向链表指定位置插入
int insertNode(Node *L, int pos, ElemType e) {
    Node *p = L;
    int i = 0;
    while (i < pos - 1) {
        p = p->next;
        i++;
        if (p == NULL) {
            return 0;
        }
    }
    Node *q = (Node*)malloc(sizeof(Node));
    if (q == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }
    q->data = e;
    q->prev = p;
    q->next = p->next;
    if (p->next != NULL) {
        p->next->prev = q;
    }
    p->next = q;
    return 1;
}

// 双向链表删除指定位置节点
int deleteNode(Node *L, int pos) {
    Node *p = L;
    int i = 0;
    while (i < pos - 1) {
        p = p->next;
        i++;
        if (p == NULL) {
            return 0;
        }
    }
    if (p->next == NULL) {
        printf("要删除的位置错误\n");
        return 0;
    }
    Node *q = p->next;
    p->next = q->next;
    if (q->next != NULL) {
        q->next->prev = p;
    }
    free(q);
    return 1;
}

// 释放双向链表
void freeList(Node *L) {
    Node *p = L->next;
    Node *q;
    while (p != NULL) {
        q = p->next;
        free(p);
        p = q;
    }
    free(L);
}

// 主函数:测试双向链表删除节点
int main() {
    // 1. 初始化双向链表
    Node *list = initList();

    // 2. 尾插法插入初始数据
    Node *tail = get_tail(list);
    tail = insertTail(tail, 10);
    tail = insertTail(tail, 20);
    tail = insertTail(tail, 30);
    printf("初始链表:");
    listNode(list);  // 输出:10 20 30

    // 3. 在第2个位置插入15
    insertNode(list, 2, 15);
    printf("插入后链表:");
    listNode(list);  // 输出:10 15 20 30

    // 4. 删除第2个位置的节点
    deleteNode(list, 2);
    printf("删除后链表:");
    listNode(list);  // 输出:10 20 30

    // 5. 释放链表内存
    freeList(list);
    return 0;
}

六、运行结果说明

执行后输出:

  • 初始尾插得到 10 → 20 → 30
  • 在第 2 个位置插入 15 后,链表变为 10 → 15 → 20 → 30
  • 删除第 2 个位置的节点后,链表恢复为 10 → 20 → 30,符合预期。

七、尾插法核心思想回顾

  1. 定位尾节点:通过 get_tail 遍历找到链表最后一个节点
  2. 新节点连接:新节点 pprev 指向原尾节点,原尾节点的 next 指向新节点,新节点的 next 置为 NULL
  3. 更新尾节点:函数返回新节点指针,方便后续连续尾插,避免重复遍历找尾。
  4. 顺序保证:先建立新节点与原尾节点的双向链接,再将新节点的 next 置空,保证链表结构完整。
相关推荐
y = xⁿ2 小时前
【LeetCodehot100】 T543:二叉树的直径 T102:二叉树的层序遍历
算法
敲上瘾2 小时前
位图与布隆过滤器:原理、实现与海量数据处理方案
大数据·数据结构·算法·位图·布隆过滤器
宵时待雨2 小时前
C++笔记归纳13:map & set
开发语言·数据结构·c++·笔记·算法
1104.北光c°3 小时前
滑动窗口HotKey探测机制:让你的缓存TTL更智能
java·开发语言·笔记·程序人生·算法·滑动窗口·hotkey
仰泳的熊猫7 小时前
题目2570:蓝桥杯2020年第十一届省赛真题-成绩分析
数据结构·c++·算法·蓝桥杯
无极低码10 小时前
ecGlypher新手安装分步指南(标准化流程)
人工智能·算法·自然语言处理·大模型·rag
爱编码的小八嘎10 小时前
C语言完美演绎4-7
c语言
软件算法开发11 小时前
基于海象优化算法的LSTM网络模型(WOA-LSTM)的一维时间序列预测matlab仿真
算法·matlab·lstm·一维时间序列预测·woa-lstm·海象优化