当前位置: 首页 > news >正文

单链表专题(C语言)

文章目录

    • 前言
    • 一、链表的概念和结构
      • 链表的特点
      • 链表的结构示意
    • 二、实现单链表
    • 三、链表的分类
    • 总结

前言

链表是一种最基本、最常用的数据结构之一。相较于数组,链表在动态内存分配、插入、删除等操作上具有天然的优势。C 语言作为系统级编程语言,常常需要直接管理内存,通过链表来动态存储和操作数据也成为了不少工程项目中的必备技能。本文将通过详细讲解和完整代码示例,帮助读者理解和实现一个简单的单链表。

在这里插入图片描述


一、链表的概念和结构

链表(Linked List)是一种由节点构成的数据结构,每个节点包含存储数据以及指向下一个节点的指针(或引用)。与数组连续的内存区域不同,链表的节点分散存储,通过指针将它们串联起来,形成一个线性结构。

链表的特点

  • 动态内存分配:链表在程序运行时动态创建,无需提前确定大小。
  • 高效的插入和删除:在已知节点位置的前提下,插入或删除操作只涉及指针调整,不需要移动大量数据。
  • 非随机访问:链表不能像数组那样通过下标直接访问某个元素,必须从头节点开始依次查找。

链表的结构示意

假设有三个节点组成的链表,结构图如下:

[数据 | 指针] -> [数据 | 指针] -> [数据 | 指针] -> NULL
  • 每个节点中的 数据 用于存储具体信息,指针 指向下一个节点。
  • 最后一个节点的指针指向 NULL,表示链表的结束。

二、实现单链表

sequentialtable.h

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

//定义顺序表结构

//#define N 1000
静态顺序表
//struct sqllist
//{
//	int arr[N];
//	int size;
//};

//动态顺序表

typedef int type;
typedef struct sqlList
{
	type* arr;
	int size;
	int capacity;
}SL;

//初始化顺序表
void SLInit(SL* p);

//销毁顺序表
void DeleteSL(SL* p);

//检查是否有还有空间
void CapacityCheak(SL* p);

//尾插
void PushBack(SL* p, type x);

//头插
void PushFront(SL* p, type x);

//头删
void PopFront(SL* p);

//尾删
void PopBack(SL* p);

//在指定位置之前插入数据
void SLInsert(SL* p, int pos, type x);

//删除指定位置的内容
void SLErase(SL* p, int pos);

//查询顺序表
int SLFind(SL* p, type x);

//打印
void Print(SL* p);




sequentialtable.c

#define _CRT_SECURE_NO_WARNINGS 1
#include "sequentialtable.h"  // 包含顺序表数据结构及相关类型的头文件

/*
 * 函数:SLInit
 * 作用:初始化顺序表,将数组指针置为空,同时将大小和容量都设置为 0
 * 参数:SL* p —— 指向顺序表结构的指针
 */
void SLInit(SL* p)
{
    p->arr = NULL;            // 初始化数组为空
    p->size = p->capacity = 0;  // 初始化大小和容量为 0
}

/*
 * 函数:DeleteSL
 * 作用:销毁顺序表,释放数组内存,并重置顺序表的大小与容量
 * 参数:SL* p —— 指向顺序表结构的指针
 */
void DeleteSL(SL* p)
{
    if (p->arr)              // 如果数组不为空,则释放内存
    {
        free(p->arr);
    }

    p->arr = NULL;           // 重置数组指针为空
    p->size = p->capacity = 0;  // 重置大小和容量为 0
}

/*
 * 函数:CapacityCheak
 * 作用:检查顺序表是否有足够空间存储新元素,如果空间不足则扩容
 * 参数:SL* p —— 指向顺序表结构的指针
 * 说明:
 *   - 若 size 等于 capacity,说明数组已满,需要重新分配内存
 *   - 新容量:如果原容量为 0,则设为4;否则扩为原来的2倍
 */
void CapacityCheak(SL* p)
{
    // 判断当前存储元素个数是否已达到容量
    if (p->size == p->capacity)
    {
        int newCapacity = p->capacity == 0 ? 4 : 2 * p->capacity;
        // 重新分配内存
        type* tmp = (type*)realloc(p->arr, newCapacity * sizeof(type));
        if (tmp == NULL)
        {
            // 内存申请失败,输出错误信息并退出程序
            perror("realloc");
            exit(1);
        }
        // 内存申请成功,更新数组指针与容量
        p->arr = tmp;
        p->capacity = newCapacity;
    }
}

/*
 * 函数:PushBack
 * 作用:在顺序表尾部追加一个新元素
 * 参数:
 *   SL* p —— 指向顺序表结构的指针
 *   type x —— 要插入的元素
 * 说明:
 *   - 首先检查顺序表是否有足够空间,否则扩容
 *   - 将元素存入数组最后位置,再更新元素个数
 */
void PushBack(SL* p, type x)
{
    assert(p);             // 确保传入的顺序表指针不为 NULL

    CapacityCheak(p);      // 检查是否需要扩容

    p->arr[p->size++] = x; // 插入新元素,并更新顺序表的 size
}

/*
 * 函数:PushFront
 * 作用:在顺序表前端插入一个新元素
 * 参数:
 *   SL* p —— 指向顺序表结构的指针
 *   type x —— 要插入的元素
 * 说明:
 *   - 插入新元素前,需要将原有元素全部向后移动1位
 *   - 插入后,顺序表 size 加 1
 */
void PushFront(SL* p, type x)
{
    assert(p);             // 确保传入的顺序表指针不为 NULL
    CapacityCheak(p);      // 检查是否需要扩容

    // 将所有元素向后移动一位,以腾出位置给新元素
    for (int i = p->size; i > 0; i--)
    {
        p->arr[i] = p->arr[i - 1];
    }

    p->arr[0] = x;         // 在数组第一个位置插入新元素
    ++p->size;             // 更新顺序表的 size
}

/*
 * 函数:PopFront
 * 作用:删除顺序表前端的元素
 * 参数:SL* p —— 指向顺序表结构的指针
 * 说明:
 *   - 删除操作将所有后续元素依次前移一位
 *   - 顺序表 size 减 1
 */
void PopFront(SL* p)
{
    assert(p);             // 确保顺序表指针不为 NULL
    assert(p->arr);        // 确保数组已经被分配

    // 将每个元素向前移动一位,覆盖掉第一个元素
    for (int i = 0; i < p->size - 1; i++)
    {
        p->arr[i] = p->arr[i + 1];
    }
    --p->size;             // 更新顺序表的 size
}

/*
 * 函数:PopBack
 * 作用:删除顺序表尾部的元素
 * 参数:SL* p —— 指向顺序表结构的指针
 * 说明:
 *   - 直接将 size 减 1,不需要移动元素
 */
void PopBack(SL* p)
{
    assert(p);             // 确保顺序表指针不为 NULL
    assert(p->arr);        // 确保数组已经被分配

    --p->size;             // 更新顺序表的 size,尾部元素自动舍弃
}

/*
 * 函数:SLInsert
 * 作用:在顺序表的指定位置 pos 插入新元素 x
 * 参数:
 *   SL* p —— 指向顺序表结构的指针
 *   int pos —— 插入位置(索引),范围 0 到 size 均有效
 *   type x —— 要插入的元素
 * 说明:
 *   - 插入前需将 pos 及之后的元素向后移动一个位置
 */
void SLInsert(SL* p, int pos, type x)
{
    assert(p);  // 确保顺序表指针不为 NULL
    assert(pos >= 0 && pos <= p->size);  // 检查 pos 是否在合法范围内

    // 将 pos 后面的所有元素向后移动一位
    for (int i = p->size; i > pos; i--)
    {
        p->arr[i] = p->arr[i - 1];
    }
    p->arr[pos] = x;  // 将新元素存入 pos 位置
    ++p->size;        // 更新顺序表的 size
}

/*
 * 函数:SLErase
 * 作用:删除顺序表中指定位置 pos 的元素
 * 参数:
 *   SL* p —— 指向顺序表结构的指针
 *   int pos —— 要删除的元素位置(索引)
 * 说明:
 *   - 删除后,将 pos 后面的元素向前移动一位
 */
void SLErase(SL* p, int pos)
{
    assert(p);                    // 确保顺序表指针不为 NULL
    assert(pos >= 0 && pos <= p->size);  // 检查 pos 是否在合法范围内

    // 从 pos 开始,将后面的元素前移一位覆盖 pos 元素
    for (int i = pos; i < p->size - 1; i++)
    {
        p->arr[i] = p->arr[i + 1];
    }
    --p->size;                    // 更新顺序表的 size
}

/*
 * 函数:SLFind
 * 作用:在顺序表中查找元素 x
 * 参数:
 *   SL* p —— 指向顺序表结构的指针
 *   type x —— 要查找的元素
 * 返回值:
 *   查找到则返回 x(也可以返回索引更好),否则返回 -1 表示没有找到
 * 说明:
 *   - 此处返回 x 值不是很直观,通常建议返回元素的索引或指针
 */
int SLFind(SL* p, type x)
{
    assert(p);  // 确保顺序表指针不为 NULL

    for (int i = 0; i < p->size; i++)
    {
        if (p->arr[i] == x)
        {
            return x;  // 如果找到匹配元素,返回 x(建议返回 i 或者 p->arr[i] 的地址)
        }
    }
    return -1;     // 没有找到返回 -1
}

/*
 * 函数:Print
 * 作用:打印顺序表中所有元素
 * 参数:SL* p —— 指向顺序表结构的指针
 * 说明:
 *   - 遍历数组并依次打印每个元素,用空格分隔,最后换行
 */
void Print(SL* p)
{
    for (int i = 0; i < p->size; i++)
    {
        printf("%d ", p->arr[i]);  // 打印数组中每个元素
    }
    printf("\n");  // 输出换行,结束打印
}


三、链表的分类

在数据结构中,除了单链表外,还有其他几种链表,每种在不同场景下有其优势:

  • 单链表:每个节点仅包含一个指针,指向下一个节点。结构简单,适用于大多数基本应用,但只能单向遍历。
  • 双向链表:每个节点包含两个指针,一个指向前一个节点,一个指向后一个节点。能实现双向遍历,但占用更多内存。
  • 循环链表:链表的尾节点指针指向头节点,形成一个闭环,适用于实现循环调度或需要循环遍历的场景。

总结

本文通过使用 C 语言详细介绍了单链表这一基本数据结构的概念、结构及其实现方法。主要内容包括:

  • 链表的基本概念:了解链表如何通过节点和指针构成灵活的数据结构。
  • C 语言实现:包括节点的定义、头插法和尾插法插入、删除节点及遍历链表的常用操作,配合完整代码示例帮助实践。
  • 链表的分类:简单介绍了单链表、双向链表与循环链表的区别和应用场景。

相关文章:

  • 基于SpringBoot的电影订票系统(源码+数据库+万字文档+ppt)
  • 架构师面试(三十):IM 分层架构
  • 架构生命周期(高软57)
  • CSS padding(填充)学习笔记
  • C# Winform 入门(16)之图片合成
  • Linux--线程概念与控制
  • 突破边界:从 C# 到 Python 的范式跃迁与实战指南
  • 图像分割基础学习
  • vLLM部署Qwen2.5-Omni 提供API的详细步骤
  • CSE lesson2 chrony服务器
  • CSS margin(外边距)学习笔记
  • Redash 25.1.0 简配部署
  • vscode中gcc编译器中文路径调试成功方法
  • 免费送源码:Java+SpringBoot+MySQL SpringBoot网上宠物领养管理系统 计算机毕业设计原创定制
  • zk源码—7.ZAB协议和数据存储一
  • 五子棋(测试报告)
  • Web前端之Vue+Element实现表格动态复杂的合并行功能、localeCompare、forEach、table、push、sort、Map
  • JWT认证服务与授权 .netCore
  • pywebview 常用问题分享
  • HashTable,HashMap,ConcurrentHashMap之间的区别
  • 上海:以税务支持鼓励探索更多的创新,助力企业出海
  • 韩国检方结束对尹锡悦私宅的扣押搜查
  • 4月译著联合书单|心爱之物:热爱如何联结并塑造我们
  • 游客曝九寨沟打网约车被出租车围堵,景区回应:当地无合规网约车
  • 呼伦贝尔市委常委、组织部长闫轶圣调任内蒙古交通集团党委副书记
  • 阿里千问3系列发布并开源:称成本大幅下降,性能超越DeepSeek-R1