【数据结构】线性表-单链表

线性表-单链表

顺序表

上一篇文章


链表介绍:

  • 线性表链式存储结构的特点是:用一组任意的存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。

  • 为了表示每个数据元素 Ai 与其直接后继数据元素 Ai+1之间的逻辑关系,对数据元素Ai来说,除了其本身的信息之外,还需要存储一个指示其直接后继的信息(直接后继的存储位置)。这两部分信息组成数据元素Ai的存储映像,称为节点(node)。

  • 结点包括两个域:其中存储数据元素信息的称为数据域;存储直接后继存储位置有域称为指针域。指针域中存储的信息称作指针或链。

  • n个结点 [ A(1 ≤ i ≤ n)的存储映像 ] 链接成一个链表,即为线性表(A1,A2,...,An)。


链表存储结构

c 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType; // 定义元素类型

typedef struct node // 定义节点类型
{
    ElemType data;
    struct node *next;
} Node;

单链表

初始化

注意:
头节点:最开始的头节点
第一个节点:头节点指向的节点叫做第一个节点

对照这个图,完成单链表的初始化

c 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType; // 定义元素类型

typedef struct node // 定义节点类型
{
    ElemType data;
    struct node *next;
} Node;

/* 初始化一个单链表-造一个头节点 */
Node *InitList()
{
    Node *head = (Node *)malloc(sizeof(Node)); // 为头节点分配内存
    head->data = 0;                            // 头节点的数据域为0
    head->next = NULL;                         // 头节点的指针域为空
    return head;                               // 返回头节点
}

int main()
{
    Node *list = InitList(); // 初始化一个单链表
    printf("Hello World!\n");
    return 0;
}

插入数据

链表的插入数据有两种方式:头插法和尾插法


头插法

每次都在头节点后面插入数据

第一次插入数据

第二次插入数据

可以看到:每次都在头节点后面插入数据

讲解视频,演示动画:【《数据结构(C 语言描述)》也许是全站最良心最通俗易懂最好看的数据结构课(最迟每周五更新~~)】 【精准空降到 1:31:50】 https://www.bilibili.com/video/BV1tNpbekEht/?p=3\&share_source=copy_web\&vd_source=8af85e60c2df9af1f0fd23935753a933\&t=5510

头插法的步骤:

  1. 创建一个新的节点
  2. 在新节点的数据域存入数据e
  3. 新节点的指针域指向头节点的下一个节点(把L的NULL复制给新节点)
  4. 头节点的指针域指向新节点
c 复制代码
/*单链表 - 头插法*/
int InsertHead(Node *L, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    p->next = L->next;                      // 新节点的指针域指向头节点的下一个节点(把L的NULL复制给新节点)
    L->next = p;                            // 头节点的指针域指向新节点
    return 1;                               // 返回1表示成功
}

如果后面已经有了数据,那么头插法的演示效果如下,先让新节点指向第一个节点,再让头节点指向新节点(这样就能防止后面的数据丢失)

  • 对比:顺序表的插入是在数组里插入,每个元素都往后移动,链表的插入不是这样的,只改变节点之间的指向关系即可实现!!!

单链表:单个方向的链表

双向链表:知道下一个,也知道上一个

循环链表:构成一个环的链表


尾插法

找到尾NULL,尾节点地址,根据这个地址再插入数据。

c 复制代码
/* 单链表 - 尾插法 */
// 获取尾节点地址
Node *GetTail(Node *List)
{
    Node *p = List;         // 从头节点开始遍历
    while (p->next != NULL) // 遍历到链表末尾
    {
        p = p->next; // 移动到下一个节点
    }
    return p; // 返回尾节点
}
// 尾插法插入数据
Node *InsertTail(Node *tail, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    tail->next = p;                         // 尾节点的指针域指向新节点
    p->next = NULL;                         // 新节点的指针域为空
    return p;                               // 返回新的尾节点
}

完整的尾插法代码

c 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType; // 定义元素类型

typedef struct node // 定义节点类型
{
    ElemType data;
    struct node *next;
} Node;

/* 初始化一个单链表-造一个头节点 */
Node *InitList()
{
    Node *head = (Node *)malloc(sizeof(Node)); // 为头节点分配内存
    head->data = 0;                            // 头节点的数据域为0
    head->next = NULL;                         // 头节点的指针域为空
    return head;                               // 返回头节点
}
/*单链表 - 头插法*/
int InsertHead(Node *L, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    p->next = L->next;                      // 新节点的指针域指向头节点的下一个节点(把L的NULL复制给新节点)
    L->next = p;                            // 头节点的指针域指向新节点
    return 1;                               // 返回1表示成功
}
/* 单链表 - 遍历 */
void TraverseList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历
    while (p != NULL)  // 遍历到链表末尾
    {
        printf("%d ", p->data); // 输出节点的数据域
        p = p->next;            // 移动到下一个节点
    }
    printf("\n"); // 换行
}

/* 单链表 - 尾插法 */
// 获取尾节点地址
Node *GetTail(Node *List)
{
    Node *p = List;         // 从头节点开始遍历
    while (p->next != NULL) // 遍历到链表末尾
    {
        p = p->next; // 移动到下一个节点
    }
    return p; // 返回尾节点
}
// 尾插法插入数据
Node *InsertTail(Node *tail, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    tail->next = p;                         // 尾节点的指针域指向新节点
    p->next = NULL;                         // 新节点的指针域为空
    return p;                               // 返回新的尾节点
}

int main()
{
    Node *list = InitList(); // 初始化一个单链表
    printf("头插法插入数据1 2 3\n");
    InsertHead(list, 1); // 头插法插入数据1
    InsertHead(list, 2); // 头插法插入数据2
    InsertHead(list, 3); // 头插法插入数据3

    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表
    printf("\n");       // 换行

    printf("\n尾插法插入数据4 5 6\n");
    Node *tail = GetTail(list); // 获取尾节点地址
    tail = InsertTail(tail, 4); // 尾插法插入数据4
    tail = InsertTail(tail, 5); // 尾插法插入数据5
    tail = InsertTail(tail, 6); // 尾插法插入数据6
    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表

    return 0;
}

输出结果如下:

c 复制代码
头插法插入数据1 2 3
遍历-链表中的元素为:3 2 1


尾插法插入数据4 5 6
遍历-链表中的元素为:3 2 1 4 5 6

请按任意键继续. . .

可以看到尾插法的数据是正确的!

在指定位置插入数据

在70和80中间插入一个new

一定要注意这个顺序,不能颠倒

c 复制代码
**
 * @Description:单链表 - 在指定位置插入数据
 * @param {Node} *L     单链表的头节点
 * @param {int} pos     位置
 * @param {ElemType} e  插入的数据
 * @return {*}
 */
int InsertPosNode(Node *L, int pos, ElemType e)
{
    // 用来保存插入位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到插入位置的前驱节点
    while (i < pos - 1) // 遍历到插入位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("插入位置不合法\n");
            return 0;
        }
    }

    Node *newnode = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    newnode->data = e;                            // 在新节点的数据域存入数据e
    newnode->next = p->next;                      // 新节点的指针域指向插入位置的前驱节点的下一个节点
    p->next = newnode;                            // 插入位置的前驱节点的指针域指向新节点
    return 1;
}

完整的演示代码:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType; // 定义元素类型

typedef struct node // 定义节点类型
{
    ElemType data;
    struct node *next;
} Node;

/* 初始化一个单链表-造一个头节点 */
Node *InitList()
{
    Node *head = (Node *)malloc(sizeof(Node)); // 为头节点分配内存
    head->data = 0;                            // 头节点的数据域为0
    head->next = NULL;                         // 头节点的指针域为空
    return head;                               // 返回头节点
}
/*单链表 - 头插法*/
int InsertHead(Node *L, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    p->next = L->next;                      // 新节点的指针域指向头节点的下一个节点(把L的NULL复制给新节点)
    L->next = p;                            // 头节点的指针域指向新节点
    return 1;                               // 返回1表示成功
}
/* 单链表 - 遍历 */
void TraverseList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历
    while (p != NULL)  // 遍历到链表末尾
    {
        printf("%d ", p->data); // 输出节点的数据域
        p = p->next;            // 移动到下一个节点
    }
    printf("\n"); // 换行
}

/* 单链表 - 尾插法 */
// 获取尾节点地址
Node *GetTail(Node *List)
{
    Node *p = List;         // 从头节点开始遍历
    while (p->next != NULL) // 遍历到链表末尾
    {
        p = p->next; // 移动到下一个节点
    }
    return p; // 返回尾节点
}

/**
 * @Description:单链表 - 尾插法插入数据
 * @param {Node} *tail   尾节点
 * @param {ElemType} e   插入的数据
 * @return {*}           返回新的尾节点
 */
Node *InsertTail(Node *tail, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    tail->next = p;                         // 尾节点的指针域指向新节点
    p->next = NULL;                         // 新节点的指针域为空
    return p;                               // 返回新的尾节点
}

/**
 * @Description:单链表 - 在指定位置插入数据
 * @param {Node} *L     单链表的头节点
 * @param {int} pos     位置
 * @param {ElemType} e  插入的数据
 * @return {*}
 */
int InsertPosNode(Node *L, int pos, ElemType e)
{
    // 用来保存插入位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到插入位置的前驱节点
    while (i < pos - 1) // 遍历到插入位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("插入位置不合法\n");
            return 0;
        }
    }

    Node *newnode = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    newnode->data = e;                            // 在新节点的数据域存入数据e
    newnode->next = p->next;                      // 新节点的指针域指向插入位置的前驱节点的下一个节点
    p->next = newnode;                            // 插入位置的前驱节点的指针域指向新节点
    return 1;
}

int main()
{
    Node *list = InitList(); // 初始化一个单链表
    printf("头插法插入数据1 2 3\n");
    InsertHead(list, 1); // 头插法插入数据1
    InsertHead(list, 2); // 头插法插入数据2
    InsertHead(list, 3); // 头插法插入数据3

    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表
    printf("\n");       // 换行

    printf("\n尾插法插入数据4 5 6\n");
    Node *tail = GetTail(list); // 获取尾节点地址
    tail = InsertTail(tail, 4); // 尾插法插入数据4
    tail = InsertTail(tail, 5); // 尾插法插入数据5
    tail = InsertTail(tail, 6); // 尾插法插入数据6
    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表

    printf("\n在指定位置3插入数据7\n");
    InsertPosNode(list, 3, 7); // 在指定位置3插入数据7
    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表
    return 0;
}

输出结果

c 复制代码
头插法插入数据1 2 3
遍历-链表中的元素为:3 2 1


尾插法插入数据4 5 6
遍历-链表中的元素为:3 2 1 4 5 6

在指定位置3插入数据7
遍历-链表中的元素为:3 2 7 1 4 5 6

请按任意键继续. . .

在指定位置3插入数据7,插入数据成功!


遍历链表

传入头节点的下一个节点,然后再while循环里遍历,直到指向NULL为止,输出链表的数据域内容即可

c 复制代码
/* 单链表 - 遍历 */
void TraverseList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历
    while (p != NULL)  // 遍历到链表末尾
    {
        printf("%d ", p->data); // 输出节点的数据域
        p = p->next;            // 移动到下一个节点
    }
    printf("\n"); // 换行
}

完整代码如下:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType; // 定义元素类型

typedef struct node // 定义节点类型
{
    ElemType data;
    struct node *next;
} Node;

/* 初始化一个单链表-造一个头节点 */
Node *InitList()
{
    Node *head = (Node *)malloc(sizeof(Node)); // 为头节点分配内存
    head->data = 0;                            // 头节点的数据域为0
    head->next = NULL;                         // 头节点的指针域为空
    return head;                               // 返回头节点
}
/*单链表 - 头插法*/
int InsertHead(Node *L, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    p->next = L->next;                      // 新节点的指针域指向头节点的下一个节点(把L的NULL复制给新节点)
    L->next = p;                            // 头节点的指针域指向新节点
    return 1;                               // 返回1表示成功
}
/* 单链表 - 遍历 */
void TraverseList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历
    while (p != NULL)  // 遍历到链表末尾
    {
        printf("%d ", p->data); // 输出节点的数据域
        p = p->next;            // 移动到下一个节点
    }
    printf("\n"); // 换行
}

int main()
{
    Node *list = InitList(); // 初始化一个单链表

    InsertHead(list, 1); // 头插法插入数据1
    InsertHead(list, 2); // 头插法插入数据2
    InsertHead(list, 3); // 头插法插入数据3

    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表

    return 0;
}

输出:

cpp 复制代码
遍历-链表中的元素为:3 2 1

请按任意键继续. . .

按照头插法,依次插入 1 2 3,遍历链表输出 3 2 1,这是正确的!

删除节点

找到被删除节点的前驱节点p

用指针q记录被删除的节点

通过改变p的后继节点实现删除

释放删除节点的空间

删除操作:

c 复制代码
/**
 * @Description:单链表 - 删除指定位置的节点
 * @param {Node} *L 单链表的头节点
 * @param {int} pos 位置
 * @return {*}       返回1表示成功
 */
int DeletePosNode(Node *L, int pos)
{
    // 用来保存删除位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到删除节点的前驱节点
    while (i < pos - 1) // 遍历到删除位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("删除位置不合法\n");
            return 0;
        }
    }
    if (p->next == NULL) // 判断删除位置是否合法
    {
        printf("删除位置不合法\n");
        return 0;
    }
    Node *q = p->next; // 保存要删除的节点的地址
    p->next = q->next; // 删除节点的前驱节点的指针域 指向 删除节点的下一个节点
    free(q);           // 释放删除节点的内存

    return 1; // 返回1表示成功
}

完整的删除节点代码:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType; // 定义元素类型

typedef struct node // 定义节点类型
{
    ElemType data;
    struct node *next;
} Node;

/* 初始化一个单链表-造一个头节点 */
Node *InitList()
{
    Node *head = (Node *)malloc(sizeof(Node)); // 为头节点分配内存
    head->data = 0;                            // 头节点的数据域为0
    head->next = NULL;                         // 头节点的指针域为空
    return head;                               // 返回头节点
}
/*单链表 - 头插法*/
int InsertHead(Node *L, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    p->next = L->next;                      // 新节点的指针域指向头节点的下一个节点(把L的NULL复制给新节点)
    L->next = p;                            // 头节点的指针域指向新节点
    return 1;                               // 返回1表示成功
}
/* 单链表 - 遍历 */
void TraverseList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历
    while (p != NULL)  // 遍历到链表末尾
    {
        printf("%d ", p->data); // 输出节点的数据域
        p = p->next;            // 移动到下一个节点
    }
    printf("\n"); // 换行
}

/* 单链表 - 尾插法 */
// 获取尾节点地址
Node *GetTail(Node *List)
{
    Node *p = List;         // 从头节点开始遍历
    while (p->next != NULL) // 遍历到链表末尾
    {
        p = p->next; // 移动到下一个节点
    }
    return p; // 返回尾节点
}

/**
 * @Description:单链表 - 尾插法插入数据
 * @param {Node} *tail   尾节点
 * @param {ElemType} e   插入的数据
 * @return {*}           返回新的尾节点
 */
Node *InsertTail(Node *tail, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    tail->next = p;                         // 尾节点的指针域指向新节点
    p->next = NULL;                         // 新节点的指针域为空
    return p;                               // 返回新的尾节点
}

/**
 * @Description:单链表 - 在指定位置插入数据
 * @param {Node} *L     单链表的头节点
 * @param {int} pos     位置
 * @param {ElemType} e  插入的数据
 * @return {*}
 */
int InsertPosNode(Node *L, int pos, ElemType e)
{
    // 用来保存插入位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到插入位置的前驱节点
    while (i < pos - 1) // 遍历到插入位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("插入位置不合法\n");
            return 0;
        }
    }

    Node *newnode = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    newnode->data = e;                            // 在新节点的数据域存入数据e
    newnode->next = p->next;                      // 新节点的指针域指向插入位置的前驱节点的下一个节点
    p->next = newnode;                            // 插入位置的前驱节点的指针域指向新节点
    return 1;
}

/**
 * @Description:单链表 - 删除指定位置的节点
 * @param {Node} *L 单链表的头节点
 * @param {int} pos 位置
 * @return {*}       返回1表示成功
 */
int DeletePosNode(Node *L, int pos)
{
    // 用来保存删除位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到删除节点的前驱节点
    while (i < pos - 1) // 遍历到删除位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("删除位置不合法\n");
            return 0;
        }
    }
    if (p->next == NULL) // 判断删除位置是否合法
    {
        printf("删除位置不合法\n");
        return 0;
    }
    Node *q = p->next; // 保存要删除的节点的地址
    p->next = q->next; // 删除节点的前驱节点的指针域 指向 删除节点的下一个节点
    free(q);           // 释放删除节点的内存

    return 1; // 返回1表示成功
}

int main()
{
    Node *list = InitList(); // 初始化一个单链表
    printf("头插法插入数据1 2 3\n");
    InsertHead(list, 1); // 头插法插入数据1
    InsertHead(list, 2); // 头插法插入数据2
    InsertHead(list, 3); // 头插法插入数据3

    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表
    printf("\n");       // 换行

    printf("\n尾插法插入数据4 5 6\n");
    Node *tail = GetTail(list); // 获取尾节点地址
    tail = InsertTail(tail, 4); // 尾插法插入数据4
    tail = InsertTail(tail, 5); // 尾插法插入数据5
    tail = InsertTail(tail, 6); // 尾插法插入数据6
    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表

    printf("\n在指定位置3插入数据7\n");
    InsertPosNode(list, 3, 7); // 在指定位置3插入数据7
    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表

    printf("\n删除指定位置3的节点\n");
    DeletePosNode(list, 3); // 删除指定位置3的节点
    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表
    return 0;
}

输出结果:

c 复制代码
头插法插入数据1 2 3
遍历-链表中的元素为:3 2 1


尾插法插入数据4 5 6
遍历-链表中的元素为:3 2 1 4 5 6

在指定位置3插入数据7
遍历-链表中的元素为:3 2 7 1 4 5 6

删除指定位置3的节点
遍历-链表中的元素为:3 2 1 4 5 6

请按任意键继续. . .

获取链表长度

c 复制代码
int GetListLength(Node *L)
{
    int length = 0;
    Node *p = L->next; // 从头节点的下一个节点开始遍历,头节点不算在内

    while (p != NULL)
    {
        p = p->next;
        length++;
    }
    return length;
}

从头节点的下一个节点开始遍历,头节点不算在内

释放链表

保存头节点,把剩下的每一个节点的内存都释放掉

  • 指针p指向头节点后的第一个节点
  • 判断指针p是否指向NULL
  • 如果p不为NULL,用指针q记录指针p的后继结点
  • 释放指针p指向的节点
  • 指针p和指针q指向同一个节点,循环上面的操作

释放链表代码

c 复制代码
void FreeList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历,头节点不需要释放
    Node *q = NULL;    // 用来保存下一个节点的地址,q能掌握下一个节点的地址,这是灵魂所在
    while (p != NULL)
    {
        q = p->next; // 保存下一个节点的地址
        free(p);     // 释放当前节点的内存
        p = q;       // 移动到下一个节点
    }
    L->next = NULL; // 头节点的指针域为空
}

完整代码如下:

c 复制代码
#include <stdio.h>
#include <stdlib.h>

typedef int ElemType; // 定义元素类型

typedef struct node // 定义节点类型
{
    ElemType data;
    struct node *next;
} Node;

/* 初始化一个单链表-造一个头节点 */
Node *InitList()
{
    Node *head = (Node *)malloc(sizeof(Node)); // 为头节点分配内存
    head->data = 0;                            // 头节点的数据域为0
    head->next = NULL;                         // 头节点的指针域为空
    return head;                               // 返回头节点
}
/*单链表 - 头插法*/
int InsertHead(Node *L, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    p->next = L->next;                      // 新节点的指针域指向头节点的下一个节点(把L的NULL复制给新节点)
    L->next = p;                            // 头节点的指针域指向新节点
    return 1;                               // 返回1表示成功
}
/* 单链表 - 遍历 */
void TraverseList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历
    while (p != NULL)  // 遍历到链表末尾
    {
        printf("%d ", p->data); // 输出节点的数据域
        p = p->next;            // 移动到下一个节点
    }
    printf("\n"); // 换行
}

/* 单链表 - 尾插法 */
// 获取尾节点地址
Node *GetTail(Node *List)
{
    Node *p = List;         // 从头节点开始遍历
    while (p->next != NULL) // 遍历到链表末尾
    {
        p = p->next; // 移动到下一个节点
    }
    return p; // 返回尾节点
}

/**
 * @Description:单链表 - 尾插法插入数据
 * @param {Node} *tail   尾节点
 * @param {ElemType} e   插入的数据
 * @return {*}           返回新的尾节点
 */
Node *InsertTail(Node *tail, ElemType e)
{
    Node *p = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    p->data = e;                            // 在新节点的数据域存入数据e
    tail->next = p;                         // 尾节点的指针域指向新节点
    p->next = NULL;                         // 新节点的指针域为空
    return p;                               // 返回新的尾节点
}

/**
 * @Description:单链表 - 在指定位置插入数据
 * @param {Node} *L     单链表的头节点
 * @param {int} pos     位置
 * @param {ElemType} e  插入的数据
 * @return {*}
 */
int InsertPosNode(Node *L, int pos, ElemType e)
{
    // 用来保存插入位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到插入位置的前驱节点
    while (i < pos - 1) // 遍历到插入位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("插入位置不合法\n");
            return 0;
        }
    }

    Node *newnode = (Node *)malloc(sizeof(Node)); // 创建一个新的节点
    newnode->data = e;                            // 在新节点的数据域存入数据e
    newnode->next = p->next;                      // 新节点的指针域指向插入位置的前驱节点的下一个节点
    p->next = newnode;                            // 插入位置的前驱节点的指针域指向新节点
    return 1;
}

/**
 * @Description:单链表 - 删除指定位置的节点
 * @param {Node} *L 单链表的头节点
 * @param {int} pos 位置
 * @return {*}       返回1表示成功
 */
int DeletePosNode(Node *L, int pos)
{
    // 用来保存删除位置的前驱节点
    Node *p = L; // 从头节点开始遍历
    int i = 0;
    // 遍历链表-找到删除节点的前驱节点
    while (i < pos - 1) // 遍历到删除位置的前驱节点
    {
        p = p->next; // 移动到下一个节点
        i++;
        if (p == NULL) // 判断是否到达链表末尾
        {
            printf("删除位置不合法\n");
            return 0;
        }
    }
    if (p->next == NULL) // 判断删除位置是否合法
    {
        printf("删除位置不合法\n");
        return 0;
    }
    Node *q = p->next; // 保存要删除的节点的地址
    p->next = q->next; // 删除节点的前驱节点的指针域 指向 删除节点的下一个节点
    free(q);           // 释放删除节点的内存

    return 1; // 返回1表示成功
}

int GetListLength(Node *L)
{
    int length = 0;
    Node *p = L; // 从头节点开始遍历,头节点算在内

    while (p != NULL)
    {
        p = p->next;
        length++;
    }
    return length;
}

void FreeList(Node *L)
{
    Node *p = L->next; // 从头节点的下一个节点开始遍历,头节点不需要释放
    Node *q = NULL;    // 用来保存下一个节点的地址,q能掌握下一个节点的地址,这是灵魂所在
    while (p != NULL)
    {
        q = p->next; // 保存下一个节点的地址
        free(p);     // 释放当前节点的内存
        p = q;       // 移动到下一个节点
    }
    L->next = NULL; // 头节点的指针域为空
}

int main()
{
    Node *list = InitList(); // 初始化一个单链表
    printf("头插法插入数据1 2 3\n");
    InsertHead(list, 1); // 头插法插入数据1
    InsertHead(list, 2); // 头插法插入数据2
    InsertHead(list, 3); // 头插法插入数据3

    printf("尾插法插入数据4 5 6\n");
    Node *tail = GetTail(list); // 获取尾节点地址
    tail = InsertTail(tail, 4); // 尾插法插入数据4
    tail = InsertTail(tail, 5); // 尾插法插入数据5
    tail = InsertTail(tail, 6); // 尾插法插入数据6

    printf("在指定位置3插入数据7\n");
    InsertPosNode(list, 3, 7); // 在指定位置3插入数据7

    printf("删除指定位置3的节点\n");
    DeletePosNode(list, 3); // 删除指定位置3的节点
    printf("遍历-链表中的元素为:");
    TraverseList(list); // 遍历单链表

    printf("链表长度为%d\n", GetListLength(list));

    printf("释放链表\n");
    FreeList(list); // 释放链表的内存
    printf("链表长度为%d\n", GetListLength(list));

    return 0;
}

输出

c 复制代码
头插法插入数据1 2 3
尾插法插入数据4 5 6
在指定位置3插入数据7
删除指定位置3的节点
遍历-链表中的元素为:3 2 1 4 5 6
链表长度为7
释放链表
链表长度为1

请按任意键继续. . .
相关推荐
CodeCraft Studio8 分钟前
「实战应用」如何为DHTMLX JavaScript 甘特图添加进度线
javascript·算法·甘特图
wclass-zhengge12 分钟前
02UML图(D1_结构图)
java·开发语言·算法
孑么18 分钟前
力扣 打家劫舍
java·算法·leetcode·职场和发展·动态规划
九离十1 小时前
基于C语言的通讯录实现
c语言·开发语言
打不了嗝 ᥬ᭄1 小时前
Vector的模拟实现与迭代器失效问题
c语言·c++·算法
mljy.2 小时前
优选算法《二分查找》
c++·算法
XianxinMao3 小时前
《多模态语言模型:一个开放探索的技术新领域》
人工智能·算法·语言模型
YANQ6623 小时前
5. 推荐算法的最基础和最直观的认识
算法·机器学习·推荐算法
m0_748251354 小时前
在21世纪的我用C语言探寻世界本质——字符函数和字符串函数(2)
c语言·开发语言