1.链式存储结构的定义

与顺序表的“数组顺序存储”不同,链式存储结构用 一组任意的存储单元(可以分散在内存中) 来存放线性表中的元素。

每个存储单元既要存放数据,还要存放指针,用来指向逻辑上相邻的元素。

所以链式存储的基本单元是 结点(Node):

 ┌─────────────────┬─────────────────┐
 │ 数据域(data)     │ 指针域(next)    │
 └─────────────────┴─────────────────┘

2.链式存储和顺序存储对比

特点顺序表(数组)链表
存储位置连续内存分散内存
插入/删除O(n)(需移动元素)O(1)(只改指针,若已找到位置)
随机访问O(1)(支持下标)O(n)(只能顺序找)
内存利用必须预分配或扩容按需分配,不浪费空间
缓存局部性好(访问快)差(访问慢)

3.链式存储的优缺点

优点:

不需要连续空间,内存利用率高;

插入、删除效率高(已知位置时 O(1));

表长不受固定容量限制。

缺点:

节点要多存一个指针,开销大;

随机访问困难(只能顺序遍历);

缓存局部性差,访问速度比顺序表慢。

链表初识

#include<stdio.h>    // 引入标准输入输出库,提供 printf 等函数
#include<assert.h>   // 引入断言库,提供 assert 宏,用来检查指针是否为空
#include<malloc.h>   // 引入内存分配函数 malloc(有些平台推荐用 stdlib.h)

#define ElemType int  // 定义链表中存储的数据类型为 int,以后想换类型只改这里

// 定义链表节点结构体
typedef struct ListNode
{
	ElemType data;           // 节点存储的数据
	struct ListNode *next;   // 指向下一个节点的指针
}ListNode;

// 定义链表类型为指向节点的指针(即链表头指针)
typedef ListNode* List;

// 初始化链表,生成头结点
void InitList(List *head)
{
	*head = (ListNode*)malloc(sizeof(ListNode)); // 分配一个头结点内存
	assert(*head != NULL);                       // 判断是否分配成功
	(*head)->next = NULL;                        // 初始化时链表为空,头结点 next 置为 NULL
}

// 创建链表,生成 10 个节点并存放数据 1~10
void CreateList(List *head)
{
	ListNode *p = *head;  // 临时指针 p 指向头结点
	for(int i=1; i<=10; ++i)   // 循环 10 次,依次生成 1~10 的节点
	{
		// 分配新节点,同时让当前 p 的 next 指向新节点,再让 p 指向新节点(尾插法)
		p = p->next = (ListNode*)malloc(sizeof(ListNode));  
		assert(p != NULL);   // 检查内存分配是否成功
		p->data = i;         // 给新节点赋值(1~10)
		p->next = NULL;      // 新节点的 next 先置为空,表示链表结束
	}
}

// 遍历链表并打印数据
void ShowList(List head)
{
	ListNode *p = head->next;   // 从第一个有效节点开始(跳过头结点)
	while(p != NULL)            // 遍历到链表末尾(NULL)为止
	{
		printf("%d-->", p->data); // 打印当前节点数据
		p = p->next;              // 移动到下一个节点
	}
	printf("Nul.\n");           // 打印链表结束标记
}

// 主函数
void main()
{
	List mylist;           // 定义一个链表头指针
	InitList(&mylist);     // 初始化链表,分配头结点
	CreateList(&mylist);   // 创建 10 个节点并存放 1~10
	ShowList(mylist);      // 遍历并打印链表内容
}

4.单链表详细说明(概念与基本结构)

节点(Node):链表的基本单元,通常包含数据域和指针域(指向下一个节点)。

头指针(head):指向链表第一个节点或哨兵(dummy)节点。

尾指针(tail/last)(可选):指向最后一个节点,便于 O(1) 尾插。

大小(size)(可选):记录当前节点数量,便于边界判断与复杂度分析。

哨兵头(dummy head / sentinel):一个不存实际数据的头结点,使插入/删除边界处理更统一(很多实现推荐使用)

head(dummy) -> node1 -> node2 -> node3 -> NULL
               ↑
             head->next 指向第一个真实节点

SList.h

#ifndef __SLIST_H__          // 防止头文件重复包含的宏定义(如果没定义则定义,已经定义过就跳过)
#define __SLIST_H__

#include<stdio.h>            // 标准输入输出头文件,用于 printf / scanf
#include<malloc.h>           // 内存分配函数 malloc/free
#include<assert.h>           // assert 断言函数,运行时检查指针是否为空等

#define ElemType int          // 定义链表中存放的数据类型为 int,可以通过改这个宏实现泛型效果

/*----------------------------- 结点结构体 -----------------------------*/
// 单链表结点结构
typedef struct Node
{
    ElemType data;            // 数据域,用来存放元素
    struct Node* next;        // 指针域,指向下一个结点
}Node, * PNode;               // Node 是结构体名,PNode 是 Node* 类型别名

/*----------------------------- 链表结构体 -----------------------------*/
// 链表的管理结构(带有头指针、尾指针、元素个数)
// 使用这个结构可以更方便管理整个链表
typedef struct List
{
    PNode  first;             // 指向链表的头结点(通常是一个哨兵结点,不存放实际数据)
    PNode  last;              // 指向链表的尾结点(最后一个有效结点,方便尾插 O(1))
    size_t size;              // 链表中存放的元素个数
}List;

/*----------------------------- 链表操作函数声明 -----------------------------*/

// 初始化链表
void InitList(List* list);

// 尾插:在链表尾部插入一个元素
void push_back(List* list, ElemType x);

// 头插:在链表头部插入一个元素(头结点之后)
void push_front(List* list, ElemType x);

// 打印链表中的所有元素
void show_list(List* list);

// 删除尾结点
void pop_back(List* list);

// 删除头结点之后的第一个结点
void pop_front(List* list);

// 插入一个值(一般是有序插入:比如找到合适位置后插入)
void insert_val(List* list, ElemType x);

// 查找某个值,返回结点指针(没找到返回 NULL)
Node* find(List* list, ElemType key);

// 返回链表长度
int  length(List* list);

// 删除某个值(删除第一个等于 key 的结点)
void delete_val(List* list, ElemType key);

// 排序链表(通常是插入排序或者冒泡排序实现)
void sort(List* list);

// 逆置链表(把链表反转)
void resver(List* list);

// 清空链表(删除所有结点,但保留头结点)
void clear(List* list);

// 销毁链表(释放所有结点,包括头结点)
void destroy(List* list);

/*----------------------------- 迭代器部分(模仿 C++ STL) -----------------------------*/
///////////////////////////////////////////////////////

// 定义迭代器类型(本质就是 Node*,结点指针)
typedef Node* It;

// 申请一个新结点,存放数据 x,返回该结点指针
Node* _buynode(ElemType x);

// 返回指向链表第一个有效元素的迭代器(list->first->next)
It begin(List* list);

// 返回链表结束位置的迭代器(即 NULL,表示终点)
It end(List* list);

// 在 pos 位置前插入一个元素 x
void insert(List* list, It pos, ElemType x);

#endif //__SLIST_H__

main.cpp

#include "SList.h"   // 包含单链表的头文件,里面声明了List结构体、Node结构体,以及各种链表操作函数

int main()
{
    List mylist;              // 定义一个单链表对象 mylist(一般会包含头结点指针和链表长度等信息)
    InitList(&mylist);        // 初始化单链表,使其成为一个空链表(通常会分配头结点,next置NULL)

    ElemType Item;            // 定义一个元素变量,用来存放用户输入的数据
    Node* p = NULL;           // 定义一个结点指针,用于存放查找结果
    int select = 1;           // 控制菜单选择的变量,初始值为1,保证可以进入循环

    // 主循环 —— 菜单交互界面
    while (select)
    {
        // 打印功能菜单
        printf("**************************************\n");
        printf("* [1] push_back      [2] push_front  *\n");
        printf("* [3] show_list      [4] pop_back    *\n");
        printf("* [5] pop_front      [6] insert_val  *\n");
        printf("* [7] find           [8] length      *\n");
        printf("* [9] delete_val     [10] sort       *\n");
        printf("* [11] resver        [12] clear      *\n");
        printf("* [13] destroy       [0] quit_system *\n");
        printf("**************************************\n");
        printf("请选择:>");

        // 输入用户选择
        scanf_s("%d", &select);
        if (select == 0)      // 输入0时退出系统
            break;

        // 根据用户输入执行对应功能
        switch (select)
        {
        case 1: // push_back —— 尾插法
            printf("请输入要插入的数据(-1结束):>");
            while (scanf_s("%d", &Item), Item != -1)   // 输入 -1 结束输入
            {
                push_back(&mylist, Item);  // 将数据插入到链表尾部
            }
            break;

        case 2: // push_front —— 头插法
            printf("请输入要插入的数据(-1结束):>");
            while (scanf_s("%d", &Item), Item != -1)   // 输入 -1 结束输入
            {
                push_front(&mylist, Item); // 将数据插入到链表头部(头结点之后)
            }
            break;

        case 3: // show_list —— 遍历打印链表
            show_list(&mylist);
            break;

        case 4: // pop_back —— 删除尾结点
            pop_back(&mylist);
            break;

        case 5: // pop_front —— 删除头结点之后的第一个结点
            pop_front(&mylist);
            break;

        case 6: // insert_val —— 插入指定值(通常是有序插入)
            printf("请输入要插入的数据:>");
            scanf_s("%d", &Item);
            insert_val(&mylist, Item);
            break;

        case 7: // find —— 查找指定值
            printf("请输入要查找的数据:>");
            scanf_s("%d", &Item);
            p = find(&mylist, Item);       // 返回值是结点指针
            if (p == NULL)
            {
                printf("查找的值在链表中不存在.\n");
            }
            else
            {
                printf("查找的值在链表中存在.\n");
            }
            break;

        case 8: // length —— 返回链表长度
            printf("单链表的长度为:> %d \n", length(&mylist));
            break;

        case 9: // delete_val —— 删除指定值
            printf("请输入要删除的值:>");
            scanf_s("%d", &Item);
            delete_val(&mylist, Item);
            break;

        case 10: // sort —— 排序链表
            sort(&mylist);
            break;

        case 11: // resver —— 逆置链表
            resver(&mylist);
            break;

        case 12: // clear —— 清空链表(保留头结点)
            clear(&mylist);
            break;

        // 如果想彻底销毁链表(包括头结点),可以用 destroy
        // case 13:
        //     destroy(&mylist);
        //     break;

        default: // 输入无效数字时
            printf("输入的命令错误,请重新输入.\n");
            break;
        }
    }

    destroy(&mylist);   // 退出循环后,销毁链表(释放所有结点内存,包括头结点)
}

SList.cpp

初始化
#include "SList.h"

// 初始化单链表
void InitList(List* list)
{
    // 1. 创建一个头结点(哨兵结点)
    // 这里 first 和 last 同时指向这个头结点
    // malloc 分配一个 Node 结点空间
    list->first = list->last = (Node*)malloc(sizeof(Node));

    // 2. 检查内存分配是否成功
    // 如果 malloc 返回 NULL,表示内存分配失败,assert 会终止程序
    assert(list->first != NULL);

    // 3. 设置头结点的 next 指针为空
    // 表示当前链表为空,没有任何实际数据结点
    list->first->next = NULL;

    // 4. 初始化链表长度为 0
    list->size = 0;
}

解释:

头结点的作用

头结点本身不存放实际数据,只作为链表的固定起点。

好处:统一了操作(比如插入、删除时不用单独考虑头结点特殊情况)。

first 和 last

first 指向头结点。

last 也指向头结点(因为一开始链表是空的,尾结点就是头结点)。

size

size = 0,表示链表里没有有效数据结点。

插入时 size++,删除时 size--。

尾插
void push_back(List* list, ElemType x)
{
	insert(list, end(list), x);
}

/*
void push_back(List *list, ElemType x)
{
	//Node *s = _buynode(x);

	Node *s = (Node *)malloc(sizeof(Node));
	assert(s != NULL);
	s->data = x;
	s->next = NULL;


	list->last->next = s;
	list->last = s;
	list->size++;
}
*/

原函数解释:

Node s = (Node )malloc(sizeof(Node));
为新元素申请一个节点的动态内存空间。

assert(s != NULL);
检查内存是否分配成功,如果失败程序会中止。

s->data = x;
将新节点的数据域设置为传入的元素 x。

s->next = NULL;
新节点作为链表的尾结点,它的 next 指针置为空。

list->last->next = s;
把当前尾结点的 next 指针指向新节点。

list->last = s;
更新链表的尾指针,使其指向新加入的节点。

list->size++;
链表元素个数加 1。

头插
void push_front(List* list, ElemType x)
{
	insert(list, begin(list), x);
}

/*
void push_front(List *list, ElemType x)
{
	//Node *s = _buynode(x);

	Node *s = (Node *)malloc(sizeof(Node));
	assert(s != NULL);
	s->data = x;

	s->next = list->first->next;
	list->first->next = s;
	if(list->size == 0)
	{
		list->last = s;
	}
	list->size++;
}
*/

原函数解释:

Node s = (Node )malloc(sizeof(Node));
为新节点申请内存空间。

assert(s != NULL);
检查内存是否分配成功,如果失败程序会中止。

s->data = x;
将新节点的数据域赋值为传入的元素 x。

s->next = list->first->next;
让新节点的 next 指针指向原来头结点后的第一个节点。

list->first->next = s;
把头结点的 next 指针改为指向新节点,新节点就插入到了链表最前面。

if(list->size == 0) { list->last = s; }
如果原来链表为空(没有其他节点),那么插入的这个节点既是第一个,也是最后一个,需要更新尾指针。

list->size++;
链表元素个数加 1。

显示
void show_list(List* list)
{
	Node* p = list->first->next;
	while (p != NULL)
	{
		printf("%d-->", p->data);
		p = p->next;
	}
	printf("Nul.\n");
}

解释:

Node p = list->first->next;
定义一个指针 p,从头结点之后的第一个有效节点开始遍历链表。

while (p != NULL)
当 p 不为空时,说明链表还没有遍历到尾部,继续循环。

printf("%d-->", p->data);
打印当前节点的数据,并用箭头 --> 表示链表连接关系。

p = p->next;
将指针 p 移动到下一个节点,继续遍历。

printf("Nul.\n");
遍历结束后输出 "Nul.",表示链表的末尾(空指针)。

尾删
void pop_back(List* list)
{
	if (list->size == 0)
		return;

	Node* p = list->first;
	while (p->next != list->last)
		p = p->next;

	free(list->last);
	list->last = p;
	list->last->next = NULL;
	list->size--;
}

解释:

if (list->size == 0) return;
如果链表为空,没有元素可删除,直接返回。

Node p = list->first;
定义指针 p,从头结点开始,用来寻找最后一个节点的前驱节点。

while (p->next != list->last) p = p->next;
循环遍历链表,直到找到最后一个节点 last 的前一个节点 p。

free(list->last);
释放尾节点的内存空间。

list->last = p;
更新链表的尾指针,让它指向删除后新的尾节点。

list->last->next = NULL;
将新尾节点的 next 置空,表示链表结束。

list->size--;
链表元素个数减 1。

头删
void pop_front(List* list)
{
	if (list->size == 0)
		return;
	Node* p = list->first->next;
	list->first->next = p->next;
	free(p);
	if (list->size == 1)
	{
		list->last = list->first;
	}
	list->size--;
}

解释:

if (list->size == 0) return;
如果链表为空,没有元素可删除,直接返回。

Node p = list->first->next;*
定义指针 p,指向链表的第一个有效节点(头结点后的第一个节点)。

list->first->next = p->next;
让头结点的 next 指针绕过 p,直接指向第二个节点,把第一个节点从链表中断开。

free(p);
释放第一个节点的内存空间。

if (list->size == 1) { list->last = list->first; }
如果删除前链表只有一个元素,那么删除后链表就为空,此时需要把尾指针 last 重新指向头结点。

list->size--;
链表元素个数减 1。

插入数值
void insert_val(List* list, ElemType x)
{
	Node* s = _buynode(x);
	/*
	Node *s = (Node *)malloc(sizeof(Node));
	assert(s != NULL);
	s->data = x;
	s->next = NULL;
	*/
	Node* p = list->first;
	while (p->next != NULL && p->next->data < x)
		p = p->next;

	if (p->next == NULL)
	{
		list->last = s;
	}

	s->next = p->next;
	p->next = s;
	list->size++;
}

解释:

Node s = _buynode(x);*
调用 _buynode 创建一个新节点,并存放元素 x。
(注:代码中保留了另一种写法,即用 malloc + 初始化来生成新节点)。

Node p = list->first;*
定义指针 p,从头结点开始,用于查找插入位置。

while (p->next != NULL && p->next->data < x) p = p->next;
遍历链表,找到第一个 大于或等于 x 的节点位置,循环结束时 p 指向插入位置的前驱节点。

if (p->next == NULL) { list->last = s; }
如果循环走到链表尾部,说明 x 是最大值,新节点会插到最后一个位置,所以要更新尾指针。

s->next = p->next;
让新节点的 next 指针指向插入位置的后继节点。

p->next = s;
让前驱节点的 next 指针指向新节点,完成插入操作。

list->size++;
链表长度加 1。

查找
Node* find(List* list, ElemType key)
{
	Node* p = list->first->next;
	while (p != NULL && p->data != key) //
		p = p->next;
	return p;
}

解释:

Node p = list->first->next;*
定义指针 p,从链表的第一个有效节点开始查找。

while (p != NULL && p->data != key) p = p->next;
遍历链表:

如果 p 不为空且当前节点的数据 data 不等于查找值 key,则继续向后移动。

循环结束时有两种情况:

找到值等于 key 的节点,p 指向该节点。

遍历到链表尾部仍未找到,p 变为 NULL。

return p;
返回查找到的节点指针:

如果找到,返回对应节点的地址。

如果没找到,返回 NULL。

注意:while (p != NULL && p->data != key) 和 while (p->data != key && p != NULL) 在语义上是不同的。

while (p != NULL && p->data != key)

先判断 p != NULL,如果 p 已经是空指针,则 不会再去访问 p->data。

这样就避免了对空指针解引用导致的 段错误 (Segmentation Fault)。

while (p->data != key && p != NULL)

先判断 p->data != key。

但是如果 p == NULL,这一步就会尝试访问 NULL->data,直接导致 程序崩溃。

即使后面有 && p != NULL,也来不及执行,因为程序已经访问了非法内存。

int  length(List* list)
{
	return list->size;
}

直接返回链表结构体中维护的成员变量 size,即当前链表中节点的个数。

删除值
void delete_val(List* list, ElemType key)
{
	if (list->size == 0)
		return;
	Node* p = find(list, key);
	if (p == NULL)
	{
		printf("要删除的数据不存在.\n");
		return;
	}

	if (p == list->last)
	{
		pop_back(list);
	}
	else
	{
		Node* q = p->next;
		p->data = q->data;
		p->next = q->next;
		free(q);
		list->size--;
	}
}

解释:

if (list->size == 0) return;
如果链表为空,直接返回,无需删除。

Node p = find(list, key);*
调用 find 函数查找值等于 key 的节点,返回指针 p。

if (p == NULL) { printf("要删除的数据不存在.\n"); return; }
如果没找到对应的节点,提示信息并返回。

if (p == list->last) { pop_back(list); }
如果要删除的节点是尾结点,直接调用尾删函数 pop_back。

else { … }
否则,采用“复制覆盖”的方式删除节点:

Node q = p->next;*
定义指针 q,指向 p 的下一个节点。

p->data = q->data;
用后继节点的数据覆盖当前节点的数据。

p->next = q->next;
让 p 跳过 q,直接指向 q 的后继节点。

free(q);
释放后继节点 q 的内存空间。

list->size--;
链表元素个数减 1。

排序
void sort(List* list)
{
	if (list->size == 0 || list->size == 1)
		return;

	Node* s = list->first->next;
	Node* q = s->next;

	list->last = s;
	list->last->next = NULL;

	while (q != NULL)
	{
		s = q;
		q = q->next;

		Node* p = list->first;
		while (p->next != NULL && p->next->data < s->data)
			p = p->next;

		if (p->next == NULL)
		{
			list->last = s;
		}

		s->next = p->next;
		p->next = s;
	}
}

解释:

if (list->size == 0 || list->size == 1) return;
如果链表为空或只有一个节点,不需要排序,直接返回。

Node s = list->first->next;*
s 指向第一个有效节点,表示当前处理的节点。

Node q = s->next;*
q 指向 s 的下一个节点,用于遍历未排序的部分。

list->last = s; list->last->next = NULL;
将链表断开:此时把第一个节点当作已排序部分,并把它设置为新的尾节点,尾指针置空。

进入循环(插入排序的核心):

while (q != NULL)
遍历剩余未排序的节点。

s = q; q = q->next;
从未排序部分取出一个节点 s,并移动 q 到下一个节点。

Node p = list->first;*
p 从头结点开始,准备在已排序部分中找到 s 的插入位置。

while (p->next != NULL && p->next->data < s->data) p = p->next;
遍历已排序部分,找到第一个比 s->data 大的位置,p 就是插入点的前驱节点。

if (p->next == NULL) { list->last = s; }
如果插入位置在尾部,说明 s 最大,更新链表尾指针。

s->next = p->next; p->next = s;
将 s 插入到正确位置:

s->next 指向 p 的后继。

p->next 指向 s。

逆序
void resver(List* list)
{
	if (list->size == 0 || list->size == 1)
		return;

	Node* p = list->first->next;
	Node* q = p->next;

	list->last = p;
	list->last->next = NULL;

	while (q != NULL)
	{
		p = q;
		q = p->next;

		p->next = list->first->next;
		list->first->next = p;
	}
}

假设链表内容是:

head -> 1 -> 2 -> 3 -> 4 -> NULL

初始状态:

p = 1,q = 2

last = 1,并且 1->next = NULL
链表断开为:

head -> 1 -> NULL      (保存了尾巴)
q = 2 -> 3 -> 4 -> NULL

第一次循环:

p = 2,q = 3

插入到头结点后:

head -> 2 -> 1 -> NULL
q = 3 -> 4 -> NULL

第二次循环:

p = 3,q = 4

插入到头结点后:

head -> 3 -> 2 -> 1 -> NULL
q = 4 -> NULL

第三次循环:

p = 4,q = NULL

插入到头结点后:

head -> 4 -> 3 -> 2 -> 1 -> NULL
清除
void clear(List* list)
{
	if (list->size == 0)
		return;

	Node* p = list->first->next;
	while (p != NULL)
	{
		list->first->next = p->next;
		free(p);
		p = list->first->next;
	}
	list->last = list->first;
	list->size = 0;
}

关键点解释:

while 循环释放节点

list->first->next = p->next;
free(p);
p = list->first->next;

这段逻辑很巧妙,它避免了提前保存 p->next。

因为一旦释放了 p,就不能再访问它的 next,所以提前用 list->first->next 保存下一个节点。

最后修复尾指针

当链表清空后,list->last 必须回到头结点,否则链表处于“无效”状态。

size 清零

保证 length(list) 会返回 0,和链表状态一致。

举例

链表初始状态:

head -> 1 -> 2 -> 3 -> NULL
last = 3
size = 3

执行过程:

第一次循环:释放 1

head -> 2 -> 3 -> NULL
p = 2

第二次循环:释放 2

head -> 3 -> NULL
p = 3

第三次循环:释放 3

head -> NULL
p = NULL

结束:

head -> NULL
last = head
size = 0
void destroy(List* list)
{
	clear(list);
	free(list->first);
	list->first = list->last = NULL;
}

解释:

clear(list);

调用 clear 函数释放链表中所有有效节点(头结点之后的节点),并把尾指针指向头结点,大小置 0。

free(list->first);

释放头结点的内存空间。

头结点通常在 InitList 或 malloc 时动态分配,需要单独释放。

list->first = list->last = NULL;

将链表结构的头指针和尾指针置空,避免悬空指针(Dangling Pointer)。

这表示链表已经被完全销毁,不能再访问。

申请空间
Node* _buynode(ElemType x)
{
	Node* s = (Node*)malloc(sizeof(Node));
	assert(s != NULL);
	s->data = x;
	s->next = NULL;
	return s;
}

Node s = (Node)malloc(sizeof(Node));**

为一个新节点动态申请内存空间。

返回的 s 是指向新节点的指针。

assert(s != NULL);

检查内存分配是否成功,如果失败,程序会中止。

s->data = x;

将节点的数据域设置为传入的元素 x。

s->next = NULL;

新节点的 next 指针置空,表示当前节点暂时没有后继。

return s;

返回新创建的节点指针,供链表插入等操作使用。

返回第一或最后一个节点
It begin(List* list)
{
	return list->first->next;
}
It end(List* list)
{
	return list->last->next;
}

返回链表第一个有效节点的指针(头结点后的第一个节点)。

返回尾节点的下一个节点的指针。

插入节点
void insert(List* list, It pos, ElemType x)
{
	Node* p = list->first;
	while (p->next != pos)
	{
		p = p->next;
	}

	Node* s = _buynode(x);
	s->next = p->next;
	p->next = s;
	if (pos == NULL)
		list->last = s;
	list->size++;
}

Node p = list->first;*

定义指针 p 从头结点开始,寻找插入位置的前驱节点。

while (p->next != pos) p = p->next;

遍历链表,直到找到 pos 对应节点的前一个节点 p。

因为单链表无法直接访问前驱节点,所以必须从头结点开始遍历。

Node s = _buynode(x);*

调用 _buynode 创建新节点 s,数据域存放 x,next 初始化为 NULL。

s->next = p->next;

新节点 s 的 next 指向原本前驱节点 p 的后继节点(即 pos)。

p->next = s;

前驱节点 p 的 next 指向新节点 s,完成插入操作。

if (pos == NULL) list->last = s;

如果 pos == NULL,表示在链表末尾插入,需要更新尾指针 last。

list->size++;

链表长度加 1。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐