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

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

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:链表头节点指针
 * 返回值:尾节点指针
 */
Node* get_tail(Node *L) {
    Node *p = L;
    // 遍历链表,直到找到next为NULL的节点(即尾节点)
    while (p->next != NULL) {
        p = p->next;
    }
    return p;
}

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

cpp 复制代码
/**
 * 双向链表尾插法(在链表末尾插入新节点)
 * 参数 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;
}
尾插法核心思想:
  1. 定位尾节点:通过 get_tail 函数遍历找到当前链表的最后一个节点(nextNULL 的节点)。
  2. 新节点连接
    • 新节点 pprev 指向原尾节点,建立向前链接
    • 原尾节点的 next 指向新节点 p,建立向后链接
    • 新节点 pnext 置为 NULL标志其成为新的尾节点。
  3. 更新尾节点:函数返回新节点指针,方便后续连续尾插时直接使用,避免重复遍历找尾。
  4. 顺序关键 :必须先完成新节点与原尾节点的双向链接再将新节点的 next 置空,保证链表结构完整。

四、遍历链表函数

cpp 复制代码
/**
 * 遍历并打印双向链表(从第一个有效节点开始)
 * 参数 L:链表头节点指针
 */
void listNode(Node* L) {
    Node *p = L->next;  // 从第一个有效节点开始遍历(跳过头节点)
    while (p != NULL) { // 遍历到链表末尾(next为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;
}

// 获取双向链表的尾节点
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 main() {
    // 1. 初始化双向链表
    Node *list = initList();

    // 2. 获取初始尾节点(头节点)
    Node *tail = get_tail(list);

    // 3. 尾插法插入数据(尾插法数据按插入顺序存储)
    tail = insertTail(tail, 10);
    tail = insertTail(tail, 20);
    tail = insertTail(tail, 30);

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

    return 0;
}

六、运行结果说明

执行后输出:

因为尾插法是将新节点插入到链表末尾 ,所以插入顺序和遍历顺序完全一致,最终遍历顺序是 10 → 20 → 30,且尾节点的 next 始终为 NULL,头节点的 prev 也为 NULL,保持双向链表非循环的结构。

七、与双向循环链表尾插法的区别

特性 双向链表(非循环) 双向循环链表
尾节点next 指向NULL 指向头节点
头节点prev 指向NULL 指向尾节点
找尾节点方式 遍历到next == NULL 直接取L->prev
遍历终止条件 p == NULL p == L(回到头节点)
相关推荐
apocelipes2 天前
常用编程语言和库的正则表达式性能对比
c语言·c++·python·性能优化·golang·开发工具和环境
CSharp精选营5 天前
关系型 vs 非关系型:从原理到选型,一文搞定数据库核心分类
数据结构·nosql·关系型数据库·非关系型数据库·技术选型
刘马想放假8 天前
Modbus 全栈技术解析:TCP、RTU、ASCII、RTU over TCP
数据结构·网络协议
北域码匠9 天前
冒泡排序太慢?鸡尾酒排序双向优化,原生 C# 零第三方库完整代码
数据结构·排序算法·泛型·c# 算法·鸡尾酒排序·原生 c# 开发·冒泡排序优化·嵌入式算法
Darling噜啦啦16 天前
列表转树算法深度解析:从 Map 到 Reduce 的两种实现,面试高频考点
数据结构·算法·面试
LDR00617 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
小小工匠17 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
Luminous.17 天前
C语言--day30
c语言·开发语言
玖玥拾17 天前
C/C++ 数据结构(七)栈、容器适配器
c语言·数据结构·c++··容器适配器
謓泽17 天前
C语言不是语法,是通往机器的地图。
c语言·开发语言