C语言数据结构链表(图文)

简介: 关于链表:以下文章仅仅代表我自己的看法,如果哪有不对还请各位大佬批评指出问题所在,互相交流,互相学习!!!链表与顺序表的区别是顺序表在物理空间上和逻辑上都是连续的,而对于链表来说,链表在物理上不一定连续,因为其保存的元素是空间通过malloc从堆空间上申请出来的,而malloc在一次中申请多个空间是连续的,如果是多次申请的话就不是连续的。但是!!链表在逻辑上是连续的。

一、链表的简单理解与引入

1.1    链表的引入    

    首先!!!链表与顺序表的区别是顺序表在物理空间上和逻辑上都是连续的,而对于链表来说,链表在物理上不一定连续,因为其保存的元素是空间通过malloc从堆空间上申请出来的,而malloc在一次中申请多个空间是连续的,如果是多次申请的话就不是连续的。但是!!链表在逻辑上是连续的。


1.2     节点的理解

     在链表中,用来保存元素的一段空间(这里边有俩个元素,一个是这个元素的值,一个是下一个元素的空间地址)。微信图片_20230516151318.png

1.3 链表的分类

       链表主要有单向和双向、带头和不带头、循环和不循环三类,这三类通过组合就产生了8中链表情况。

1.单向或者双向

微信图片_20230516151503.png

2.带头或者不带头

微信图片_20230516151514.png

3.循环或者非循环

微信图片_20230516151645.png

虽然但是有这么多链表,我们最最最最常用的就是“无头单向非循环链表”和“带头双向循环链表两种”!!!!大家只要把这俩种弄懂,那么其他的也就差不多了

 无头单向非循环链表:

微信图片_20230516151744.png

带头双向循环链表

微信图片_20230516151821.png

注:无头单向非循环链表结构简单,一般不会单独用来存储数据,但是它会跟随其他的结构,作为它的子结构,这种链表种类在面试中考的比较多。而带头双向循环链表在实际中使用的比较多一些,它结构比较复杂,但是优势也是比较大的。


二、常用链表功能的实现

2.1  节点的定义

   链表嘛,就是一个个节点串起来的。如2.1所见,节点就是俩个变量,一个是值域,一个是下一个节点的地址,我们只需要把它定义出来就行!!!如:

typedef int DataType;//这里是吧变量类型重新命名了,方便修改
typedef struct SListNode//这个是节点的定义
{
  DataType data;
  struct SListNode* next;
}SListNode;           //这里为它重新命名了,方便后边使用,否则每次都得加上struct

2.2  节点的创建

 这里没啥难的,大家可以去看看malloc的用法。

SListNode* BuySListNode(DataType x) {
    SListNode*  newnode = (SListNode*)malloc(sizeof(SListNode));//这里是给这个新的节点在堆上申请了一处空间
    if (newnode == NULL)                   //这个就是申请空间失败了,即节点创建失败
    {
        printf("创建节点失败!!");
        exit(0);                         
    }
    newnode->data = x;                 //将参数x保存到值域
    newnode->next = NULL;              //在没有串起来的时候,将下一个地址保存为NULL
    return newnode;                    //将新创建的节点保存起来
}

2.3    单链表的尾插

尾插说白了就是找到最后一个元素,然后将它指向我们要插入的这个节点就行。但是注意要分情况,链表为空和链表不为空


assert: assert是一个调试宏,即assert是一个宏,这个宏只在debug模式下有效assert是断言,assert(表达式),表达式的结果是真或者假;如果assert内部的表达式为真,assert就不会触发,程序可以继续往下执行如果assert内部的表达式为假,assert就触发,assert就会中断程序的执行---弹出一个窗口因此:assert是用来断言非法情况的,即当assert触发了之后,程序一定是处于非法的状态if(表达式):当表达式为假,程序处于合法的情况,就使用if来检测条件链表不存在:根本就没有链表,就不能对链表进行任何操作 ----当往链表中插入或者删除元素时候,将链表不存在视为非法情况空链表:链表中没有插入任何节点---合法情况


*/

//单链表的尾插
void SListPushBack(SListNode** pplist, DataType x)
{
    assert(pplist);              //保证品牌pplist这个链表存在
    if (*pplist == NULL)         //如果链表首地址为空,说明这块插入的节点就充当这个单链表的第一个元素
        *pplist = BuySListNode(x);
    else {                       //如果链表中还有其他的元素,这时候需要找到链表的最后一个元素,将他的next地址指向我们插入的这个节点
        SListNode* cur = *pplist;       //要进行遍历,故构建了一个相同类型的变量
        while (cur->next)           //如果是最后一个元素,这个元素的下一个就会为空,跳出循环
        {
            cur = cur->next;        //相当数组中的i++
        }
        cur->next = BuySListNode(x);   //跳出循环说明他是最后一个元素了。将它指向我们要插入的元素就行啦
    }
}

2.4  单链表的尾删

找到最后一个元素,让他的上一个元素指向NULL,注意要释放空间(free)。有两个注意,一个是注意控制最后一个元素和倒数第二个元素的移动,第二个是注意分类,下面代码的第二个else和第三个else可以直接合并为第三个一种情况。

// 单链表的尾删
void SListPopBack(SListNode** pplist)
{
    assert(pplist);
    if (*pplist == NULL)
        return;
    else if((*pplist)->next==NULL) {    //若链表只有一个元素的时候,将他释放掉,再讲链表的首地址指向NULL,否则会造成野指针的产生
        free(*pplist);
        *pplist = NULL;
    }
    else {
        SListNode* cur = *pplist;      //用来遍历数组
        SListNode* prev = NULL;       //保存上一个元素的地址
        while (cur->next)
        {
            prev = cur;
            cur = cur->next;
        }                           //当cur为最后一个的同时,prev刚好是倒数第二个,想不来的用
        free(cur);                  //123456自己推一下就懂啦
        prev->next = NULL;
    }
}

2.5  单链表的头插

注意最后更新*pplist的值

// 单链表的头插
void SListPushFront(SListNode** pplist, DataType x)
{
    assert(pplist);
    if ((*pplist) == NULL)
    {
        *pplist = BuySListNode(x);
    }
    SListNode* newnode = BuySListNode(x);
    newnode->next = *pplist;
    *pplist = newnode;
}

2.6  链表的头删

注意:每个删除都需要提前保存这个节点,然后将这个节点释放

// 单链表头删
void SListPopFront(SListNode** pplist)
{
    assert(pplist);
    if ((*pplist) == NULL)
    {
        return;
    }
    SListNode* cor = *pplist;
    *pplist = cor->next;
    free(cor);
}

2.7  单链表的查找

这个和数组的查找没啥区别,主要变通就行

// 单链表查找
SListNode* SListFind(SListNode* plist, DataType x)
{
    assert(plist);
    if (plist == NULL)
    {
        return NULL;
    }
    SListNode* cur = plist;
    while (cur)
    {
        if (cur->data == x)
        {
            return cur;
        }
        cur = cur->next;
    }
    return NULL;
}

2.8  删除pos后边的值

注意条件与地址保存释放就行

//删除pos之后的值
void SListEraseAfter(SListNode* pos) {
    if (pos == NULL || pos->next == NULL)    //如果pos是空或者他是最后一个元素,都不能进行尾删
    {
        return;
    }
    SListNode* cur = pos->next;           //保存next的地址,以便释放
    pos->next = cur->next;
    free(cur);
}

2.9 在元素pos后插入x

这里注意链接链表的先后就行,这样的好处是2的位置不容易遗失

微信图片_20230516152702.png

//在某个元素之后插入X
void SListInsertAfter(SListNode* pos, DataType x) {
    if (pos == NULL)
        return;
    else {
        SListNode* cur = BuySListNode(x);
         cur->next = pos->next;
        pos->next = cur;
    }
}

2.10  链表的销毁

注意点与上一个相同,要先保存下一个元素的地址,要不会造成地址遗失。

//链表的销毁
void SListDestroy(SListNode** plist)
{
    assert(plist);
    SListNode* cur = *plist;
    while (cur)
    {
        *plist = cur->next;
        free(cur);
        cur = *plist;
    }
    plist = NULL;
}

(链表相关oj题解答看下一章)


目录
相关文章
|
26天前
|
算法 数据处理 C语言
C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合
本文深入解析了C语言中的位运算技巧,涵盖基本概念、应用场景、实用技巧及示例代码,并讨论了位运算的性能优势及其与其他数据结构和算法的结合,旨在帮助读者掌握这一高效的数据处理方法。
38 1
|
1月前
|
存储 算法 搜索推荐
【趣学C语言和数据结构100例】91-95
本文涵盖多个经典算法问题的C语言实现,包括堆排序、归并排序、从长整型变量中提取偶数位数、工人信息排序及无向图是否为树的判断。通过这些问题,读者可以深入了解排序算法、数据处理方法和图论基础知识,提升编程能力和算法理解。
46 4
|
1月前
|
存储 机器学习/深度学习 搜索推荐
【趣学C语言和数据结构100例】86-90
本文介绍并用C语言实现了五种经典排序算法:直接插入排序、折半插入排序、冒泡排序、快速排序和简单选择排序。每种算法都有其特点和适用场景,如直接插入排序适合小规模或基本有序的数据,快速排序则适用于大规模数据集,具有较高的效率。通过学习这些算法,读者可以加深对数据结构和算法设计的理解,提升解决实际问题的能力。
43 4
|
1月前
|
存储 算法 数据处理
【趣学C语言和数据结构100例】81-85
本文介绍了五个经典算法问题及其C语言实现,涵盖图论与树结构的基础知识。包括使用BFS求解单源最短路径、统计有向图中入度或出度为0的点数、统计无向无权图各顶点的度、折半查找及二叉排序树的查找。这些算法不仅理论意义重大,且在实际应用中极为广泛,有助于提升编程能力和数据结构理解。
41 4
|
1月前
|
算法 数据可视化 数据建模
【趣学C语言和数据结构100例】76-80
本文介绍了五种图论算法的C语言实现,涵盖二叉树的层次遍历及广度优先搜索(BFS)和深度优先搜索(DFS)的邻接表与邻接矩阵实现。层次遍历使用队列按层访问二叉树节点;BFS利用队列从源节点逐层遍历图节点,适用于最短路径等问题;DFS通过递归或栈深入图的分支,适合拓扑排序等场景。这些算法是数据结构和算法学习的基础,对提升编程能力和解决实际问题至关重要。
51 4
|
1月前
|
存储 算法 vr&ar
【趣学C语言和数据结构100例】71-75
本文介绍了五个C语言数据结构问题及其实现,涵盖链表与二叉树操作,包括按奇偶分解链表、交换二叉树左右子树、查找节点的双亲节点、计算二叉树深度及求最大关键值。通过递归和遍历等方法,解决了理论与实际应用中的常见问题,有助于提升编程能力和数据结构理解。
39 4
|
1天前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
24天前
|
存储 算法 C语言
【C语言】深入浅出:C语言链表的全面解析
链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。
244 6
|
27天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
53 5
|
26天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
58 1