第二章数据结构线性表 – 单链表的插入和删除操作

一、知识要点

第二章数据结构线性表 - 单链表的插入和删除操作

1.1 按位序插入 – 带头结点

第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作

1.2 按位序插入 – 不带头结点

第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作

1.3 指定结点的后插操作

第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作

1.4 指定结点的前插操作

第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作

1.5 按位序删除(带头结点)

第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作

1.6 指定结点删除

第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作
第二章数据结构线性表 - 单链表的插入和删除操作

1.7 封装的好处

第二章数据结构线性表 - 单链表的插入和删除操作

二、单链表 – 插入操作

3.1 按位序插入(带头结点) – ListInsert(&L,i,e)

ListInsert(&L,i,e) : 插入操作。在表L中的第 i 个位置上插入指定元素e。

第二章数据结构线性表 - 单链表的插入和删除操作

c语言代码实现

#include <stdio.h>
#include <stdlib.h>

// 单链表节点定义(考研标准写法)
typedef struct LNode {
    int data;          // 数据域
    struct LNode *next;// 指针域
} LNode, *LinkList;     // LNode是节点类型,LinkList是链表类型(指向头节点的指针)

/**
 * 按位序插入操作:在链表L的第i个位置插入元素e
 * @param L 指向链表头指针的指针(用于可能修改头节点的情况)
 * @param i 插入位置(从1开始计数)
 * @param e 要插入的元素值
 * @return 插入成功返回1,失败返回0
 */
int ListInsert(LinkList *L, int i, int e) {
    // 1. 检查位置合法性(i不能小于1)
    if (i < 1) return 0;

    // 2. 查找第i-1个节点(p最终指向i-1号节点)
    LNode *p = *L;       // p初始指向头节点(头节点是第0个节点)
    int j = 0;           // 计数器,记录当前p指向的是第j个节点
    while (p != NULL && j < i - 1) { // 循环条件:p不为空且未到达i-1位置
        p = p->next;     // p后移
        j++;             // 计数器递增
    }

    // 3. 若i-1位置不存在(p为NULL说明i超过链表长度+1)
    if (p == NULL) return 0;

    // 4. 创建新节点(考研中通常不处理malloc失败的情况)
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;

    // 5. 插入操作(关键步骤)
    s->next = p->next;   // 新节点的next指向原i-1节点的后继
    p->next = s;         // 原i-1节点的next指向新节点

    return 1;            // 插入成功
}

// 测试函数(考研中可能需要自行编写测试用例)
int main() {
    // 初始化带头节点的空链表
    LinkList L = (LNode *)malloc(sizeof(LNode));
    L->next = NULL;

    // 测试插入操作:在第1个位置插入5
    ListInsert(&L, 1, 5);  // 链表变为:头节点 -> 5 -> NULL

    // 在第2个位置插入10
    ListInsert(&L, 2, 10); // 链表变为:头节点 -> 5 -> 10 -> NULL

    // 在第1个位置插入3(头插法效果)
    ListInsert(&L, 1, 3);  // 链表变为:头节点 -> 3 -> 5 -> 10 -> NULL

    // 遍历输出链表验证
    LNode *p = L->next;
    while (p != NULL) {
        printf("%d ", p->data);
        p = p->next;
    }
    // 输出结果应为:3 5 10
    return 0;
}

这段代码实现了带头节点的单链表按位序插入操作,并通过main函数进行了测试验证。以下是代码的详细执行过程分析:

一、基础结构:单链表节点定义

首先看节点结构体的定义:

typedef struct LNode {
    int data;          // 数据域(存储具体数值)
    struct LNode *next;// 指针域(指向下一个节点)
} LNode, *LinkList;     // LNode是节点类型,LinkList是链表类型(指向头节点的指针)
  • LNode:表示一个具体的链表节点(包含数据和指向下一个节点的指针)。
  • LinkList:本质是LNode*(指向节点的指针),通常作为链表的头指针(指向头节点)。

二、核心函数:ListInsert(按位序插入)

函数功能是在链表的第i个位置插入元素e,关键步骤如下:

1. 位置合法性检查

if (i < 1) return 0;  // 位序从1开始,i=0或负数不合法
  • 单链表的位序从 1 开始计数(第一个数据节点是位置 1),因此i < 1时直接返回失败。

2. 查找第i-1个节点(前驱节点)

LNode *p = *L;       // p初始指向头节点(头节点是第0个节点)
int j = 0;           // 计数器:p当前指向第j个节点(头节点是j=0)
while (p != NULL && j < i - 1) { 
    p = p->next;     // p后移
    j++;             // 计数器递增
}
  • 为什么找i-1节点?:插入第i个位置时,需要将新节点插入到第i-1个节点之后(例如在位置 2 插入,需要找到位置 1 的节点,将新节点接在它后面)。
  • 头节点的作用:头节点是链表的 “哨兵”(第 0 个节点),不存储数据。即使插入位置是 1(第一个数据位置),也可以通过头节点找到前驱(头节点本身就是i-1=0的节点)。
  • 循环终止条件p != NULL确保不越界(如果链表长度不足i-1p会变成NULL);j < i-1确保找到第i-1个节点。

3. 检查前驱节点是否存在

if (p == NULL) return 0;  // 若i-1位置不存在(链表长度不足),插入失败
  • 如果pNULL,说明链表长度不够(例如链表只有 2 个数据节点,但尝试在位置 4 插入),此时无法插入。

4. 创建新节点并插入

LNode *s = (LNode *)malloc(sizeof(LNode));  // 分配内存
s->data = e;                                 // 存储数据e
s->next = p->next;                           // 新节点的next指向原i-1节点的后继
p->next = s;                                 // 原i-1节点的next指向新节点
  • 指针操作顺序:必须先将新节点的next指向原i-1节点的后继(p->next),再将p->next指向新节点。如果顺序颠倒,会导致原i-1节点的后继丢失(断链)。

三、测试函数:main的执行过程

main函数初始化了一个带头节点的空链表,并进行了 3 次插入操作,最终遍历输出结果。

1. 初始化空链表

LinkList L = (LNode *)malloc(sizeof(LNode));  // 创建头节点
L->next = NULL;                               // 头节点的next为空(空链表)

此时链表结构:
头节点(data无意义) -> NULL

2. 第一次插入:ListInsert(&L, 1, 5)

  • 目标:在位置 1 插入元素 5。
  • 执行过程
    • i=1,检查i >= 1合法。
    • 查找i-1=0的节点(即头节点):p初始指向头节点,j=0,循环j < 0不成立,p仍指向头节点。
    • p不为NULL,创建新节点s(data=5)。
    • s->next = p->nextp->nextNULL,所以s->next=NULL)。
    • p->next = s(头节点的 next 指向s)。
  • 插入后链表
    头节点 -> 5(位置1) -> NULL

3. 第二次插入:ListInsert(&L, 2, 10)

  • 目标:在位置 2 插入元素 10。
  • 执行过程
    • i=2,检查i >= 1合法。
    • 查找i-1=1的节点:p初始指向头节点(j=0),循环j < 1成立,p后移指向位置 1 的节点(data=5),j=1,循环结束。
    • p不为NULL,创建新节点s(data=10)。
    • s->next = p->nextp->nextNULL,所以s->next=NULL)。
    • p->next = s(位置 1 的节点的 next 指向s)。
  • 插入后链表
    头节点 -> 5(位置1) -> 10(位置2) -> NULL

4. 第三次插入:ListInsert(&L, 1, 3)

  • 目标:在位置 1 插入元素 3(头插法效果)。
  • 执行过程
    • i=1,检查i >= 1合法。
    • 查找i-1=0的节点(头节点):p初始指向头节点,j=0,循环j < 0不成立,p仍指向头节点。
    • p不为NULL,创建新节点s(data=3)。
    • s->next = p->nextp->next是位置 1 的节点,即 data=5 的节点)。
    • p->next = s(头节点的 next 指向s)。
  • 插入后链表
    头节点 -> 3(位置1) -> 5(位置2) -> 10(位置3) -> NULL

5. 遍历输出验证

LNode *p = L->next;  // p指向第一个数据节点(位置1)
while (p != NULL) {
    printf("%d ", p->data);  // 输出3 5 10
    p = p->next;
}

最终输出结果为3 5 10,与预期一致。

总结

这段代码通过带头节点的单链表实现了按位序插入操作,核心逻辑是:

  1. 检查插入位置合法性;
  2. 找到插入位置的前驱节点(第i-1个节点);
  3. 通过修改指针将新节点插入到前驱节点之后。

测试用例验证了不同位置的插入(头插、中间插、尾插),最终输出结果正确,说明插入操作实现正确。

3.2 按位序插入(不带头结点) – ListInsert(&L,i,e)

三、单链表 – 不带头结点

第二章数据结构线性表 - 单链表的插入和删除操作

本网站原创文章版权归何大锤的狂飙日记所有。发布者:何大锤,转转请注明出处:何大锤的博客

(0)
何大锤的头像何大锤管理团队

相关推荐

  • 第二章数据结构线性表 – 单链表动画演示

    一、带头结点单链表 1、头插法创建-动画演示 2、尾插法创建-动画演示 3、插入操作 插入表头 插入中间 插入表尾

    2025年6月16日
    800
  • 第二章数据结构线性表 – 单链表的定义与初始化

    一、单链表的定义和表示 – 知识要点 1.1 定义单链表 1.2 带头结点单链表的初始化 二、单链表 – 定义与初始化 1.1 带头结点代码实现 1.1.1 C语言实现 实现算法实现 测试主函数 运行结果 运行过程 带头结点单链表的初始化的注意点 1.1.2 C++单链表定义及初始化 运行结果 1.2 不带头结点代码实现 1.2.1 …

    2025年6月14日
    700
  • 第二章数据结构线性表 – 单链表概念

    一、引言 1.1 知识框架 1.2 有了数组为什么还要链表? 在前面我们介绍过数组,数组中元素是存储在连续的内存位置在声明数组时,我们可以指定数组的大小,但这将限制数组可以存储的元素数量 例如我们声明的是 int arr[10],那么arr数组最多可以存储10个数据元素 但是我们事先不知道元素的大小呢? 我们该如何去做? 当然首先想到的是申请一个足够大的数组…

    2025年6月14日
    300
  • 08 – 数据结构基础知识补充

    内存分类 一、静态 / 全局内存(程序运行期间一直存在) 特点:全局变量和静态变量(用static声明)的内存,在程序启动时分配,程序结束才释放。通俗理解:像「公共仓库」,一旦创建就一直存在,所有需要的地方都能访问(全局变量),或在特定范围内持续保留(静态变量)。 解释: 二、自动内存(栈内存,函数调用时临时创建) 特点:函数内的普通局部变量(不用stati…

    2025年6月6日
    1100
  • 07 – 第二章数据结构线性表 – 顺序表总结

    顺序表代码实现 一、动态分配方式实现的所有功能 dynamic_list.c dynamic_list.h relloc函数 realloc 是 C 语言标准库中用于重新调整已分配内存块大小的函数,主要用于动态内存管理(头文件 <stdlib.h>)。 ptr:指向待调整大小的原内存块的指针(若为 NULL,效果等价于 malloc…

    2025年5月29日
    700

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

2211932694

在线咨询: QQ交谈

邮件:hdcblog1999@163.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信
网站建设中ing......