惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
SegmentFault 最新的问题
B
Blog
P
Proofpoint News Feed
美团技术团队
The GitHub Blog
The GitHub Blog
Y
Y Combinator Blog
A
About on SuperTechFans
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Vercel News
Vercel News
有赞技术团队
有赞技术团队
小众软件
小众软件
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Google DeepMind News
Google DeepMind News
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
aimingoo的专栏
aimingoo的专栏
H
Help Net Security
罗磊的独立博客
L
LangChain Blog
GbyAI
GbyAI
腾讯CDC
T
The Blog of Author Tim Ferriss
Microsoft Security Blog
Microsoft Security Blog

博客园 - androllen

Python yield 关键词 PDF 构成 桌面客户端的主要类型和技术方案 Python 压缩转义 异步删除嵌套文件夹及文件 数据结构 二叉树遍历 JS 字符串转换为函数体 VSCode 配置内部Python项目 缺少模块ModuleNotFoundError C# 基础知识 算法-数列 算法-排序算法 C# 设计模式-简单工厂模式 C# 设计模式 C# Utils .Net 异步与同步 WPF StreamGeometry Vue 打开窗口输出文件路径 CSS Flexbox layout 2 CSS Flexbox layout 1
数据结构 线性表
androllen · 2025-03-21 · via 博客园 - androllen

尾插法

//实现方式
Node* createListTailInsert() {
    Node* head = (Node*)malloc(sizeof(Node));  // 创建头节点
    head->next = NULL;

    int n, data;
    printf("请输入要插入的节点数: ");
    scanf("%d", &n);

    Node* tail = head;  // 尾指针初始化为头节点

    for (int i = 0; i < n; ++i) {
        printf("请输入第 %d 个节点的数据: ", i + 1);
        scanf("%d", &data);

        // 创建新节点
        Node* newNode = (Node*)malloc(sizeof(Node));
        newNode->data = data;
        newNode->next = NULL;

        // 插入到尾部
        tail->next = newNode;
        tail = newNode;  // 更新尾指针
    }

    return head;
}

假设:
尾指针和闯红灯指挥员

简单理解:

typedef struct Node {
    int data;           // 数据域
    struct Node* next;  // next指针域
} Node;

Node *node1 = (Node*)malloc(sizeof(Node));
Node *node2 = (Node*)malloc(sizeof(Node));
Node *node3 = (Node*)malloc(sizeof(Node));

node1->data = 10;
node2->data = 20;
node3->data = 30;

// 设置next指针
node1->next = node2;
node2->next = node3;
node3->next = NULL;  // 最后一个节点的next指向NULL

这个时候加入了尾指针 tail

tail:是一个辅助指针,用于动态跟踪链表的最后一个节点

想象一下 如果第一个闯红灯后一直被留到最后,而后面闯红灯没有被罚会有多崩溃,所以每次后闯红灯的人都会接替前一个闯红灯的人
此时我们需要tail = newNode时, 将 tail 更新为指向新插入的节点,确保 tail 始终指向链表的最后一个节点。

typedef struct Node {
    int data;           // 数据域
    struct Node* next;  // next指针域
} Node;

// 创建头节点
Node* head = (Node*)malloc(sizeof(Node));
head->data = 0;  // 头节点通常不存储实际数据,这里用0表示
head->next = NULL;

// 添加数据节点
Node* node1 = (Node*)malloc(sizeof(Node));
node1->data = 10;
node1->next = NULL;

Node* node2 = (Node*)malloc(sizeof(Node));
node2->data = 20;
node2->next = NULL;

// 更新链表的连接关系
head->next = node1;
node1->next = node2;

Node* tail = head; // 尾指针初始化为头节点
tail 指向head ,tail 类似于 head 的副本一样,拥有 head->next = NULL; 也就是 tail->next = NULL;

head -> [0] -> NULL
tail ----^

添加第一个数据节点

// 创建第一个数据节点
Node* node1 = (Node*)malloc(sizeof(Node));
node1->data = 10;
node1->next = NULL;

// 更新尾指针
tail->next = node1; // 将当前尾部(即头节点)的next指向新的节点
tail = node1;       // 更新尾指针,使其指向新的最后一个节点

此时 tail(也是head) 指向 node1 节点, tail 接替 node1

head -> [0] -> [10] -> NULL
                    ^
                  tail

添加第二个数据节点

// 创建第二个数据节点
Node* node2 = (Node*)malloc(sizeof(Node));
node2->data = 20;
node2->next = NULL;


tail->next = node2;  // 将当前尾部(即node1)的next指向新的节点
tail = node2;        // 更新尾指针,使其指向新的最后一个节点

此时的 tail 指针(是node1指针) 更新尾指针后 tail 就变成了 node2

head -> [0] -> [10] -> [20] -> NULL
                               ^
                             tail

为什么要更新尾指针?
如果不更新尾指针,尾指针将无法正确指向链表的最后一个节点,导致后续操作出现问题

tail->next = node1;  // 正确地将头节点的next指向node1
// 缺少 tail = node1; 这一步

在这种情况下,链表结构虽然暂时看起来正确:

head -> [0] -> [10] -> NULL
                    ^
                  tail (still points to head)

但是当尝试添加第二个节点时

tail->next = node2;  // 错误!仍然试图通过头节点的next来连接

实际会变成

head -> [0] -> [20] -> NULL
          |
         [10]

这是因为尾指针仍然指向头节点,而不是node1,所以新的节点被错误地插入到了头节点之后,而不是node1之后

参考:
https://bbs.huaweicloud.com/blogs/280936?utm_source=zhihu&utm_medium=bbs-ex&utm_campaign=other&utm_content=content