0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

C语言-链表(单向链表、双向链表)

DS小龙哥-嵌入式技术 来源:DS小龙哥-嵌入式技术 作者:DS小龙哥-嵌入式技 2022-09-09 11:30 次阅读

1. 链表结构介绍

在前面章节已经学习了数组的使用,数组的空间是连续空间,数组的大小恒定的,在很多动态数据存储的应用场景下,使用不方便;而这篇文章介绍的链表结构,支持动态增加节点,释放节点,比较适合存储动态数据的应用场景,而且链表的空间是存储在堆上面的,可以动态分配,释放。从效率上来讲,数组的空间是连续的,查询、读取数据数组占优势;链表的优势在于节点可以动态增加、动态删除,删除支持任意位置的节点删除。

特点:

数组的空间是连续的,可以直接通过[]下标访问。

链表的节点是不连续的,需要通过每个节点的指针,来找到上一个节点或者下一个节点的地址。

链表的每个节点就是一个结构体变量,节点里有一个或者两个指针,可以保存上一个节点和下一个节点的地址,方便遍历链表,删除、插入节点时定位位置。

image-20211203094458762

2. 案例: 单向链表的创建与使用

下面例子采用函数封装的形式编写,每个功能都使用子函数实现。

image-20211203095248628image-20211203095310341

实现的功能如下:

  1. 初始化链表头
  2. 插入节点的函数(链表任意位置插入,链表尾插入)
  3. 删除节点的函数(链表任意位置删除、链表尾删除)
  4. 遍历链表,输出链表里的所有信息
#include 
#include 
​
//定义链表节点的结构体
struct app
{
  int a;
  struct app *next; //能保存结构体的地址
};
​
struct app *list_head=NULL; //链表的头指针
​
void list_print(struct app *head);
struct app *list_HeadInit(struct app *head);
void list_add(int a,struct app *head);
void list_del(int a,struct app *head);
​
int main()
{
  //1. 初始化链表头
  list_head=list_HeadInit(list_head);
  //2. 在链表尾插入数据
  list_add(10,list_head);
  list_add(11,list_head);
  list_add(12,list_head);
  list_add(13,list_head);
  //3. 删除节点
  list_del(11,list_head);
  //4. 输出链接节点里的数据
  list_print(list_head);
  return 0;
}
​
/*
函数功能: 初始化链表头--给链表头申请空间
*/
struct app *list_HeadInit(struct app *head)
{
  if(head==NULL) //没有空间
   {
    //1. 申请链表头空间
    head=malloc(sizeof(struct app));
    //2. 初始化链表头
    head->next=NULL;
   }
  return head;
}
​
/*
函数功能: 在链表尾插入数据
int a  插入的数据值
struct app *head  链表头
*/
void list_add(int a,struct app *head)
{
  struct app *new_p=NULL;
  struct app *next_p=head;
  struct app *tmp_p; //保存上一个节点的地址
  //1.申请空间并给空间成员赋值
  new_p=malloc(sizeof(struct app));
  new_p->a=a;
  new_p->next=NULL;
​
  //2. 找到链表尾
  while(next_p!=NULL)
   {
    tmp_p=next_p;
    next_p=next_p->next; //指针指向下一个节点
   }
​
  //3. 插入新节点--链接结尾
  tmp_p->next=new_p;
}
​
/*
函数功能: 遍历输出链接里的所有数据
*/
void list_print(struct app *head)
{
  struct app *next_p=head;
  int cnt=0;
  if(head!=NULL)
   {
    while(next_p->next!=NULL)
     {
      next_p=next_p->next;
      printf("链表节点[%d]:a=%d\n",cnt++,next_p->a);
     }
   }
}
​
/*
函数功能:链表的删除--按照指定的数据删除
*/
void list_del(int a,struct app *head)
{
  struct app *next_p=head;
  struct app *tmp_p; //保存上一个节点的地址
  //1. 找到要删除的链表
  if(next_p!=NULL)
   {
    while(next_p->next!=NULL)
     {
      tmp_p=next_p; //保存上一个节点的地址
      next_p=next_p->next; //获取有效节点的地址
      if(next_p->a==a) //判断是否需要删除
       {
        tmp_p->next=next_p->next;
        free(next_p);
       }
     }
   }
}
复制代码

3. 案例: 单向循环链表

代码直接在上面的案例2例子上改造的,区别就是尾结点指向了头结点而不是NULL。

#include 
#include 
​
//定义链表节点的结构体
struct app
{
  int a;
  struct app *next; //能保存结构体的地址
};
​
struct app *list_head=NULL; //链表的头指针
​
void list_print(struct app *head);
struct app *list_HeadInit(struct app *head);
void list_add(int a,struct app *head);
void list_del(int a,struct app *head);
​
int main()
{
  //1. 初始化链表头
  list_head=list_HeadInit(list_head);
  //2. 在链表尾插入数据
  list_add(10,list_head);
  list_add(11,list_head);
  list_add(12,list_head);
  list_add(13,list_head);
  //3. 删除节点
  list_del(11,list_head);
  //4. 输出链接节点里的数据
  list_print(list_head);
  return 0;
}
​
/*
函数功能: 初始化链表头--给链表头申请空间
*/
struct app *list_HeadInit(struct app *head)
{
  if(head==NULL) //没有空间
   {
    //1. 申请链表头空间
    head=malloc(sizeof(struct app));
    //2. 初始化链表头
    head->next=head;
   }
  return head;
}
​
/*
函数功能: 在链表尾插入数据
int a  插入的数据值
struct app *head  链表头
*/
void list_add(int a,struct app *head)
{
  struct app *new_p=NULL;
  struct app *next_p=head;
  struct app *tmp_p; //保存上一个节点的地址
  //1.申请空间并给空间成员赋值
  new_p=malloc(sizeof(struct app));
  new_p->a=a;
  new_p->next=head;
​
  //2. 找到链表尾
  if(head!=NULL)
   {
    if(next_p->next==head) //表示第一次插入节点
     {
      next_p->next=new_p;
      //head ----<节点1>---head
     }
    else
     {
      while(next_p->next!=head)
       {
        next_p=next_p->next; //指针指向下一个节点
       }
      //3. 插入新节点--链接结尾
      next_p->next=new_p;
     }  
   } 
}
​
/*
函数功能: 遍历输出链接里的所有数据
*/
void list_print(struct app *head)
{
  struct app *next_p=head;
  int cnt=0;
  if(head!=NULL)
   {
    printf("头地址: %#x\n",next_p); //头
    printf("第一个节点地址:%#x\n",next_p->next); //下一个节点地址
    printf("第二个节点地址:%#x\n",next_p->next->next); //下下一个节点地址
    printf("第三个节点地址:%#x\n",next_p->next->next->next);
    printf("第四个节点地址:%#x\n",next_p->next->next->next->next);
  
    while(next_p->next!=head)
     {
      next_p=next_p->next;
      printf("链表节点[%d]:a=%d\n",cnt++,next_p->a);
     }
   }
}
​
/*
函数功能:链表的删除--按照指定的数据删除
*/
void list_del(int a,struct app *head)
{
  struct app *next_p=head;
  struct app *tmp_p; //保存上一个节点的地址
  //1. 找到要删除的链表
  if(next_p!=NULL)
   {
    while(next_p->next!=head)
     {
      tmp_p=next_p; //保存上一个节点的地址
      next_p=next_p->next; //获取有效节点的地址
      if(next_p->a==a) //判断是否需要删除
       {
        tmp_p->next=next_p->next;
        free(next_p);
       }
     }
   }
}
复制代码

4. 案例: 创建双向链表循环,实现插入、删除、遍历

双向链表在每个节点里新增加了一个指针,用于保存上一个节点的地址,现在的节点里一个用两个指针,一个保存上一个节点的地址,一个保存下一个节点的地址。

#include 
#include 
​
//定义链表节点的结构体
struct app
{
  int a;
  struct app *next; //下一个节点地址
  struct app *prev; //前一个节点地址
};
​
//全局变量声明区域
struct app *list_head=NULL; //链表的头指针
​
//函数声明
struct app *List_HeadInit(struct app *head);
void list_add(int a,struct app *head);
void list_print(struct app *head);
void list_del(int a,struct app *head);
​
int main()
{
  /*1. 初始化链表*/
  list_head=List_HeadInit(list_head);
  /*2. 添加链表节点*/
  list_add(10,list_head);
  list_add(11,list_head);
  list_add(12,list_head);
  list_add(13,list_head);
  list_add(14,list_head);
  /*3.删除指定节点*/
  list_del(12,list_head);
  /*4. 遍历输出所有节点信息*/
  list_print(list_head);
  return 0;
}
​
/*
函数功能: 创建链表头
*/
struct app *List_HeadInit(struct app *head)
{
  if(head==NULL)
   {
    head=malloc(sizeof(struct app));
    head->a=0;
    head->next=head;
    head->prev=head;
   }
  return head;
}
​
/*
函数功能: 添加数据-链表尾添加数据
*/
void list_add(int a,struct app *head)
{
  struct app *next_p=head;
  struct app *new_p=NULL;
  /*1. 申请新的节点*/
  new_p=malloc(sizeof(struct app));
  new_p->a=a;
  new_p->next=head;
  /*2. 完成新节点的添加*/
  //遍历链表
  while(next_p->next!=head)
   {
    next_p=next_p->next;
   }
  //添加新节点
  new_p->prev=next_p;
  next_p->next=new_p;
  //修改链表头的上一个节点地址
  head->prev=new_p;
}
​
/*
函数功能:输出链表里的所有数据
*/
void list_print(struct app *head)
{
  struct app *next_p=head;
  int cnt=0;
  /*1. 顺向遍历*/
  while(next_p->next!=head)
   {
    next_p=next_p->next;
    printf("节点[%d]:%d\n",cnt++,next_p->a);
   }
  /*2. 反向遍历*/
  next_p=head;
  while(next_p->prev!=head)
   {
    next_p=next_p->prev;
    printf("节点[%d]:%d\n",cnt--,next_p->a);
   }
}
​
/*
函数功能:删除链表里的指定节点
*/
void list_del(int a,struct app *head)
{
  struct app *next_p=head;
  struct app *tmp_p=NULL;
  while(next_p->next!=head)
   {
    tmp_p=next_p; //保存上一个节点的地址
    next_p=next_p->next;
    if(next_p->a==a)
     {
      //方式1
      tmp_p->next=tmp_p->next->next;
      tmp_p->next->prev=tmp_p;
​
      //方式2
      //tmp_p->next=next_p->next;
      //next_p->next->prev=tmp_p;
      
      //printf("%d\n",tmp_p->a); //11
      //printf("%d\n",tmp_p->next->a); //13
      //printf("%d\n",next_p->next->prev->a); //11
      free(next_p);
      break;
     }
   }
}

审核编辑 黄昊宇
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    4976

    文章

    18246

    浏览量

    287941
  • C语言
    +关注

    关注

    180

    文章

    7522

    浏览量

    127527
收藏 人收藏

    评论

    相关推荐

    C语言算法题:反转一个单向链表

    链表是编程学习的一个难点。其实,在C语言编程以及单片机裸机开发中,链表运用并不多。但是如果想提升嵌入式技能水平或收入水平,可以考虑深入嵌入式系统层面(如参与操作系统设计、深入学习新的操作系统等),此时,
    发表于 06-21 11:07 385次阅读
    C<b class='flag-5'>语言</b>算法题:反转一个<b class='flag-5'>单向</b><b class='flag-5'>链表</b>

    C语言链表知识点(2)

    C语言链表知识点(2)
    发表于 08-22 10:38 177次阅读
    C<b class='flag-5'>语言</b><b class='flag-5'>链表</b>知识点(2)

    C语言单向链表

    本帖最后由 snowmay001 于 2016-5-22 15:57 编辑 lianbiao.cpp/* 练习使用链表:创建链表、遍历链表、查找节点、添加节点、删除节点*/#include
    发表于 05-22 15:53

    C语言链表

    C语言链表,,,
    发表于 11-07 17:19

    C语言玩转链表

    C语言是必学的一个课程,不管你是单片机还是嵌入式物联网,都是基础,所以还是要好好学习的今天推荐的资料是关于C语言链表的资料我自己看了一下主要
    发表于 11-13 13:50

    玩转C语言链表-链表各类操作详解

    ,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束。  链表的各类操作包括:学习单向链表的创建、删除、 插入(无序、有序)、输出、 排序(选择、插入、冒泡
    发表于 09-18 13:30

    【HarmonyOS】双向循环链表

    了一个个双向循环链表,把指针的高效能运用到了极致,这也许就是编程的艺术吧!致敬鸿蒙内核开发者贡献了如此优秀的源码,鸿蒙内核源码可作为大学C语言,操作系统,数据结构三门课的教学项目
    发表于 10-20 15:39

    链表的缺陷是什么

    链表有一定的缺陷,就是单向性,只能从一个结点到下一个节点,而不能访问到上一个结点,而循环链表就可以解决这一问题,当然,用双向链表更加方便#
    发表于 07-14 08:09

    怎么实现c语言循环链表

    怎么实现c语言循环链表
    发表于 10-19 06:07

    如何在C语言中去创建一种双向链表

    双向链表的结构是由哪些部分组成的?如何在C语言中去创建一种双向链表呢?
    发表于 12-24 06:22

    C语言实现单链表举例

    所谓链表,就是用一组任意的存储单元存储线性表元素的一种数据结构。链表又分为单链表双向链表和循环链表
    发表于 07-11 16:40 87次下载
    C<b class='flag-5'>语言</b>实现单<b class='flag-5'>链表</b>举例

    了解Linux通用的双向循环链表

    在linux内核中,有一种通用的双向循环链表,构成了各种队列的基础。链表的结构定义和相关函数均在include/linux/list.h中,下面就来全面的介绍这一链表的各种API。
    发表于 05-07 10:44 560次阅读

    双向循环链表的创建

    需要注意的是,虽然双向循环链表成环状,但本质上还是双向链表,因此在双向循环链表中,依然能够找到头
    的头像 发表于 05-24 16:27 1825次阅读

    C语言_链表总结

    本篇文章介绍C语言链表相关知识点,涉及链表的创建、单向链表、循环链表
    的头像 发表于 08-14 09:53 1469次阅读

    链表和双链表的区别在哪里

    。 上面的三幅图对于理解链表的插入、删除很重要,看代码的时候要对着看。 实际中经常使用的一般为带头双向循环链表,下面是一个双向循环链表的 d
    的头像 发表于 07-27 11:20 1089次阅读
    单<b class='flag-5'>链表</b>和双<b class='flag-5'>链表</b>的区别在哪里