<数据结构> 链表 - 单链表(c语言实现)(二)

简介: <数据结构> 链表 - 单链表(c语言实现)(二)

五、功能的实现


1)打印单链表

//打印 单链表
void SLTPrint(SLTNode* phead);
void SLTPrint(SLTNode* phead)
{
  SLTNode* cur = phead;//①
  while (cur != NULL)//②
  {
  printf("%d -> ",cur->data);
  cur = cur->next;//③
  }
  printf("NULL\n");
}


① 这里也可以直接用phead进行循环,但是像这样创建一个 “当前节点” (cur源自单词current)会比较“美观”。(But 如果这个函数内部后面还需要用头节点的话就不能直接用phead,否则会找不到头)

② 控制结束的条件

③ 遍历


2)创建新节点

链表的结点:按需分配,随用随创

链表的头插、尾插(只要是插入)都需要创建一个新节点,然后插到对应位置。所以我们可以直接把“创建新节点”封装成一个函数,以便后面直接调用:👇

SLTNode* BuySLTNode(SLTDataType x)
{
  SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//①
  if(newnode == NULL)//②
  {
  perror("malloc newnode fail: ");
  return;
  }
  //③给新节点赋值
  newnode->x;
  newnode->next = NULL;
  return newnode;//别忘了返回newnode
}


①动态开辟一个新节点,.h头文件里要包含 <stdlib.h>

②判断开辟是否成功,如果不成功则输出错误并返回

③给新节点的数据域赋值,指针域赋为空:NULL,这样做的好处是: 不需要最后对链表尾结点的指针域置空。


3)尾插

在链表尾部插入一个节点

先看这段代码:

void SLTPushBack(SLTNode** pphead, SLTDataType x);
{
    SLTNode* newnode = BuySLTNode(x);
    SLTNode* tail = *pphead;
    while (tail->next != NULL)
    {
        tail = tail->next;
    }
    tail->next = newnode;
}


上面这段代码的前提是,我们已经假定这个链表有>=1个节点,但是如果*pphead为空呢?

如果为空,则只执行:

SLTNode* newnode = BuySLTNode(x);
SLTNode*tail = *pphead;


初始化:SLTNode* phead = NULL;

传参: SLTPushBack(&phead, x);)

当phead为NULL时,也就不存在tail->next了


所以切记要先判断*pphead是否为空:

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
  SLTNode* newnode = BuySLTNode(x);
  if (*pphead == NULL)
  {
  *pphead = newnode;
  }
  else
  {
  SLTNode* tail = *pphead;
  //找原链表的尾结点
  while (tail->next != NULL)
  {
    tail = tail->next;
  }
  tail->next = newnode;
  }
}


4)尾删

尾删比较麻烦,因为要判断链表是否为空以及分情况讨论结点个数。


先看这段代码:

void SLTPopBack(SLTNode** pphead)
{
    assert(pphead && *pphead);//①
    SLTNode* tail, * tailpre;//②
    tail = *pphead->next;
    tailpre = *pphead;
    while (tail->next)
    {
        tail = tail->next;
        tailpre = tailpre->next;
    }
    free(tail);//③
    tailpre->next = NULL;//④
}


①pphead(二级指针)和*pphead绝对不可以为空,最好断言一下

②定义tail和tail前一个结点tailpre,目的是释放tail后,直接得到新的尾结点,方便置空

③没必要再把tail置空:tail = NULL;因为tail是局部变量,函数结束就自动销毁了

④释放后,新的尾结点的next置空

看似没什么毛病·······


但是,上面没有考虑只有一个结点的情况!!

⚡如果只有一个结点, tail = *pphead->next;后,tail为NULL,下面的执行就出大问题了


解决办法是判断一下:

void SLTPopBack(SLTNode** pphead)
{
    assert(pphead && *pphead);
    if ((*pphead)->next == NULL)//只有一个结点
    {
        free(*pphead);
        *pphead = NULL;
    }
    else//    >=2个结点
    {
        SLTNode* tail, * tailpre;
        tail = *pphead->next;
        tailpre = *pphead;
        while (tail->next)
        {
            tail = tail->next;
            tailpre = tailpre->next;
        }
        free(tail);
        tailpre->next = NULL;
    }
}


5)头插

按照尾插的路子,可能会这样写:

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);
    SLTNode* newnode = BuySLTNode(x);
    if (*pphead == NULL)
    {
        *pphead = newnode;
    }
    else
    {
        newnode->next = *pphead;
        *pphead = newnode;
    }
}


当然没有错,但是仔细想一想,其实没有必要判断*pphead是否为空,因为即使*pphead为空,执行else部分依然没毛病!


化简为:

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
    assert(pphead);
    SLTNode* newnode = BuySLTNode(x);
    newnode->next = *pphead;
    *pphead = newnode;
}


6)头删

头删相比较尾删很简单,因为不需要像尾删一样找tail前一个结点。


头删可以直接删:

void SLPopFront(SLTNode** pphead)
{
    assert(pphead && *pphead);
    SLTNode* next = (*pphead)->next;//临时存一下第二个元素的结点
    free(*pphead);
    *pphead = next;
}


7)查找

查找链表中的某个元素,只需遍历一遍链表。返回data == 要查找的元素第一次出现的节点的地址;如果链表中没有要查找的元素,返回NULL。


<注意,空链表也可以查找,返回NULL即可>↓

SLTNode* SLFind(SLTNode* phead, SLDataType x)
{
    SLTNode* cur = phead;
    while (cur)
    {
        if (cur->data == x)
            return cur;
        cur = cur->next;
    }
    return NULL;
}


8)删除

分两种情况:链表只有一个节点、链表有多个节点。

1、只有一个节点:如果*pphead == pos,相当于头删,直接调用前面的函数即可。

2、有多个节点:遍历链表,直到找到地址为pos的结点,按照尾删的思路,删除即可。

void SLErase(SLTNode** pphead, SLTNode* pos)
{
    assert(pphead && *pphead && pos);//都不能为空
    if (*pphead == pos)
    {
        SLPopFront(pphead);
    }
    else
    {
        SLTNode* cur = *pphead;
        while (cur->next != pos)
        {
            cur = cur->next;
        }
        SLTNode* next = cur->next->next;
        cur->next = next;
        free(pos);//一定要free
    }
}


9)插入结点

在pos前插入:

void SLInsert(SLTNode** pphead, SLTNode* pos, SLDataType x)
{
    assert(pphead && pos);
    if (pos == *pphead)
    {
        SLPushFront(pphead, x);
    }
    else
    {
        SLTNode* cur = *pphead;
        while (cur->next != pos)
        {
            cur = cur->next;
        }
        SLTNode* insnode = BuyNode(x);
        cur->next = insnode;
        insnode->next = pos;
    }
}


10)销毁

对于销毁链表,如果只free掉 *pphead行么?

当然不行!!


因为单链表由一个一个的结点连接起来的。如果只free(*pphead),头结点是释放了,但是后面的节点没被释放,还占用着空间但是已经找不到他们的地址了。


所以应该逐个释放👇

void SLDestroy(ListNode** pphead)
{
  ListNode* cur = *pphead;
  while (cur)
  {
  ListNode* next = cur->next;
  free(cur);
  cur = next;
  }
  *pphead = NULL; //最后别忘了置空
}


销毁完后 最好把*pphead 置空,防止销毁链表后对链表误操作而导致的野指针问题。

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