手动实现单链表操作

手动实现单链表操作

一、头文件:Linked_list.h

#ifndef LINKED_LIST_H  // 头文件保护宏,防止重复包含
#define LINKED_LIST_H

// 包含常用头文件
#include <stdbool.h>  // 布尔类型
#include <stdlib.h>   // 内存管理函数(malloc/calloc/free)
#include <stdio.h>    // 输入输出函数

// 定义数据类型别名(方便后续修改数据类型)
typedef int DataType;

// 定义链表结点结构
typedef struct node {
    DataType data;      // 数据域,存储具体数据
    struct node *next;  // 指针域,指向下一个结点
} Node;

// 定义链表结构体(管理链表整体信息)
typedef struct {
    Node *head;     // 头指针,指向链表第一个结点
    Node *tail;     // 尾指针,指向链表最后一个结点
    int size;       // 链表长度(结点个数)
} LinkedList;

// 函数声明列表

// 创建空链表
// 返回值:指向新创建链表的指针
LinkedList *create_linked_list();

// 销毁链表(释放所有内存)
// 参数:链表指针
void destroy_linked_list(LinkedList *list);

// 头插法:在头部插入新结点
// 参数:链表指针、新数据
// 返回值:插入成功与否(布尔值)
bool add_before_head(LinkedList *list, DataType new_data);

// 尾插法:在尾部插入新结点
// 参数:链表指针、新数据
// 返回值:插入成功与否(布尔值)
bool add_behind_tail(LinkedList *list, DataType new_data);

// 按索引插入结点(0-based索引)
// 参数:链表指针、索引、新数据
// 返回值:插入成功与否(布尔值)
bool add_by_idx(LinkedList *list, int idx, DataType new_data);

// 按索引搜索结点
// 参数:链表指针、索引
// 返回值:目标结点指针(失败返回NULL)
Node *search_by_idx(LinkedList *list, int idx);

// 按数据值搜索结点(返回首个匹配结点)
// 参数:链表指针、目标数据
// 返回值:目标结点指针(失败返回NULL)
Node *search_by_data(LinkedList *list, DataType data);

// 按数据值删除首个匹配结点
// 参数:链表指针、目标数据
// 返回值:删除成功与否(布尔值)
bool delete_by_data(LinkedList *list, DataType data);

// 扩展:按索引删除结点(0-based索引)
// 参数:链表指针、索引
// 返回值:删除成功与否(布尔值)
bool delete_by_idx(LinkedList *list, int idx);

// 打印链表内容(格式:1 -> 2 -> 3 -> ...)
// 参数:链表指针
void print_list(LinkedList *list);

#endif // !LINKED_LIST_H
#pragma once  // 替代头文件保护宏的另一种方式(部分编译器支持)

二、实现文件:linked_list.c

#include "Linked_list.h"  // 包含自定义头文件

// 创建空链表
// 功能:分配链表结构体内存,初始化头/尾指针和长度
LinkedList *create_linked_list() {
    // calloc自动初始化内存为0,链表初始时head/tail为NULL,size为0
    return calloc(1, sizeof(LinkedList));
}

// 销毁链表
// 功能:释放所有结点内存,再释放链表结构体内存
void destroy_linked_list(LinkedList *list) {
    Node *curr = list->head;  // 从头部开始遍历
    while (curr != NULL) {     // 循环直到所有结点处理完毕
        Node *temp = curr->next;  // 暂存下一个结点指针
        free(curr);              // 释放当前结点内存
        curr = temp;             // 移动到下一个结点
    }
    free(list);  // 释放链表结构体内存
}

// 打印链表
// 功能:按格式输出链表所有结点数据
void print_list(LinkedList *list) {
    Node *curr = list->head;  // 从头部开始遍历
    while (curr != NULL) {
        printf("%d", curr->data);  // 输出当前数据
        if (curr->next != NULL) {   // 非最后一个结点时添加箭头
            printf("->");
        }
        curr = curr->next;  // 移动到下一个结点
    }
    printf("\n");  // 换行
}

// 头插法实现
bool add_before_head(LinkedList *list, DataType new_data) {
    Node *new_node = (Node *)malloc(sizeof(Node));  // 分配新结点内存
    if (new_node == NULL) {                         // 内存分配失败处理
        printf("内存分配失败");
        exit(-1);  // 终止程序(实际开发中可返回错误码)
    }
    new_node->data = new_data;       // 赋值数据域
    new_node->next = list->head;     // 新结点的next指向原头部
    list->head = new_node;           // 头指针指向新结点
    // 处理空链表情况:插入后尾指针也指向新结点
    if (list->tail == NULL) {        
        list->tail = new_node;
    }
    list->size++;  // 长度+1
    return true;
}

// 尾插法实现
bool add_behind_tail(LinkedList *list, DataType new_data) {
    Node *new_node = malloc(sizeof(Node));  // 分配新结点内存
    if (new_node == NULL) {
        printf("内存分配失败");
        exit(-1);
    }
    new_node->data = new_data;       // 赋值数据域
    new_node->next = NULL;           // 尾结点的next必须为NULL
    // 处理空链表情况:头/尾指针均指向新结点
    if (list->tail == NULL) {        
        list->head = new_node;
        list->tail = new_node;
    } else {                        // 非空链表:原尾结点的next指向新结点,更新尾指针
        list->tail->next = new_node;
        list->tail = new_node;
    }
    list->size++;  // 长度+1
    return true;
}

// 按索引插入实现
bool add_by_idx(LinkedList *list, int idx, DataType new_data) {
    // 索引合法性检查(idx范围:0 ~ size)
    if (idx < 0 || idx > list->size) {     
        printf("输入的索引错误\n");
        return false;
    }
    // 特殊情况:索引为0(头插法)或索引为size(尾插法)
    if (idx == 0) {
        return add_before_head(list, new_data);
    }
    if (idx == list->size) {
        return add_behind_tail(list, new_data);
    }
    // 普通情况:找到索引idx-1的结点(前驱结点)
    Node *new_node = malloc(sizeof(Node));  // 分配新结点内存
    if (new_node == NULL) {
        printf("内存分配失败");
        return false;
    }
    new_node->data = new_data;       // 赋值数据域
    Node *prev = list->head;         // 从头部开始遍历
    // 移动prev到idx-1的位置
    for (int i = 0; i < idx - 1; i++) {  
        prev = prev->next;
    }
    // 插入逻辑:新结点的next指向prev的下一个结点,prev的next指向新结点
    new_node->next = prev->next;     
    prev->next = new_node;
    list->size++;  // 长度+1
    return true;
}

// 按索引搜索实现
Node *search_by_idx(LinkedList *list, int idx) {
    // 索引合法性检查(idx范围:0 ~ size-1)
    if (idx < 0 || idx > list->size - 1) {  
        printf("索引不合法");
        return NULL;
    }
    Node *curr = list->head;  // 从头部开始遍历
    // 移动curr到idx的位置
    for (int i = 0; i < idx; i++) {  
        curr = curr->next;
    }
    return curr;  // 返回目标结点
}

// 按数据值搜索实现(返回首个匹配结点)
Node *search_by_data(LinkedList *list, DataType data) {
    Node *curr = list->head;  // 从头部开始遍历
    while (curr != NULL) {     // 遍历直到链表末尾
        if (curr->data == data) {  // 匹配成功,返回当前结点
            return curr;
        }
        curr = curr->next;  // 移动到下一个结点
    }
    return NULL;  // 未找到匹配结点
}

// 按数据值删除实现(删除首个匹配结点)
bool delete_by_data(LinkedList *list, DataType data) {
    Node *curr = list->head;  // 当前结点
    // 处理头结点匹配的情况
    if (curr != NULL && curr->data == data) {  
        list->head = curr->next;  // 头指针指向下一个结点
        if (list->head == NULL) {  // 删除后链表为空,更新尾指针
            list->tail = NULL;
        }
        free(curr);  // 释放头结点内存
        list->size--;  // 长度-1
        return true;
    }
    // 查找后续结点中的匹配项
    Node *prev = curr;       // 前驱结点
    curr = curr->next;       // 当前结点后移一位
    while (curr != NULL) {
        if (curr->data == data) {  // 找到匹配结点
            prev->next = curr->next;  // 前驱结点跳过当前结点
            if (curr->next == NULL) {  // 删除的是尾结点,更新尾指针
                list->tail = prev;
            }
            free(curr);  // 释放当前结点内存
            list->size--;  // 长度-1
            return true;
        }
        prev = curr;       // 前驱后移
        curr = curr->next; // 当前后移
    }
    return false;  // 未找到匹配结点
}

// 按索引删除实现
bool delete_by_idx(LinkedList *list, int idx) {
    // 索引合法性检查(idx范围:0 ~ size-1)
    if (idx < 0 || idx >= list->size) {  
        printf("索引错误");
        return false;
    }
    Node *curr = list->head;  // 当前结点
    Node *prev = NULL;        // 前驱结点
    // 处理删除头结点的情况(idx=0)
    if (idx == 0) {  
        list->head = curr->next;  // 头指针指向下一个结点
        if (list->size == 1) {    // 删除后链表为空,更新尾指针
            list->tail = NULL;
        }
        free(curr);  // 释放头结点内存
        list->size--;  // 长度-1
        return true;
    }
    // 查找索引idx的结点(移动curr到idx位置,prev到idx-1位置)
    for (int i = 0; i < idx; i++) {  
        prev = curr;
        curr = curr->next;
    }
    prev->next = curr->next;  // 前驱结点跳过当前结点
    if (idx == list->size - 1) {  // 删除的是尾结点,更新尾指针
        list->tail = prev;
    }
    free(curr);  // 释放当前结点内存
    list->size--;  // 长度-1
    return true;
}

三、主函数:main.c

#define _CRT_SECURE_NO_WARNINGS  // 禁用VS编译器的安全警告(可选)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 计算数组长度的宏(方便数组操作)
#define ARR_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
#include "Linked_list.h"  // 包含链表头文件

int main(void) {
    // 1. 创建链表
    LinkedList *list = create_linked_list();

    // 2. 头插法测试
    add_before_head(list, 10);
    add_before_head(list, 20);
    add_before_head(list, 30);
    printf("在头部插入30, 20, 10后:\n");
    print_list(list);  // 输出:30->20->10

    // 3. 尾插法测试
    add_behind_tail(list, 40);
    add_behind_tail(list, 50);
    printf("在尾部插入40, 50后:\n");
    print_list(list);  // 输出:30->20->10->40->50

    // 4. 按索引插入测试(索引2插入25)
    add_by_idx(list, 2, 25);
    printf("在索引2插入25后:\n");
    print_list(list);  // 输出:30->20->25->10->40->50

    // 5. 按索引搜索测试(获取索引2的结点)
    Node *node = search_by_idx(list, 2);
    printf("索引2处的元素: %d\n", node->data);  // 输出:25

    // 6. 按数据值搜索测试(查找值为40的结点)
    node = search_by_data(list, 40);
    printf("找到数据40的结点: %d\n", node->data);  // 输出:40

    // 7. 按数据值删除测试(删除值为25的结点)
    delete_by_data(list, 25);
    printf("删除数据25后:\n");
    print_list(list);  // 输出:30->20->10->40->50

    // 8. 按索引删除测试(删除索引0的结点,即30)
    delete_by_idx(list, 0);
    printf("删除索引0处的元素后:\n");
    print_list(list);  // 输出:20->10->40->50

    // 9. 销毁链表(释放内存)
    destroy_linked_list(list);

    return 0;
}

四、关键知识点总结

  1. 链表结构

    • 每个结点包含数据域和指针域(指向下一个结点)。
    • 链表结构体管理头指针、尾指针和长度,方便快速操作头尾结点。
  2. 内存管理

    • calloc用于创建链表结构体(自动初始化内存为0)。
    • malloc用于分配结点内存,需手动初始化next指针(尾插法中需设为NULL)。
    • 销毁链表时需先释放所有结点内存,再释放链表结构体内存,避免内存泄漏。
  3. 核心操作

    • 头插法:时间复杂度 O(1),更新头指针和尾指针(空链表时)。
    • 尾插法:时间复杂度 O(1)(利用尾指针),需处理空链表情况。
    • 按索引插入/删除:需先找到前驱结点,时间复杂度 O(n)(n为索引值)。
    • 搜索操作:按索引搜索 O(n),按值搜索 O(n)(需遍历链表)。
  4. 边界处理

    • 插入/删除时需检查索引合法性(如idx=0idx=size的情况)。
    • 空链表或单个结点时,头指针和尾指针需同步更新(如删除头结点后若链表为空,尾指针需置NULL)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值