Bootstrap

嵌入式数据结构 —学习笔记 单链表

01单链表_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV1ee4y1q77b?p=9&vd_source=01c0a0b4e215da5cc9a422b60e2ca405

一.线性表的链式存储结构

线性表的链式存储结构是一种用于存储线性表数据结构的方法,它通过一系列的节点来表示线性表中的元素,每个节点包含两部分:

  1. 数据域:存储实际的数据元素。
  2. 指针域:存储指向下一个节点的地址(对于单链表而言),这样就形成了一个链状的结构。

链式存储结构的特点如下:

  • 非连续存储:节点可以在内存中分散存储,不需要像数组那样占据连续的内存空间。
  • 动态分配:节点的创建和销毁可以动态进行,根据需要分配和释放内存。
  • 灵活插入和删除:插入和删除操作不需要移动大量元素,只需要修改相关节点的指针即可。

链式存储结构可以根据节点之间的连接方式进一步细分为以下几种类型:

  • 单链表:每个节点只有一个指向后继节点的指针。
  • 双链表:每个节点有两个指针,一个指向前驱节点,另一个指向后继节点。
  • 循环链表:链表的最后一个节点的指针不是指向NULL,而是回指到链表的第一个节点,形成一个闭环。
  • 静态链表:使用静态数组实现的链表,其中的“指针”实际上是数组下标,通常用在内存受限的环境中。

在链式存储结构中,通常会有一个头指针,它指向链表的第一个节点,如果链表有头结点,则头指针指向头结点,而头结点的指针域则指向链表的第一个数据节点。头结点的存在可以简化某些操作,如插入和删除操作,因为它们总是发生在链表的头部。

链式存储结构的主要优势在于它的灵活性和高效的操作(特别是插入和删除),但缺点是访问特定元素的速度较慢,因为可能需要从头开始遍历链表。

二. 线性表相关函数

2.1 定义链表

typedef int data_t;

typedef struct node {
    data_t data;
    struct node *next;

} linknode, *linklist;

2.2 链表初始化

/**
 * 创建并初始化一个链表
 *
 * 本函数用于动态分配内存以创建一个链表的头节点。头节点的数据部分被初始化为0,下一个节点指针被初始化为NULL,表示链表为空。
 *
 * @return 返回创建的链表的头节点。如果内存分配失败,返回NULL,并打印错误信息。
 */
linklist list_create()
{
    linklist H; // 定义链表的头节点指针

    H = (linklist)malloc(sizeof(linknode)); // 动态分配内存给头节点
    if (H == NULL)
    {                             // 检查内存分配是否成功
        printf("malloc error\n"); // 内存分配失败时,打印错误信息
        return H;                 // 返回NULL表示创建失败
    }
    H->data = 0;    // 初始化头节点的数据部分为0
    H->next = NULL; // 初始化头节点的下一个节点指针为NULL,表示链表为空
    return H;       // 返回创建成功的链表头节点
}

2.3 插入新节点(尾插)

/**
 * 在链表尾部插入一个新节点
 * @param H 链表的头指针
 * @param value 要插入的新节点的数据值
 * @return 如果插入成功,返回0;如果链表为空或内存分配失败,返回-1。
 */
int list_tail_insert(linklist H, data_t value)
{
    linklist P; /* 新节点的指针 */
    linklist Q; /* 用于遍历链表找到尾部的指针 */

    /* 检查链表头指针是否为空 */
    if (H == NULL)
    {
        printf("H is NULL\n");
        return -1;
    }

    /* 为新节点分配内存 */
    if ((P = (linklist)malloc(sizeof(linknode))) == NULL)
    {
        printf("malloc error\n");
        return -1;
    }

    /* 初始化新节点的数据和下一个节点指针 */
    P->data = value;
    P->next = NULL;

    /* 移动指针Q到链表的尾部 */
    Q = H;
    while (Q->next != NULL)
    {
        Q = Q->next;
    }

    /* 将新节点链接到链表的尾部 */
    Q->next = P;

    /* 插入成功,返回0 */
    return 0;
}

2.4 遍历链表

/**
 * 遍历并显示链表中的所有元素。
 * @param H 链表的头节点。链表的头节点不包含数据,主要用于方便链表的操作。
 * @return 函数始终返回0,表示操作成功。
 */
int list_show(linklist H)
{
    // 从头节点的下一个节点开始遍历,因为头节点不包含数据。
    linklist p = H->next;
    // 遍历链表直到最后一个节点。
    while (p != NULL)
    {

        printf("%d ", p->data); // 打印当前节点的数据

        p = p->next; // 移动到下一个节点
    }

    printf("\n");

    return 0;
}

2.5 查找链表节点地址

/**
 * 获取链表中指定位置的节点
 *
 * 本函数旨在从给定的链表中提取出指定位置的节点。链表的索引从0开始计数。
 * 如果给定的位置是-1,则函数直接返回链表的头节点。如果链表为空或指定位置超出链表范围,
 * 函数将打印相应的错误信息并返回NULL。
 *
 * @param H 链表的头节点指针
 * @param pos 指定的节点位置,-1表示头节点
 * @return 返回指定位置的节点指针,如果不存在则返回NULL
 */
linklist list_get(linklist H, int pos)
{
    linklist P = H; // 初始化指针P指向链表的头节点
    if (H == NULL)
    {
        printf("H is NULL\n"); // 检查链表是否为空
        return NULL;
    }
    if (pos == -1)
    {
        return H; // 如果pos为-1,直接返回头节点
    }

    if (pos < -1)
    {
        printf("pos is error\n");
        return NULL;
    }

    while (P != NULL)
    {
        if (pos == 0)
        {
            return P; // 找到指定位置的节点,返回该节点
        }
        pos--;       // 位置索引递减
        P = P->next; // 移动到下一个节点
    }
    if (P == NULL)
    {
        printf("pos is out of range\n"); // 循环结束时,如果未找到指定位置的节点,说明位置超出链表范围
    }
    return NULL; // 返回NULL,表示未找到指定位置的节点或位置超出链表范围
}

2.6 插入新节点

/**
 * 在链表中指定位置插入一个新节点
 * @param H 链表的头节点指针
 * @param pos 插入位置的索引(从0开始)
 * @param value 要插入的新节点的数据值
 * @return 如果插入成功,返回0;如果内存分配失败,返回-1。
 */
int list_insert(linklist H, int pos, data_t value)
{
    linklist P; // 用于定位插入位置前一个节点的指针
    linklist Q; // 用于存放新插入节点的指针

    // 检查链表是否为空
    if (H == NULL)
    {
        printf("H is NULL\n");
        return -1;
    }

    // 获取插入位置的前一个节点
    P = list_get(H, pos - 1);

    // 检查获取的前一个节点是否为空
    if (P == NULL)
    {
        return -1;
    }

    // 分配新节点的内存空间,如果分配失败,则打印错误信息并返回-1
    if ((Q = (linklist)malloc(sizeof(linknode))) == NULL)
    {
        printf("malloc error\n");
        return -1;
    }

    // 设置新节点的数据值
    Q->data = value;

    // 将新节点的next指针指向插入位置的节点
    Q->next = P->next;

    // 将前一个节点的next指针指向新节点,完成插入
    P->next = Q;

    return 0; // 插入成功,返回0
}

2.7 删除指定位置元素

/**
 * 从链表中删除指定位置的元素
 *
 * @param H 链表的头节点指针
 * @param pos 要删除的元素的位置,从0开始计数
 * @return 如果删除成功,返回0;如果链表为空、位置无效或无法访问指定位置,返回-1。
 *
 * 此函数首先检查链表是否为空,然后尝试访问要删除的元素的前一个节点。
 * 如果无法访问指定位置或指定位置超出链表范围,则函数提示错误并返回。
 * 如果一切正常,函数将删除指定位置的元素,并释放相应的内存。
 */
int list_delete(linklist H, int pos)
{
    linklist Q; // 用于存储要删除的节点
    linklist P; // 用于定位到要删除节点的前一个节点
    if (H == NULL)
    {
        printf("H is NULL");
        return -1;
    }

    P = list_get(H, pos - 1); // 获取要删除的节点的前一个节点
    if (P == NULL)
    {
        printf("P is NULL");
        return -1;
    }
    if (P->next == NULL)
    {
        printf("delete pos is invalid\n");
        return -1;
    }
    Q = P->next;       // 获取要删除的节点
    P->next = Q->next; // 将要删除的节点从链表中移除
    free(Q);           // 释放删除的节点的内存
    Q = NULL;

    return 0; // 删除成功,返回0
}

2.8 释放链表

/**
 * 释放链表的所有节点的内存。
 * @param H 链表的头节点指针。
 * @return 释放内存后,返回NULL。
 */
linklist list_free(linklist H)
{
    // 初始化指针P为链表的头节点,用于遍历链表。
    linklist P = H;

    // 检查链表是否为空,如果为空,则打印提示信息并返回-1。
    if (H == NULL)
    {
        printf("H is NULL");
        return NULL;
    }

    // 打印函数调用信息。
    printf("list_free\n");

    // 遍历链表,直到P指向最后一个节点。
    while (P != NULL)
    {
        // 保存当前节点的下一个节点的指针,以便在释放当前节点后继续遍历。
        H = P->next;

        // 释放当前节点的内存。
        free(P);

        // 更新P为下一个待释放的节点。
        P = H;
    }

    return NULL;
}

2.9 反转单链表

/**
 * 反转单链表
 * @param H 单链表的头节点
 * @return 如果链表为空或只有一个节点,则返回-1;否则返回0。
 *
 * 该函数通过迭代方式反转单链表。它首先检查链表是否为空或只有一个节点,
 * 因为这两种情况下都不需要反转。然后,它初始化两个指针P和Q,用于遍历
 * 和反转链表。在遍历过程中,Q用于保存当前节点,P用于前进到下一个节点,
 * 并将Q的下一个节点指向H的下一个节点,从而实现链表的反转。
 */
int list_reverse(linklist H)
{
    linklist P;
    linklist Q;

    /* 检查链表是否为空或只有一个节点 */
    if (H == NULL)
    {
        printf("list is empty\n");
        return -1;
    }
    if (H->next == NULL || H->next->next == NULL)
    {
        printf("list is only one node\n");
        return -1;
    }
    /* 初始化指针P为反转操作的起始节点 */
    P = H->next->next;
    /* 将头节点的下一个节点指向空,以隔离原链表和新链表 */
    H->next->next = NULL;
    /* 遍历链表,进行反转操作 */
    while (P != NULL)
    {
        Q = P;
        P = P->next;
        Q->next = H->next;
        H->next = Q;
    }

    return 0;
}

该函数用于将一个单链表进行反转。函数接受一个链表的头节点指针作为参数。函数首先检查链表是否为空或只有一个节点,如果是,则输出相应的提示信息并返回-1。如果链表不为空且不只有一个节点,则进行链表反转操作。首先将指针P初始化为反转操作的起始节点,即头节点的下一个节点的下一个节点。然后将头节点的下一个节点指向空,以隔离原链表和新链表。接下来使用while循环遍历链表,进行反转操作。在循环中,将指针Q指向当前节点P,然后将指针P指向下一个节点。接着将Q的下一个节点指向头节点的下一个节点,即将Q节点插入到头节点之前。最后将头节点指向Q,完成一次反转操作。循环直到P为NULL,即遍历完整个链表。最后函数返回0,表示链表反转成功。

2.10 链表相邻元素最大节点

/**
 * 寻找链表中相邻元素和最大的元素位置
 * @param H 链表的头节点
 * @return 返回具有最大相邻元素和的节点
 *
 * 该函数遍历链表,找到相邻元素和最大的节点。如果链表为空或长度小于3,
 * 则直接返回原链表或提示信息。否则,从第二个节点开始遍历,更新最大和及其位置。
 */
linklist list_adjMAX(linklist H)
{
    linklist P, Q, R;
    data_t MAX;

    /* 检查链表是否为空 */
    if (H == NULL)
    {
        printf("list is NULL\n");
        return NULL;
    }
    /* 检查链表长度是否太短 */
    if (H->next == NULL || H->next->next == NULL || H->next->next->next == NULL)
    {
        printf("list is too short\n");
        return H;
    }

    /* 初始化指针P、Q、R和最大和MAX */
    Q = H->next;
    P = Q->next;
    R = Q;
    MAX = Q->data + P->data;

    /* 遍历链表,更新最大和及其位置 */
    while (P->next != NULL)
    {
        P = P->next;
        Q = Q->next;
        if (Q->data + P->data > MAX)
        {
            MAX = Q->data + P->data;
            R = Q;
        }
    }
    return R;
}

该函数的功能是在一个给定的链表中找到两个节点,使它们的值相加得到最大值,并返回这两个节点中的第二个节点的指针。函数首先检查链表是否为空或长度是否太短,然后初始化指针P、Q、R和最大和MAX。接下来,函数遍历链表,更新最大和及其位置,直到遍历到链表末尾。最后,函数返回最大和所在位置的节点的指针。

2.11 合并两个有序链表

/**
 * 合并两个有序链表
 * @param H1 第一个有序链表的头节点
 * @param H2 第二个有序链表的头节点
 * @return 返回合并后链表的头节点
 *
 * 该函数将两个有序链表H1和H2合并为一个单一的有序链表。链表的元素按照从小到大的顺序排列。
 * 如果其中一个或两个链表为空,则直接返回非空链表的头节点。合并过程中,新链表的头节点保持为H1。
 */
int list_merge(linklist H1, linklist H2)
{
    linklist P, Q, R;

    /* 检查输入的链表是否为空,如果是,则打印错误信息并返回-1 */
    if (H1 == NULL || H2 == NULL)
    {
        printf("H1 or H2 is NULL\n");
        return -1;
    }

    /* 初始化指针P和Q分别指向H1和H2的下一个节点,R指向H1,作为合并后链表的头节点 */
    P = H1->next;
    Q = H2->next;
    R = H1;
    H1->next = NULL;
    H2->next = NULL;

    /* 遍历两个链表,将较小的元素依次连接到R的后面,直到其中一个链表遍历完毕 */
    while (P != NULL && Q != NULL)
    {
        /* 如果P指向的元素小于Q指向的元素,则将P节点连接到R后面,并更新指针 */
        if (P->data < Q->data)
        {
            R->next = P;
            R = R->next;
            P = P->next;
            R->next = NULL;
        }
        else
        {
            /* 如果Q指向的元素小于等于P指向的元素,则将Q节点连接到R后面,并更新指针 */
            R->next = Q;
            R = R->next;
            Q = Q->next;
            R->next = NULL;
        }
    }

    /* 如果P不为空,说明H1链表还有剩余节点,将剩余节点直接连接到R的后面 */
    if (P != NULL)
    {
        R->next = P;
    }

    /* 如果Q不为空,说明H2链表还有剩余节点,将剩余节点直接连接到R的后面 */
    if (Q != NULL)
    {
        R->next = Q;
    }

    /* 返回合并后链表的头节点 */
    return H1;
}

该函数用于合并两个已排序的单链表。函数首先检查输入的两个链表是否为空,如果为空则打印错误信息并返回-1。然后,函数初始化指针P、Q和R,分别指向两个链表的下一个节点和合并后链表的头节点。接着,函数通过遍历两个链表,将较小的元素依次连接到R的后面,直到其中一个链表遍历完毕。最后,如果某个链表还有剩余节点,将剩余节点直接连接到R的后面。函数返回合并后链表的头节点。

三. 线性表和顺序表

线性表

特点:

  1. 逻辑结构: 线性表是一种基本的逻辑数据结构,其中的数据元素之间存在一对一的线性关系,即每个元素都有一个前驱和一个后继(除了第一个和最后一个元素)。
  2. 多样性: 线性表可以使用多种存储结构来实现,如顺序存储、链式存储、索引存储和散列存储等。
  3. 操作: 线性表支持基本的操作,包括插入、删除、查找和访问等。

缺陷:

  1. 存储结构依赖: 线性表的具体性能取决于所选择的存储结构。例如,链表在插入和删除时比顺序表更高效,但访问速度较慢。
  2. 内存碎片: 使用某些非连续存储结构(如链表)可能会导致内存碎片问题。

顺序表

特点:

  1. 物理连续性: 顺序表是一种具体的存储结构,它使用一组连续的内存位置来存储线性表中的元素,通常使用数组实现。
  2. 随机访问: 由于元素在内存中是连续的,因此可以快速地通过下标进行随机访问。
  3. 固定大小: 创建顺序表时需要预先分配足够的内存空间,这决定了列表的最大容量。

缺陷:

  1. 插入和删除成本高: 在顺序表中插入或删除元素可能需要移动大量元素以保持连续性,效率较低。
  2. 空间浪费: 如果预分配的空间大于实际需求,可能会造成内存空间的浪费。
  3. 扩展性差: 当顺序表满时,如果需要增加更多元素,可能需要重新分配更大的内存空间,并复制所有元素,这会带来额外的开销。

总结来说,线性表是一个抽象的概念,而顺序表是实现线性表的一种具体方法。选择哪种结构取决于具体的应用场景,比如对访问速度有高要求时,顺序表可能是更好的选择;而在频繁进行插入和删除操作时,链式结构可能更为合适。

四.linklist.c linklist.h

linklist.h


typedef int data_t;

typedef struct node {
    data_t data;
    struct node *next;

} linknode, *linklist;

linklist list_create();
int list_tail_insert(linklist H, data_t value);
int list_show(linklist H);
int list_insert(linklist H, int pos, data_t value);
linklist list_get(linklist H, int pos);

int list_delete(linklist H, int pos);
int list_free(linklist H);
int list_reverse(linklist H);
linklist list_adjMAX(linklist H);
int list_merge(linklist H1, linklist H2);

linklist.c

#include "linklist.h"
#include <stdio.h>

/**
 * 创建并初始化一个链表
 *
 * 本函数用于动态分配内存以创建一个链表的头节点。头节点的数据部分被初始化为0,下一个节点指针被初始化为NULL,表示链表为空。
 *
 * @return 返回创建的链表的头节点。如果内存分配失败,返回NULL,并打印错误信息。
 */
linklist list_create()
{
    linklist H; // 定义链表的头节点指针

    H = (linklist)malloc(sizeof(linknode)); // 动态分配内存给头节点
    if (H == NULL)
    {                             // 检查内存分配是否成功
        printf("malloc error\n"); // 内存分配失败时,打印错误信息
        return H;                 // 返回NULL表示创建失败
    }
    H->data = 0;    // 初始化头节点的数据部分为0
    H->next = NULL; // 初始化头节点的下一个节点指针为NULL,表示链表为空
    return H;       // 返回创建成功的链表头节点
}

/**
 * 在链表尾部插入一个新节点
 * @param H 链表的头指针
 * @param value 要插入的新节点的数据值
 * @return 如果插入成功,返回0;如果链表为空或内存分配失败,返回-1。
 */
int list_tail_insert(linklist H, data_t value)
{
    linklist P; /* 新节点的指针 */
    linklist Q; /* 用于遍历链表找到尾部的指针 */

    /* 检查链表头指针是否为空 */
    if (H == NULL)
    {
        printf("H is NULL\n");
        return -1;
    }

    /* 为新节点分配内存 */
    if ((P = (linklist)malloc(sizeof(linknode))) == NULL)
    {
        printf("malloc error\n");
        return -1;
    }

    /* 初始化新节点的数据和下一个节点指针 */
    P->data = value;
    P->next = NULL;

    /* 移动指针Q到链表的尾部 */
    Q = H;
    while (Q->next != NULL)
    {
        Q = Q->next;
    }

    /* 将新节点链接到链表的尾部 */
    Q->next = P;

    /* 插入成功,返回0 */
    return 0;
}

/**
 * 遍历并显示链表中的所有元素。
 * @param H 链表的头节点。链表的头节点不包含数据,主要用于方便链表的操作。
 * @return 函数始终返回0,表示操作成功。
 */
int list_show(linklist H)
{
    // 从头节点的下一个节点开始遍历,因为头节点不包含数据。
    linklist p = H->next;
    // 遍历链表直到最后一个节点。
    while (p != NULL)
    {

        printf("%d ", p->data); // 打印当前节点的数据

        p = p->next; // 移动到下一个节点
    }

    printf("\n");

    return 0;
}

/**
 * 获取链表中指定位置的节点
 *
 * 本函数旨在从给定的链表中提取出指定位置的节点。链表的索引从0开始计数。
 * 如果给定的位置是-1,则函数直接返回链表的头节点。如果链表为空或指定位置超出链表范围,
 * 函数将打印相应的错误信息并返回NULL。
 *
 * @param H 链表的头节点指针
 * @param pos 指定的节点位置,-1表示头节点
 * @return 返回指定位置的节点指针,如果不存在则返回NULL
 */
linklist list_get(linklist H, int pos)
{
    linklist P = H; // 初始化指针P指向链表的头节点
    if (H == NULL)
    {
        printf("H is NULL\n"); // 检查链表是否为空
        return NULL;
    }
    if (pos == -1)
    {
        return H; // 如果pos为-1,直接返回头节点
    }

    if (pos < -1)
    {
        printf("pos is error\n");
        return NULL;
    }

    while (P != NULL)
    {
        if (pos == 0)
        {
            return P; // 找到指定位置的节点,返回该节点
        }
        pos--;       // 位置索引递减
        P = P->next; // 移动到下一个节点
    }
    if (P == NULL)
    {
        printf("pos is out of range\n"); // 循环结束时,如果未找到指定位置的节点,说明位置超出链表范围
    }
    return NULL; // 返回NULL,表示未找到指定位置的节点或位置超出链表范围
}

/**
 * 在链表中指定位置插入一个新节点
 * @param H 链表的头节点指针
 * @param pos 插入位置的索引(从0开始)
 * @param value 要插入的新节点的数据值
 * @return 如果插入成功,返回0;如果内存分配失败,返回-1。
 */
int list_insert(linklist H, int pos, data_t value)
{
    linklist P; // 用于定位插入位置前一个节点的指针
    linklist Q; // 用于存放新插入节点的指针

    // 检查链表是否为空
    if (H == NULL)
    {
        printf("H is NULL\n");
        return -1;
    }

    // 获取插入位置的前一个节点
    P = list_get(H, pos - 1);

    // 检查获取的前一个节点是否为空
    if (P == NULL)
    {
        return -1;
    }

    // 分配新节点的内存空间,如果分配失败,则打印错误信息并返回-1
    if ((Q = (linklist)malloc(sizeof(linknode))) == NULL)
    {
        printf("malloc error\n");
        return -1;
    }

    // 设置新节点的数据值
    Q->data = value;

    // 将新节点的next指针指向插入位置的节点
    Q->next = P->next;

    // 将前一个节点的next指针指向新节点,完成插入
    P->next = Q;

    return 0; // 插入成功,返回0
}

/**
 * 从链表中删除指定位置的元素
 *
 * @param H 链表的头节点指针
 * @param pos 要删除的元素的位置,从0开始计数
 * @return 如果删除成功,返回0;如果链表为空、位置无效或无法访问指定位置,返回-1。
 *
 * 此函数首先检查链表是否为空,然后尝试访问要删除的元素的前一个节点。
 * 如果无法访问指定位置或指定位置超出链表范围,则函数提示错误并返回。
 * 如果一切正常,函数将删除指定位置的元素,并释放相应的内存。
 */
int list_delete(linklist H, int pos)
{
    linklist Q; // 用于存储要删除的节点
    linklist P; // 用于定位到要删除节点的前一个节点
    if (H == NULL)
    {
        printf("H is NULL");
        return -1;
    }

    P = list_get(H, pos - 1); // 获取要删除的节点的前一个节点
    if (P == NULL)
    {
        printf("P is NULL");
        return -1;
    }
    if (P->next == NULL)
    {
        printf("delete pos is invalid\n");
        return -1;
    }
    Q = P->next;       // 获取要删除的节点
    P->next = Q->next; // 将要删除的节点从链表中移除
    free(Q);           // 释放删除的节点的内存
    Q = NULL;

    return 0; // 删除成功,返回0
}

/**
 * 释放链表的所有节点的内存。
 * @param H 链表的头节点指针。
 * @return 释放内存后,返回NULL。
 */
linklist list_free(linklist H)
{
    // 初始化指针P为链表的头节点,用于遍历链表。
    linklist P = H;

    // 检查链表是否为空,如果为空,则打印提示信息并返回-1。
    if (H == NULL)
    {
        printf("H is NULL");
        return NULL;
    }

    // 打印函数调用信息。
    printf("list_free\n");

    // 遍历链表,直到P指向最后一个节点。
    while (P != NULL)
    {
        // 保存当前节点的下一个节点的指针,以便在释放当前节点后继续遍历。
        H = P->next;

        // 释放当前节点的内存。
        free(P);

        // 更新P为下一个待释放的节点。
        P = H;
    }

    return NULL;
}

/**
 * 反转单链表
 * @param H 单链表的头节点
 * @return 如果链表为空或只有一个节点,则返回-1;否则返回0。
 *
 * 该函数通过迭代方式反转单链表。它首先检查链表是否为空或只有一个节点,
 * 因为这两种情况下都不需要反转。然后,它初始化两个指针P和Q,用于遍历
 * 和反转链表。在遍历过程中,Q用于保存当前节点,P用于前进到下一个节点,
 * 并将Q的下一个节点指向H的下一个节点,从而实现链表的反转。
 */
int list_reverse(linklist H)
{
    linklist P;
    linklist Q;

    /* 检查链表是否为空或只有一个节点 */
    if (H == NULL)
    {
        printf("list is empty\n");
        return -1;
    }
    if (H->next == NULL || H->next->next == NULL)
    {
        printf("list is only one node\n");
        return -1;
    }
    /* 初始化指针P为反转操作的起始节点 */
    P = H->next->next;
    /* 将头节点的下一个节点指向空,以隔离原链表和新链表 */
    H->next->next = NULL;
    /* 遍历链表,进行反转操作 */
    while (P != NULL)
    {
        Q = P;
        P = P->next;
        Q->next = H->next;
        H->next = Q;
    }

    return 0;
}

/**
 * 寻找链表中相邻元素和最大的元素位置
 * @param H 链表的头节点
 * @return 返回具有最大相邻元素和的节点
 *
 * 该函数遍历链表,找到相邻元素和最大的节点。如果链表为空或长度小于3,
 * 则直接返回原链表或提示信息。否则,从第二个节点开始遍历,更新最大和及其位置。
 */
linklist list_adjMAX(linklist H)
{
    linklist P, Q, R;
    data_t MAX;

    /* 检查链表是否为空 */
    if (H == NULL)
    {
        printf("list is NULL\n");
        return NULL;
    }
    /* 检查链表长度是否太短 */
    if (H->next == NULL || H->next->next == NULL || H->next->next->next == NULL)
    {
        printf("list is too short\n");
        return H;
    }

    /* 初始化指针P、Q、R和最大和MAX */
    Q = H->next;
    P = Q->next;
    R = Q;
    MAX = Q->data + P->data;

    /* 遍历链表,更新最大和及其位置 */
    while (P->next != NULL)
    {
        P = P->next;
        Q = Q->next;
        if (Q->data + P->data > MAX)
        {
            MAX = Q->data + P->data;
            R = Q;
        }
    }
    return R;
}

/**
 * 合并两个有序链表
 * @param H1 第一个有序链表的头节点
 * @param H2 第二个有序链表的头节点
 * @return 返回合并后链表的头节点
 *
 * 该函数将两个有序链表H1和H2合并为一个单一的有序链表。链表的元素按照从小到大的顺序排列。
 * 如果其中一个或两个链表为空,则直接返回非空链表的头节点。合并过程中,新链表的头节点保持为H1。
 */
int list_merge(linklist H1, linklist H2)
{
    linklist P, Q, R;

    /* 检查输入的链表是否为空,如果是,则打印错误信息并返回-1 */
    if (H1 == NULL || H2 == NULL)
    {
        printf("H1 or H2 is NULL\n");
        return -1;
    }

    /* 初始化指针P和Q分别指向H1和H2的下一个节点,R指向H1,作为合并后链表的头节点 */
    P = H1->next;
    Q = H2->next;
    R = H1;
    H1->next = NULL;
    H2->next = NULL;

    /* 遍历两个链表,将较小的元素依次连接到R的后面,直到其中一个链表遍历完毕 */
    while (P != NULL && Q != NULL)
    {
        /* 如果P指向的元素小于Q指向的元素,则将P节点连接到R后面,并更新指针 */
        if (P->data < Q->data)
        {
            R->next = P;
            R = R->next;
            P = P->next;
            R->next = NULL;
        }
        else
        {
            /* 如果Q指向的元素小于等于P指向的元素,则将Q节点连接到R后面,并更新指针 */
            R->next = Q;
            R = R->next;
            Q = Q->next;
            R->next = NULL;
        }
    }

    /* 如果P不为空,说明H1链表还有剩余节点,将剩余节点直接连接到R的后面 */
    if (P != NULL)
    {
        R->next = P;
    }

    /* 如果Q不为空,说明H2链表还有剩余节点,将剩余节点直接连接到R的后面 */
    if (Q != NULL)
    {
        R->next = Q;
    }

    /* 返回合并后链表的头节点 */
    return H1;
}

;