C语言数据结构-单链表从零入门:头插法、尾插法、插入删除释放操作详解

        数组因其连续存储和随机访问的特性,成为最基础的数据结构之一。然而,静态分配的局限使其在处理动态数据时面临挑战:预分配的空间难以匹配变化的需求,插入删除操作需要移动大量元素,效率受限。

        于是,链表作为一种更加灵活的数据结构被提出。其中,单链表采用 “节点+指针” ——每个节点独立分配内存,通过指针逻辑相连,形成了既可动态扩展又能高效操作的数据链。完美解决了数组的固有限制,支持快速插入删除操作,并能根据需求灵活调整存储规模。本文将深入探讨单链表的C语言实现,系统解析这一重要数据结构的实现原理与应用技巧。

一、单链表的基本结构

#include <stdio.h>       // 包含标准输入输出库(用于printf、scanf)
#include <stdlib.h>      // 包含内存管理库(用于malloc、free)

//定义单链表节点结构体
typedef struct Node
{
    int data;             //数据域
    struct Node *next;    //指针域 
}Node,*Link;              //给结构体取别名:Node(结点类型)、Link(结点指针类型)

二、头插法

        每次创建新节点,都插在“头部”,使链表逆序。

// 头插法创建单链表
Link createListHead(int n) {
    Link head = NULL;  // 定义头指针,初始时链表为空
    Link p;            // 用于指向新创建的节点
    int x;             // 临时存储输入数据

    for (int i = 0; i < n; i++) {
        printf("请输入第 %d 个元素:", i + 1);
        scanf("%d", &x);

        // 创建新节点
        p = (Link)malloc(sizeof(Node));  // 动态申请内存
        p->data = x;    // 给新节点赋值

        // 插入到头部
        p->next = head;  
        head = p;      //新头指针,让新节点成为新的“头”
    }
    return head;
}

三、尾插法

        每次创建新节点,都插到“尾部”,链表保持正序。

// 尾插法创建单链表 
Link createListTail(int n) {
    Link head = NULL;  // 链表头指针
    Link tail = NULL;  // 尾指针,用来记录最后一个节点
    Link p;            // 新节点指针
    //上面3条可以简写成:Link head = NULL,tail = NULL,p;
    int x;

    for (int i = 0; i < n; i++) {
        printf("请输入第 %d 个元素:", i + 1);
        scanf("%d", &x);

        // 创建新节点
        p = (Link)malloc(sizeof(Node));
        p->data = x;
        p->next = NULL;       // 新节点默认没有后继节点

        // 判断是否是第一个节点
        if (head == NULL) {
            head = p;         // 第一个节点既是头又是尾
            tail = p;
        } else {      //不是第一个,则插入到尾部
            tail->next = p;  // 旧尾节点的 next 指向新节点
            tail = p;        // 更新尾指针为新节点
        }
    }
    return head;
}

四、遍历打印

//遍历打印单链表
void printList(Link head) {
    Link p = head;   // 从头节点开始遍历
    while (p != NULL) {
        printf("%d -> ", p->data);
        p = p->next;
    }
    printf("NULL\n");  // 末尾指示结束
}

五、删除函数

         [先存后删]——分为两种情况:

                1.删除的是头节点

                2.删除的是中间/结尾节点

// 按值删除结点
Link delete_list(Link head, int key) {    //Link head——删哪里, int key——删哪个
    Link p = NULL;     // p:当前遍历的结点指针
    Link q = NULL;     // q:p的前驱结点指针(用于“跳过”p)
    
    p = head;          // p初始化为头指针,从第一个结点开始遍历
    while (1) {        // 无限循环,找到目标后用break退出
        if (head == NULL) {       // 处理空链表场景
            printf("链表为空,无结点可删除!\n");
            break;
        }
        // 场景1:头结点就是要删除的结点
        if (head->data == key) {  
            head = head->next;    // 头指针指向原头结点的下一个结点
            free(p);              // 释放原头结点的内存(避免内存泄漏)
            printf("已删除数为%d的结点!\n", key);
            break;                // 找到并删除,退出循环
        } else {
            q = p;                // q跟上p(记录p的前驱)
            p = p->next;          // p指向下一个结点,继续遍历
            if (p == NULL) {      // 遍历到末尾未找到
                printf("未找到数为%d的结点!\n", key);
                break;
            }
            // 场景2:中间或尾结点是要删除的结点
            if (p->data == key) {
                q->next = p->next; // 让q的next指向p的下一个结点(跳过p)
                free(p);          // 释放p的内存
                printf("已删除数为%d的结点!\n", key);
                break;            // 找到并删除,退出循环
            }
        }
    }
    return head;       // 返回删除后的链表头指针
}

六、插入函数

        [先连后断]——在指定值的节点后面插入新节点

// 插入函数
Link insert_List(Link head, Link pnew, int key)
{
    Link p = NULL;
    p = head;

    while(p != NULL)  //遍历到末尾停止
    {
        if(p == NULL){
            // 情况1:链表为空或没找到key,插入到头部
            pnew->next = head;
            head = pnew;
            printf("链表为空或未找到%d,新节点插入到头部\n", key);
            break;
        }
        if(p->data == key){ 
            // 情况2:找到key,插入到该节点后面
            pnew->next = p->next;
            p->next = pnew;
            printf("在值为%d的节点后面插入新节点\n", key);
            break;
        }
        p = p->next;
    }
    return head;
}

七、释放内存

        [先存后移]——每次malloc后一定要free,否则会内存泄漏。

// 释放链表内存
void freeList(Link head) {
    Link p;
    while (head != NULL) {
        p = head;          // 暂存当前节点
        head = head->next; // 移动到下一个节点
        free(p);           // 释放当前节点
    }
}

八、主函数main入口

// 主函数
int main() {
    int n,key,choice,value;
    Link list1, list2;

    printf("请输入链表长度 n: ");
    scanf("%d", &n);

    printf("\n=== 使用头插法创建链表 ===\n");
    list1 = createListHead(n);
    printList(list1);

    printf("\n=== 使用尾插法创建链表 ===\n");
    list2 = createListTail(n);
    printList(list2);

    // 插入操作演示
    printf("\n=== 插入操作演示 ===\n");
    
    // 选择要操作哪个链表
    printf("请选择要操作的链表:\n");
    printf("1.头插法链表\n");
    printf("2.尾插法链表\n");
    printf("选择1或2:");
    scanf("%d", &choice);
    
    if (choice == 1 || choice == 2) {
        // 输入插入信息
        printf("请输入要插入的新值:");
        scanf("%d", &value);
        printf("请输入在哪个值后面插入(如果要插入到头部,输入一个不存在的值):");
        scanf("%d", &key);
        
        // 创建新节点
        Link newNode = (Link)malloc(sizeof(Node));
        newNode->data = value;
        newNode->next = NULL;
        
        if (choice == 1) {
            printf("\n插入前链表:");
            printList(list1);
            list1 = insert_List(list1, newNode, key);
            printf("插入后链表:");
            printList(list1);
        } else {
            printf("\n插入前链表:");
            printList(list2);
            list2 = insert_List(list2, newNode, key);
            printf("插入后链表:");
            printList(list2);
        }
    } else {
        printf("选择错误!\n");
    }

    // 删除操作
    printf("\n=== 删除操作 ===\n");
    
    // 选择要操作哪个链表
    printf("请选择要操作的链表:\n");
    printf("1.头插法\n");
    printf("2.尾插法\n");
    printf("选择1或2:");
    scanf("%d", &choice);

    // 输入要删除的值
    printf("请输入要删除的值:");
    scanf("%d", &key);
    
    // 执行删除操作
    if (choice == 1) {
        printf("删除前链表:");
        printList(list1);
        list1 = delete_list(list1, key);  // 对list1进行删除
        printf("删除后链表:");
        printList(list1);
    } 
    else if (choice == 2) {
        printf("删除前链表:");
        printList(list2);
        list2 = delete_list(list2, key);  // 对list2进行删除
        printf("删除后链表:");
        printList(list2);
    } 
    else {
        printf("选择错误!\n");
    }

    // 释放内存
    freeList(list1);
    freeList(list2);

    return 0;
}

九、运行结果

        注意:理解链表的关键是多多画图

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值