【数据结构】单链表的基本操作(C语言版)

目录

单链表的定义

 单链表的特点以及与顺序表的差别

单链表的基本操作 

1、单链表的初始化

 2、头插法建立单链表

3、尾插法建立单链表 

 4、求单链表长度

5、按值查找元素

6、按序号查找元素

7、在第i个位置前插入节点(元素)

8、在某个值x前插入节点 

 9、按位置删除节点

 10、删除单链表中所有数据域等于x的节点

11、输出单链表数据

12、销毁单链表 

完整测试代码: 


单链表的定义

单链表(Singly linked list)是一种常见的数据结构,它由一个结点(Node)的链表构成,每个结点包含两个域:数据域和指针域。数据域用于存储结点的数据,指针域用于存储下一个结点的地址。

单链表中的第一个结点被称为头结点(Head),它用来存放链表的基地址。头结点中的指针域指向链表的第一个结点,最后一个结点的指针域指向空地址,表示链表结束。

下面是一个单链表结点的定义:

typedef int DataType;

typedef struct ListNode
{
	DataType data;            // 数据域,存储结点的数据
	struct ListNode* next;    // 指针域,指向下一个结点
}ListNode;

 

值得注意的是,单链表是一种动态数据结构,可以在运行时动态添加或删除结点,也可以在任何位置插入或删除结点,因为每个结点的指针域储存的是下一个结点的地址,所以链表的结构是相对灵活的,有着较强的扩展性。

 单链表的特点以及与顺序表的差别

  1. 链表是动态数据结构:链表的长度可以根据实际需要进行动态调整,可以在运行时添加或删除结点。

  2. 链表的插入和删除操作高效:在链表中插入或删除结点的操作时间复杂度为O(1),只需要修改指针的指向即可,而不需要像数组那样需要移动其他元素。

  3. 链表对内存的利用率较低:链表中的结点是通过指针连接的,需要额外的内存空间来存储指针。

  4. 链表的访问操作相对较慢:由于链表中的结点不是连续存储的,因此无法像数组一样通过下标直接访问元素,需要从头结点开始遍历链表,直到找到目标结点。

  5. 链表的结构灵活:可以在链表中间插入或删除结点,只需修改指针的指向即可。而数组的长度是固定的,插入或删除元素需要移动其他元素。

  6. 链表适用于频繁插入和删除的场景:由于链表插入和删除的时间复杂度为O(1),相比数组,链表更适合频繁插入和删除结点的情况。

需要注意的是,链表的访问操作时间复杂度为O(n),其中n是链表的长度。因此,在需要频繁根据索引访问元素的情况下,数组可能更加合适。而链表适用于需要动态调整长度、频繁插入和删除元素的场景。


单链表的基本操作 

1、单链表的初始化

 单链表的初始化即构造一个仅包含头结点的空单链表。其过程是首先申请一个结点并让指针 head指向该结点,然后将它的指针域赋为空(NULL),最后返回头指针 head。

首先我们先了解下什么是头节点以及为什么要设置头节点:

  1. 方便操作空链表:初始时,链表是空的,没有任何结点。如果没有头结点,链表的第一个结点就是空链表。通过设置头结点,即使链表为空,也有了一个起始的位置,可以方便地进行插入和删除操作。

  2. 简化插入和删除操作:有了头结点,插入和删除操作可以统一处理。无论在链表的头部、中间还是尾部插入或删除结点,操作逻辑都是一致的。通过将头结点作为起点,可以避免处理边界情况的复杂性。

  3. 方便获取链表长度:头结点为链表提供了一个便捷的起点,从头结点开始遍历链表,可以方便地计算链表的长度。

  4. 方便遍历操作:通过头结点,从链表的第一个结点开始遍历整个链表,可以避免操作和管理指针时的边界情况。

注意:头结点不存储实际的数据,仅作为链表的起点其下一个结点指向链表的第一个有效节点。因此,在使用链表时,需要特别注意头结点的存在,并在涉及到插入、删除和遍历操作时进行处理。

由此可见,设置头节点可以简化部分操作。当然不设置头节点也是可以的,但是博主建议大家在学习单链表的初期尽量使用带头节点的单链表,等掌握熟练后自己再尝试不包含头节点的单链表。 

 

// 初始化单链表
ListNode* InitList()
{
    // 创建头结点,并进行内存分配
    ListNode* head = (ListNode*)malloc(sizeof(ListNode));
   
    // 检查内存分配是否成功
    if (head == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }

    // 将头结点的指针域置空
    head->next = NULL;

    return head;
}

 2、头插法建立单链表

链表与顺序表不同,它是一种动态管理的存储结构,链表中的每个结点占用的存储空间不是预先分配,而是运行时系统根据需求生成的。因此,建立在初始化链表后,建立线性链表从空表开始,每读入有效的数据则申请一个结点s,并将读取的数据存放到新结点s的数据域中,然后将新结点插入到当前链表头结点head之后,直到循环结束为止。

注意:代码中head为二级指针,即链表头结点的地址。

*head 是一个解引用操作,意味着它获取了 head 指针所指向的值。在这种情况下,head 是一个指向链表头结点指针的指针,通过 *head 可以获取到链表头结点指针的值。

通过 *head 可以访问并修改链表的头指针。在 AddListHead 函数中,(*head)->next 用于获取头结点的下一个节点的指针,而 (*head)->next = newnode 则是将新节点的地址赋值给头结点的下一个节点指针,从而实现在链表头部插入新节点的操作。

void CreateListHead(ListNode** head)
{
    assert(head && *head);
    int i, n, val;

    // 获取要增加的节点数
    printf("需要增加多少个节点:");
    scanf("%d", &n);

    // 循环增加新节点
    for (i = 0; i < n; i++)
    {
        // 获取用户输入的节点值
        printf("请输入第 %d 个节点的数据:", i + 1);
        scanf("%d", &val);

        // 创建新节点,并进行内存分配检查
        ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));

        // 检查内存分配是否成功
        if (newnode == NULL)
        {
            perror("malloc fail");
            exit(-1);
        }

        // 将节点值存储在新节点中
        newnode->data = val;

        // 将新节点插入到链表头部:将新节点的下一个节点指向当前头节点的下一个节点
        // 然后将新节点设为头节点的下一个节点
        newnode->next = (*head)->next;
        (*head)->next = newnode;
    }
}

3、尾插法建立单链表 

 头插法建立链表虽然算法简单易理解,但生成的链表中结点的次序和原输入的次序相反。而尾插法建立链表可实现次序的一致,该算法依旧从空表开始,但需增加一个尾指针last,使其指向当前链表的尾结点。其过程是:每读入有效的数据则申请一个结点s,并将读取的数据存放到新结点s的数据域中,将s的尾指针设为空指针(NULL),然后将新结点插入到当前链表尾部(last 指针所指的结点后面),直到循环结束为止。

void CreateListLast(ListNode** head)
{
    assert(head && *head);

    int i, n, val;

    // 获取要增加的节点数
    printf("需要增加多少个节点:");
    scanf("%d", &n);

    // 创建临时指针并指向链表的头节点
    ListNode* temp = *head;

    // 移动到链表的最后一个节点
    while (temp->next)
    {
        temp = temp->next;
    }

    // 循环增加新节点
    for (i = 0; i < n; i++)
    {
        // 获取用户输入的节点值
        printf("请输入第 %d 个节点的数据:", i + 1);
        scanf("%d", &val);

        // 创建新节点,并进行内存分配检查
        ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));

        // 检查内存分配是否成功
        if (newnode == NULL)
        {
            perror("malloc fail");
            exit(-1);
        }

        // 将节点值存储在新节点中
        newnode->data = val;
        newnode->next = NULL;

        // 将新节点连接到链表的最后一个节点,并更新最后一个节点为新节点
        temp->next = newnode;
        temp = newnode;
    }
}

 4、求单链表长度

  1. 检查头节点是否为空,如果为空则无法计算节点数量,可以抛出异常或者进行其他错误处理。
  2. 初始化指针 p,指向链表的第一个节点(头节点的下一个节点)。
  3. 初始化计数器 count 为 0。
  4. 使用循环遍历链表,直到 p 为 NULL(链表遍历结束)。
  5. 在循环中,每遍历到一个节点,计数器 count 加 1。
  6. 将指针 p 指向下一个节点,继续遍历。
  7. 遍历结束后,返回计数器 count 的值,即链表的节点数量。

注意:头节点不计入链表长度。

int LengthList(ListNode* head)
{
    // 检查头结点是否为空
    assert(head);
    
    // 初始化指针p,指向链表的第一个节点
    ListNode* p = head->next;
    
    // 初始化计数器count为0
    int count = 0;
    
    // 使用循环遍历链表,计算节点数量
    while (p != NULL)
    {
        // 每遍历到一个节点,计数器加1
        count++;
        
        // 将指针p指向下一个节点
        p = p->next;
    }
    
    // 返回链表节点的数量
    return count;
}

说明:本函数中仅对单链表进行遍历,未对链表内容进行改变,所以函数形参为一级指针即可,即头指针。


5、按值查找元素

  1. 初始化变量 j 为 1,指针 p 指向头节点的下一个节点。
  2. 使用循环遍历链表,直到找到值等于 x 的节点或者遍历结束。
  3. 在循环中,检查当前节点 p 的 data 值是否等于 x。如果相等,则找到了指定值的节点。打印提示信息,显示在链表的第 j 位上找到值为 x 的节点。
  4. 如果不相等,继续遍历下一个节点。同时,j 增加 1。
  5. 如果遍历结束仍未找到值为 x 的节点,打印提示信息,显示未找到值为 x 的节点。
// 按值查找
void Locate(ListNode* head, DataType x)
{
    int j = 1;
    ListNode* p;
    p = head->next;

    while (p != NULL)
    {
        if (p->data == x)
        {
            // 在表中找到值为x的结点
            printf("在表的第%d位找到值为%d的结点!", j, x);
            return;
        }
        p = p->next;
        j++;
    }

    // 在链表中未找到值为x的结点
    printf("未找到值为%d的结点!", x);
}

6、按序号查找元素

  1. 初始化变量 j 为 0,指针 p 指向头节点的下一个节点。
  2. 使用循环遍历链表,直到找到指定位置 i 的节点或者遍历结束。
  3. 在循环中,p 指针向后移动,并且 j 增加 1,直到 j 等于 i 或 p 为 NULL。
  4. 如果 j 等于 i,说明找到了指定位置的节点。打印提示信息,显示在第 i 位上的元素值为 p 的 data 值。
  5. 如果 j 不等于 i,说明链表中没有指定位置的节点。打印提示信息,显示位置错误,链表中没有该位置。
// 按序号查找
void SearchList(ListNode* head, int i)
{
    int j = 0;
    ListNode* p = head->next;

    while (p != NULL && j < i)
    {
        p = p->next;
        j++;
    }

    if (j == i)
    {
        // 在第i位上找到结点
        printf("在第%d位上的元素值为%d!", i, p->data);
    }
    else
    {
        // 位置错误,链表中没有该位置
        printf("位置错误,链表中没有该位置!");
    }
}

7、在第i个位置前插入节点(元素)

算法思路:

  1. 首先如果链表为空,那么直接新建一个节点,把链表的头指针指向该节点。
  2. 否则,从链表的头节点开始,使用一个指针变量 cur 循环遍历链表找到插入位置的前一个节点。具体操作如下:
    • 初始化指针变量 cur 为链表的头节点。
    • 用计数器 count 从 0 开始往后逐一比较节点,如果当前节点是第 i-1 个节点,则暂停循环。
    • 如果 count 已经是链表中的最后一个节点,那么说明插入位置超出了链表的长度,插入失败。
  3. 如果插入位置符合要求,则在 cur 节点后插入新节点,并更新相应指针。具体操作如下:
    • 新建一个节点 newnode,将元素值赋给它。
    • 将新节点newnode的 next 指针指向 cur->next。
    • 将 cur->next 指针指向 newnode。
// 链表插入节点函数
void InsertNode(ListNode** head, int i, int value)
{
    // 创建新节点
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = value;
    newNode->next = NULL;

    // 如果链表为空
    if (*head == NULL)
    {
        newNode->next = *head;
        *head = newNode;
        printf("插入成功\n");
        return;
    }

    // 找到插入位置的前一个节点
    int count = 0;
    ListNode* cur = *head;
    while (cur->next != NULL && count < i - 1)
    {
        cur = cur->next;
        count++;
    }

    // 如果插入位置超过链表长度,插入失败
    if (cur == NULL)
    {
        free(newNode);
        printf("插入失败\n");
        return;
    }

    // 插入新节点
    newNode->next = cur->next;
    cur->next = newNode;

    printf("插入成功\n");
    return;
}

8、在某个值x前插入节点 

  1. 首先,创建一个新的节点 newNode,并给其分配内存空间。将要插入的值 value 赋给新节点 newNode 的 data,将新节点的 next 指针设为 NULL。
  2. 检查链表是否为空,如果为空,则将新节点 newNode 插入链表的头部,并将新节点设为头指针。此时打印提示信息并返回。
  3. 如果链表不为空,需要找到要插入节点的前一个节点。初始化两个指针 prev 和 cur,分别指向 NULL 和头节点 *head。
  4. 使用 prev 和 cur 遍历链表,寻找要插入的值 x。
  5. 如果找到要插入的值 x,将新节点 newNode 插入到 cur 节点之前。即将新节点的 next 指针指向 cur,同时将 prev 的 next 指针指向新节点。同时更新头指针 *head,如果 prev 为 NULL,则表示要插入的节点在链表的头部。
  6. 如果未找到要插入的值 x,说明要插入的节点的值在链表中不存在,插入失败。此时释放新节点所占用的内存空间,并打印提示信息。
  7. 插入成功,打印提示信息。
void InsertNodeBeforeValue(ListNode** head, int x, int value)
{
    // 创建新节点
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = value;
    newNode->next = NULL;

    // 如果链表为空
    if (*head == NULL)
    {
        newNode->next = *head;
        *head = newNode;
        printf("插入成功\n");
        return;
    }

    // 找到要插入节点的前一个节点
    ListNode* prev = NULL;
    ListNode* cur = *head;
    while (cur != NULL)
    {
        // 如果找到要插入节点的值
        if (cur->data == x)
        {
            // 插入新节点
            newNode->next = cur;
            if (prev == NULL)
            {
                *head = newNode;
            }
            else
            {
                prev->next = newNode;
            }
            printf("插入成功\n");
            return;
        }

        prev = cur;
        cur = cur->next;
    }

    // 未找到要插入节点的值,插入失败
    free(newNode);
    printf("插入失败\n");
}

 9、按位置删除节点

  1. 首先,判断链表是否为空或指定的位置是否无效。如果链表为空或指定的位置小于等于 0,则无法进行删除操作。此时打印提示信息并返回。
  2. 初始化一个指针 cur,指向头节点。
  3. 使用计数器 count 和指针 cur 遍历链表,找到待删除节点的前一个节点。如果计数器 count 小于 index-1,且指针 cur 的下一个节点不为 NULL,则令指针 cur 指向下一个节点,同时计数器 count 自增。
  4. 如果计数器 count 不等于 index-1,或者指针 cur 的下一个节点为 NULL,则表示指定位置不在链表的范围内,删除失败。此时打印提示信息并返回。
  5. 执行到此,说明已经找到待删除节点的前一个节点 cur。使用一个临时指针 temp,指向待删除节点的位置。
  6. 更新 cur 的 next 指针,令其指向待删除节点的下一个节点,从而实现删除操作。释放节点 temp 所占用的内存空间。
  7. 删除成功,打印提示信息。
void DeleteNodeAtPosition(ListNode** head, int index) {
    // 链表为空或者索引无效,无法进行删除操作
    if (*head == NULL || index <= 0) {
        printf("删除失败\n");
        return;
    }

    ListNode* cur = *head;
    int count = 0;

    // 找到待删除节点的前一个节点
    while (count < index - 1 && cur->next != NULL) {
        cur = cur->next;
        count++;
    }

    // 如果索引超出链表范围,删除失败
    if (count != index - 1 || cur->next == NULL) {
        printf("删除失败\n");
        return;
    }

    // 删除节点
    ListNode* temp = cur->next;
    cur->next = cur->next->next;
    free(temp);

    printf("删除成功\n");
    return;
}

 10、删除单链表中所有数据域等于x的节点

  1. 初始化一个指针 cur,指向链表的头节点。
  2. 遍历链表,如果 cur 的下一个节点的数据域值等于 x,说明该节点需要被删除。此时,创建一个临时节点指针 temp,保存 cur 的下一个节点的地址。修改 cur 的 next 指针,将其指向 temp 的下一个节点。cur仍需在当前位置,不需要移动至下一个位置。释放 temp 指向的节点,完成删除操作。
  3. 如果 cur 的下一个节点的数据域值不等于 x,则将 cur 移动到下一个节点,直到遍历到链表的末尾。
  4. 判断链表删除是否成功,即判断 cur 的 next 指针是否为 NULL。如果为 NULL,则表示链表中不存在值为 x 的节点,删除失败;否则表示链表中至少存在一个值为 x 的节点,删除成功。
void DeleteNodeByValue(ListNode** head, int x)
{
    assert(head && *head); // 确保指针不为空

    ListNode* cur = *head; // 当前节点指针
    while (cur->next != NULL) // 遍历链表
    {
        if (cur->next->data == x) // 找到要删除的节点
        {
            ListNode* temp = cur->next; // 临时保存要删除的节点
            // 修改指针,将当前节点的下一个节点指向要删除节点的下一个节点
            cur->next = cur->next->next; 
            free(temp); // 释放要删除节点的内存
        }
        else // 当前节点不是要删除的节点
        {
            cur = cur->next; // 移动到下一个节点
        }
    }
    if (cur->next == NULL)
    {
        printf("删除成功\n");
        return;
    }
    printf("删除失败\n");
}

11、输出单链表数据

  1. 首先,判断链表是否为空。检查 head 的 next 指针是否为 NULL,如果为 NULL,则表示链表为空,打印提示信息并返回。
  2. 初始化一个指针 cur,指向链表的第一个节点(即头节点的下一个节点)。
  3. 遍历链表,依次访问每个节点。在循环中,当cur不为空时,打印当前节点的数据域值(假设为整数类型)。
  4. 将 cur 移动到下一个节点,直到遍历完整个链表。
  5. 循环结束后,打印换行符,完成打印操作。
// 打印链表数据
void PrintList(ListNode* head)
{
    if (head->next == NULL)
    {
        printf("链表为空\n");
        return;
    }

    ListNode* cur = head->next;
    while (cur != NULL)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

12、销毁单链表 

  1. 首先,确保链表不为空。检查 head 是否为空指针,如果为空,则断言失败,程序会中止执行。
  2. 初始化一个指针 cur,指向 head。
  3. 遍历链表,依次释放每个节点所占用的内存空间。
  4. 在循环中,先保存当前节点的下一个节点的地址,然后释放当前节点的内存空间(如果颠倒顺序则无法找到下一个节点的地址),最后将当前节点指针 cur 指向下一个节点。
  5. 重复执行步骤 4 直到遍历到链表的末尾。
  6. 销毁链表之后,可以将头指针的值设为 NULL。
void ListDestory(ListNode* head)
{
    assert(head);
    ListNode* cur = head;
    while (cur != NULL)
    {
        head = cur->next;
        free(cur);
        cur = head;
    }
}

完整测试代码: 

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

typedef int DataType;

typedef struct ListNode
{
	DataType data;
	struct ListNode* next;
}ListNode;

ListNode* InitList();

void CreateListHead(ListNode** head);
void CreateListLast(ListNode** head);


int LengthList(ListNode* head);
void Locate(ListNode* head, DataType x);
void SearchList(ListNode* head, int i);
void InsertNode(ListNode** head, int i, int value);
void InsertNodeBeforeValue(ListNode** head, int x, int value);
void DeleteNodeByValue(ListNode** head, int x);
void DeleteNodeAtPosition(ListNode** head, int index);
void PrintList(ListNode* head);
void ListDestory(ListNode* head);

//初始化
ListNode* InitList()
{
	ListNode* head = (ListNode*)malloc(sizeof(ListNode));
	if (head == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	head->next = NULL;
	return head;
}

void CreateListHead(ListNode** head)
{
    assert(head && *head);
    int i, n, val;

    // 获取要增加的节点数
    printf("需要增加多少个节点:");
    scanf("%d", &n);

    // 循环增加新节点
    for (i = 0; i < n; i++)
    {
        // 获取用户输入的节点值
        printf("请输入第 %d 个节点的数据:", i + 1);
        scanf("%d", &val);

        // 创建新节点,并进行内存分配检查
        ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));

        // 检查内存分配是否成功
        if (newnode == NULL)
        {
            perror("malloc fail");
            exit(-1);
        }

        // 将节点值存储在新节点中
        newnode->data = val;

        // 将新节点插入到链表头部:将新节点的下一个节点指向当前头节点的下一个节点
        // 然后将新节点设为头节点的下一个节点
        newnode->next = (*head)->next;
        (*head)->next = newnode;
    }
}

void CreateListLast(ListNode** head)
{
    assert(head && *head);

    int i, n, val;

    // 获取要增加的节点数
    printf("需要增加多少个节点:");
    scanf("%d", &n);

    // 创建临时指针并指向链表的头节点
    ListNode* temp = *head;

    // 移动到链表的最后一个节点
    while (temp->next)
    {
        temp = temp->next;
    }

    // 循环增加新节点
    for (i = 0; i < n; i++)
    {
        // 获取用户输入的节点值
        printf("请输入第 %d 个节点的数据:", i + 1);
        scanf("%d", &val);

        // 创建新节点,并进行内存分配检查
        ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));

        // 检查内存分配是否成功
        if (newnode == NULL)
        {
            perror("malloc fail");
            exit(-1);
        }

        // 将节点值存储在新节点中
        newnode->data = val;
        newnode->next = NULL;

        // 将新节点连接到链表的最后一个节点,并更新最后一个节点为新节点
        temp->next = newnode;
        temp = newnode;
    }
}

int LengthList(ListNode* head)
{
    // 检查头结点是否为空
    assert(head);

    // 初始化指针p,指向链表的第一个节点
    ListNode* p = head->next;

    // 初始化计数器count为0
    int count = 0;

    // 使用循环遍历链表,计算节点数量
    while (p != NULL)
    {
        // 每遍历到一个节点,计数器加1
        count++;

        // 将指针p指向下一个节点
        p = p->next;
    }

    // 返回链表节点的数量
    return count;
}

// 按值查找
void Locate(ListNode* head, DataType x)
{
    int j = 1;
    ListNode* p;
    p = head->next;

    while (p != NULL)
    {
        if (p->data == x)
        {
            // 在表中找到值为x的结点
            printf("在表的第%d位找到值为%d的结点!", j, x);
            return;
        }
        p = p->next;
        j++;
    }

    // 在链表中未找到值为x的结点
    printf("未找到值为%d的结点!", x);
}

// 按序号查找
void SearchList(ListNode* head, int i)
{
    int j = 0;
    ListNode* p = head->next;

    while (p != NULL && j < i)
    {
        p = p->next;
        j++;
    }

    if (j == i)
    {
        // 在第i位上找到结点
        printf("在第%d位上的元素值为%d!", i, p->data);
    }
    else
    {
        // 位置错误,链表中没有该位置
        printf("位置错误,链表中没有该位置!");
    }
}

// 链表插入节点函数
void InsertNode(ListNode** head, int i, int value)
{
    // 创建新节点
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = value;
    newNode->next = NULL;

    // 如果链表为空或者插入位置是第一个位置
    if (*head == NULL)
    {
        newNode->next = *head;
        *head = newNode;
        printf("插入成功\n");
        return;
    }

    // 找到插入位置的前一个节点
    int count = 0;
    ListNode* cur = *head;
    while (cur->next != NULL && count < i - 1)
    {
        cur = cur->next;
        count++;
    }

    // 如果插入位置超过链表长度,插入失败
    if (cur == NULL)
    {
        free(newNode);
        printf("插入失败\n");
        return;
    }

    // 插入新节点
    newNode->next = cur->next;
    cur->next = newNode;

    printf("插入成功\n");
    return;
}

// 打印链表数据
void PrintList(ListNode* head)
{
    if (head->next == NULL)
    {
        printf("链表为空\n");
        return;
    }

    ListNode* cur = head->next;
    while (cur != NULL)
    {
        printf("%d ", cur->data);
        cur = cur->next;
    }
    printf("\n");
}

void InsertNodeBeforeValue(ListNode** head, int x, int value)
{
    // 创建新节点
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode->data = value;
    newNode->next = NULL;

    // 如果链表为空
    if (*head == NULL)
    {
        newNode->next = *head;
        *head = newNode;
        printf("插入成功\n");
        return;
    }

    // 找到要插入节点的前一个节点
    ListNode* prev = NULL;
    ListNode* cur = *head;
    while (cur != NULL)
    {
        // 如果找到要插入节点的值
        if (cur->data == x)
        {
            // 插入新节点
            newNode->next = cur;
            if (prev == NULL)
            {
                *head = newNode;
            }
            else
            {
                prev->next = newNode;
            }
            printf("插入成功\n");
            return;
        }

        prev = cur;
        cur = cur->next;
    }

    // 未找到要插入节点的值,插入失败
    free(newNode);
    printf("插入失败\n");
}

void DeleteNodeAtPosition(ListNode** head, int index) {
    // 链表为空或者索引无效,无法进行删除操作
    if (*head == NULL || index <= 0) {
        printf("删除失败\n");
        return;
    }


    ListNode* cur = *head;
    int count = 0;

    // 找到待删除节点的前一个节点
    while (count < index - 1 && cur->next != NULL) {
        cur = cur->next;
        count++;
    }

    // 如果索引超出链表范围,删除失败
    if (count != index - 1 || cur->next == NULL) {
        printf("删除失败\n");
        return;
    }

    // 删除节点
    ListNode* temp = cur->next;
    cur->next = cur->next->next;
    free(temp);

    printf("删除成功\n");
    return;
}

void DeleteNodeByValue(ListNode** head, int x)
{
    assert(head && *head); // 确保指针不为空

    ListNode* cur = *head; // 当前节点指针
    while (cur->next != NULL) // 遍历链表
    {
        if (cur->next->data == x) // 找到要删除的节点
        {
            ListNode* temp = cur->next; // 临时保存要删除的节点
            // 修改指针,将当前节点的下一个节点指向要删除节点的下一个节点
            cur->next = cur->next->next; 
            free(temp); // 释放要删除节点的内存
        }
        else // 当前节点不是要删除的节点
        {
            cur = cur->next; // 移动到下一个节点
        }
    }
    if (cur->next == NULL)
    {
        printf("删除成功\n");
        return;
    }
    printf("删除失败\n");
}

void ListDestory(ListNode* head)
{
    assert(head);
    ListNode* cur = head;
    while (cur != NULL)
    {
        head = cur->next;
        free(cur);
        cur = head;
    }
}

int main()
{
	ListNode* head = InitList();
	//CreateListLast(&head);
	CreateListHead(&head);
	PrintList(head);
	InsertNode(&head, 2, 11);
	PrintList(head);
	DeleteNodeAtPosition(&head, 3);
	PrintList(head);
	DeleteNodeByValue(&head, 3);
	PrintList(head);
    return 0;
}

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
单链表是一种常见的数据结构,用于存储一系列的元素。在C语言中,可以通过定义一个结构体来表示单链表节点,每个节点包含一个数据域和一个指针域,指向下一个节点。 首先,我们需要定义单链表节点结构体: ```c struct ListNode { int data; // 数据域 struct ListNode* next; // 指针域,指向下一个节点 }; ``` 接下来,我们可以实现一些基本操作,比如创建单链表插入节点、删除节点和遍历单链表等。 创建单链表的函数如下所示,可以根据给定的数组创建一个单链表: ```c struct ListNode* createLinkedList(int arr[], int size) { struct ListNode* head = NULL; // 头节点指针 struct ListNode* tail = NULL; // 尾节点指针 for (int i = 0; i < size; i++) { struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode)); newNode->data = arr[i]; newNode->next = NULL; if (head == NULL) { head = newNode; tail = newNode; } else { tail->next = newNode; tail = newNode; } } return head; } ``` 插入节点的函数如下所示,可以在指定位置插入一个节点: ```c void insertNode(struct ListNode** head, int data, int position) { struct ListNode* newNode = (struct ListNode*)malloc(sizeof(struct ListNode)); newNode->data = data; newNode->next = NULL; // 插入到链表头部 if (position == 0) { newNode->next = *head; *head = newNode; return; } struct ListNode* curr = *head; struct ListNode* prev = NULL; int count = 0; // 找到插入位置的前一个节点 while (curr != NULL && count < position) { prev = curr; curr = curr->next; count++; } // 插入到链表中间或尾部 if (curr != NULL) { newNode->next = curr; } prev->next = newNode; } ``` 删除节点的函数如下所示,可以删除指定位置节点: ```c void deleteNode(struct ListNode** head, int position) { if (*head == NULL) { return; } struct ListNode* curr = *head; struct ListNode* prev = NULL; int count = 0; // 找到要删除的节点 while (curr != NULL && count < position) { prev = curr; curr = curr->next; count++; } // 删除头节点 if (prev == NULL) { *head = curr->next; } else { prev->next = curr->next; } free(curr); } ``` 遍历单链表的函数如下所示,可以将单链表中的元素依次输出: ```c void traverseLinkedList(struct ListNode* head) { struct ListNode* curr = head; while (curr != NULL) { printf("%d ", curr->data); curr = curr->next; } } ``` 以上是单链表基本操作实现,你可以根据需要调用这些函数进行单链表的操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

这题怎么做?!?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值