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

数据结构3线性表——单链表(C)

前言:

本专栏属于数据结构相关内容,附带一些代码加深对一些内容的理解,为方便读者观看,本专栏内的所有文章会同时附带C语言和Python对应的代码,(可自行通过目录跳转到对应的部分)辅助不同主修语言的读者去更好的理解对应的内容,若是代码0基础的读者,可先去博主其他专栏学习一下基础的语法及知识点:

魔法天才的跳转链接:

C语言:C基础_Gu_shiwww的博客-CSDN博客
Python语言:python1_Gu_shiwww的博客-CSDN博客
其他数据结构内容可见:数据结构_Gu_shiwww的博客-CSDN博客

什么是单链表

单链表(Singly Linked List)是一种链式存储结构,由一系列**节点(Node)**组成,每个节点包含两部分

1 链表的特征

逻辑结构:线性结构
存储结构:链式存储结构
特点:内存不连续,通过指针实现
解决顺序表问题:顺序表长度固定和插入删除效率低的问题。
操作:增删改查

链表就是将节点用链串起来的线性表,链就是节点中的引用

链表分为带头结点链表和不带头节点的链表,两者在逻辑结构上都属于链式存储结构,只是带头节点的链表单独拿出一个节点存放首个有效节点的地址,头节点内部也存在数据域,且也有数据存储,但是在逻辑上这个数据域内部的元素无效。带头与不带头节点在代码编写上有细微差别,本文以带头节点的单向链表来进行讲解

【例子】通过一个表格给定一个链表遍历的例子(可自行理解)

(赵, 钱, 孙, 李, 周, 吴, 郑, 王)

以下是用C语言实现单链表的一些具体操作,Python的具体编程实现详见魔法天才预设的跳转路径:

2 编程实现链表

C 语言编程实现

C.a 节点的构建

在学习链表之前,我们要先定义好每个链表连接的节点,在C语言中可以用结构体完成节点的构建

typedef struct node_t
{int data;            //数据域:存数据struct node_t *next; //指针域:存放下一个节点的地址
} link_node_t, *link_node_p;

以上定义了一个名为link_node_t的结构体(typedef是对后面的结构体定义进行重命名,link_node_t等价于struct node_t),而重定义名之后的*link_node_p是对结构体指针的名进行重定义

【练习】建立A、B、C、D四个节点,内部数据域可为空,用指针进行连接,最后通过首节点进行逐个遍历打印

1.1 遍历无头单向链表

#include <stdio.h>typedef struct node_t
{int data;            //数据域:存数据struct node_t *next; //指针域:存放下一个节点的地址
} link_node_t, *link_node_p;int main(int argc, char const *argv[])
{//1. 定义四个节点link_node_t A = {1, NULL};link_node_t B = {2, NULL};link_node_t C = {3, NULL};link_node_t D = {4, NULL};//2. 连接节点A.next = &B;B.next = &C;C.next = &D;//3. 定义一个头指针指向第一个节点,用于遍历无头单向链表link_node_p p = &A;//4. 遍历无头单向链表while (p != NULL){printf("%d ", p->data); //打印所指节点数据p = p->next; //将指针向后移动一个单位}printf("\n");return 0;
}

1.2 遍历有头单向链表

#include <stdio.h>typedef struct node_t
{int data;            //数据域:存数据struct node_t *next; //指针域:存放下一个节点的地址
} link_node_t, *link_node_p;int main(int argc, char const *argv[])
{//1. 定义四个节点link_node_t A = {1, NULL};link_node_t B = {2, NULL};link_node_t C = {3, NULL};link_node_t D = {4, NULL};//2. 连接节点A.next = &B;B.next = &C;C.next = &D;//3. 定义一个头节点,数据域无效,指针域指向第一个节点link_node_t H;H.next = &A;//4. 定义一个头指针指向头节点,用于遍历有头单向链表link_node_p p = &H;//5. 遍历有头单向链表
#if 0//方法一: 循环里面先移动再打印while (p->next != NULL){p = p->next; //向后移动一个单位printf("%d ", p->data);}printf("\n");
#else//方法二://先跨越头节点,相当于让头指针指向了一个无头单向链表p = p->next;while (p != NULL){printf("%d ", p->data);p = p->next;}printf("\n");
#endifreturn 0;
}

1.3 链表尾插法

写一个有头单向链表,用于保存输入的学生成绩,实现一输入学生成绩就创建一个新的节点,将成绩保存起来。再将该节点链接到链表的尾,直到输入-1结束。

要求:每个链表的节点由动态内存分配得到 , 也就是用malloc。

过程:

  1. malloc申请空间link_node_t大小作为头节点
  2. 将新节点放到链表尾部
#include <stdio.h>
#include<stdlib.h>
typedef struct node_t
{int data;struct node_t *next;
} link_node_t, *link_node_p;int main(int argc, char const *argv[])
{link_node_p pnew = NULL;  //用于指向新建节点link_node_p ptail = NULL; //用于指向尾节点int score;//1. 创建一个头节点并初始化link_node_p p = (link_node_p)malloc(sizeof(link_node_t));if (NULL == p){perror("p malloc err");return -1;}p->next = NULL; //初始化头节点//2.让尾指针指向头节点ptail = p;//3.循环输入学生成绩-1结束,如果不是-1那就新建一个节点保存成绩尾插到链表while (1){scanf("%d", &score);if (-1 == score)break;//(1) 开辟新节点空间,让pnew指向新节点pnew = (link_node_p)malloc(sizeof(link_node_t));if (NULL == pnew){perror("pnew err");return -1;}//(2) 初始化新节点pnew->data = score;pnew->next = NULL;//(3) 连接新节点到链表尾部ptail->next = pnew;//(4) 移动尾指针到新节点ptail = pnew;}//4. 遍历有头链表p = p->next;while (p != NULL){printf("%d ", p->data);p = p->next;}printf("\n");return 0;
}

C.b 有头链表的函数操作

C.1 C语言编程操作的函数接口

#ifndef __LINKLIST_H__
#define __LINKLIST_H__typedef int datatype;
typedef struct node_t
{datatype data;struct node_t *next;
}link_node_t,*link_node_p;//1.创建一个空的有头单向链表
link_node_p createEmptyLinkList();//2.链表指定位置插入数据
int insertIntoPostLinkList(link_node_p p,int post, datatype data);
//3.计算链表的长度。
int lengthLinkList(link_node_p p);
//4.遍历链表
void showLinkList(link_node_p p);
//5.链表指定位置删除数据
int deletePostLinkList(link_node_p p, int post);
//6.判断链表是否为空
int isEmptyLinkList(link_node_p p);
//7.清空单向链表
void clearLinkList(link_node_p p);
//8.修改指定位置的数据 post 被修改的位置 data修改成的数据
int changePostLinkList(link_node_p p, int post, datatype data);
//9.查找指定数据出现的位置 data被查找的数据 //search 查找
int searchDataLinkList(link_node_p p, datatype data);
//10.删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除
int deleteDataLinkList(link_node_p p, datatype data);
//11.转置链表
void reverseLinkList(link_node_p p);
#endif

C.2 创建一个空的有头单向链表

//创建一个空的有头单向链表
link_node_p createEmptyLinkList()
{//1.开辟头节点空间link_node_p p = (link_node_p)malloc(sizeof(link_node_t));if (NULL == p){perror("p err");return NULL;}//2. 初始化头节点p->next = NULL;//3. 返回头节点地址return p;
}

定义了一个名为createEmptyLinkList()的函数,返回的数据类型是结构体指针,内部用malloc函数动态开辟了一个头节点,且将头节点初始化(即将头节点的next指针指向NULL),返回开辟成功的结构体指针

C.3 计算链表长度:length

//计算链表长度 length:长度
int lengthLinkList(link_node_p p)
{int len = 0;while (p->next != NULL){p = p->next;len++;}return len;
}

通过while循环遍历整个链表,只要链表的next指针不为空,就进入while循环,将p的指针指向下一个节点,直到最后一个next域为空的节点停止循环,同时在循环外部定义一个len变量记录整个链表的长度只要while循环执行一次len就要+1,最终返回len(即链表的长度)

C.4 向单向链表的指定位置插入数据

//向单向链表的指定位置插入数据
//p保存链表的头指针 post 插入的位置 data插入的数据
int insertIntoPostLinkList(link_node_p p, int post, datatype data)
{// 1. 容错判断: post<0 || post>长度if (post < 0 || post > lengthLinkList(p)){printf("insert err\n");return -1;}// 2. malloc新建一个节点link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));if (NULL == pnew){perror("pnew err");return -1;}// 3. 初始化新节点pnew->data = data;// 4. 将指针移动到插入位置的前一个for (int i = 0; i < post; i++)p = p->next;// 5. 将新节点连接到链表(先连后面再连前面)pnew->next = p->next;p->next = pnew;return 0;
}

首先容错判断,判断要插入位置post的合理性,不能小于0也不能大于链表的长度
2、3步新建一个节点,并且初始化将传入的数据data初始化到节点内部
第4步通过for循环遍历链表,将指针指向要插入元素的前一个,之后第5步将该节点连入链表,区分代码中的p和pnew,先连后面再连前面(顺序交换也无所谓)
注意:为什么要将p移动到前面一个位置是因为要找到被插入位置上一个节点的next指针,因为单链表是单向遍历的,只能从前往后遍历,找到post位置的前一个位置才能与插入位置的前一个节点的next指针进行连接

C.5 遍历单向链表

//遍历单向链表
void showLinkList(link_node_p p)
{while (p->next != NULL){p = p->next;printf("%d ", p->data);}printf("\n");
}

移动p指针,打印p的data域,注意函数中p指针的移动并不会改变开辟的动态结构体链表的头指针

C.6 判断链表为空,为空返回1,不为空返回0

//判断链表为空,为空返回1,不为空返回0
int isEmptyLinkList(link_node_p p)
{return p->next == NULL;
}

函数可以直接返回p的next域是否为NULL的判断是结果,满足为1,不满足为0

C.7 删除单向链表中指定位置的节点

//删除单向链表中指定位置的数据 post 代表的是删除的位置
int deletePostLinkList(link_node_p p, int post)
{// 1.容错判断:判空 || post<0 || post>=长度if (isEmptyLinkList(p) || post < 0 || post >= lengthLinkList(p)){printf("delete err\n");return -1;}// 2.将指针移动到删除位置的前一个节点for (int i = 0; i < post; i++)p = p->next;// 3.设指针pdel指向要删除节点link_node_p pdel = p->next;// 4.前后跨过要删除节点p->next = pdel->next;// 5.释放要删除节点free(pdel);return 0;
}

首先容错判断,判断post值的合法性以及链表是否为空,若链表为空则无元素可删
第2步同插入步骤,找到要删除节点的前一个位置(因为要断开post位置的前一个节点的next指针)然后定义一个pdel指针指向要删除的节点,也可以通过p->next = p->next->next直接删除post位置的节点,最后不要忘记释放新定义的节点

C.8 删除指定数据的所有节点

//删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除
void deleteDataLinkList(link_node_p p, datatype data)
{link_node_p t = p->next; //让t指向头节点的后一个节点while (t != NULL)        //相当于让t遍历无头链表{if (t->data == data) //判断成功删除节点并向后继续遍历{//(1)跨过要删除节点p->next = t->next;//(2)释放要删除节点free(t);//(3)让t指向p的下一个继续向后遍历t = p->next;}else //p和t继续向后遍历{p = p->next;t = t->next;}}
}

只需要从头开始遍历,如果发现某一个节点的数据与传参的数据相同,则进入if判断,断开当前节点就好,请注意此时t指针和p指针指向的并不是同一个节点,p指针在t指针指向的前一个结点,于是可以通过p指针访问到该元素的前一个节点。还需注意要删除所有与data相同的数据,所以并不是找到一个数据就结束了,要循环遍历到链表尾。

C.9 清空链表

//清空链表数据
//思想:一直删除头节点的后一个,直到为空链表为止
void clearLinkList(link_node_p p)
{while (p->next != NULL){link_node_p pdel = p->next;p->next = pdel->next;free(pdel);}
}

从头开始遍历,遍历到一个断开一个,记得将被断开的节点free掉

C.10 修改指定位置的数据

//修改指定位置的数据 post 被修改的位置 data修改成的数据
int changePostLinkList(link_node_p p, int post, datatype data)
{// 1.容错判断:判空 || post<0 || post>=长度if (isEmptyLinkList(p) || post < 0 || post >= lengthLinkList(p)){printf("change err\n");return -1;}//2.将指针遍历到修改节点位置for (int i = 0; i <= post; i++)p = p->next;//3.修改节点中数据p->data = data;return 0;
}

修改指定位置,首先也要进行容错判断,看post值是否合法,以及判断链表是否为空,空链表无内容可改。
接着通过for循环遍历到要修改的节点,直接进行data域的内容修改

C.11 查找指定数据出现的位置

//查找指定数据出现的位置 data被查找的数据 //search 查找
int searchDataLinkList(link_node_p p, datatype data)
{int post = 0; //记录查找的位置while (p->next != NULL){p = p->next;if (p->data == data)return post;post++;}return -1; //说明数据不存在
}

首先循环遍历整个链表,当遍历到与传入的参数data值相同的数据时,直接return,函数内部循环不再执行,同时定义一个post变量记录下表,若查询到结果时直接返回post

C.12 转置链表

//转置链表
//解题思想:
//(1) 将头节点与当前链表断开,断开前保存下头节点的下一个节点,保证后面链表能找得到,定义一个q保存头节点的下一个节点,断开后前面相当于一个空的链表,后面是一个无头的单向链表
//(2) 遍历无头链表的所有节点,将每一个节点当做新节点插入空链表头节点的下一个节点(每次插入的头节点的下一个节点位置)
void reverseLinkList(link_node_p p)
{link_node_p t = NULL;    //让t在循环里一直记录q的下一个,防止头插q之后链表找不到了link_node_p q = p->next; //让q记录一下头节点的后一个节点p->next = NULL;          //断开头节点while (q != NULL)        //相当于遍历无头单向链表{// 让t记录q的下一个,不然头插以后链表找不到了t = q->next;//头插:将q插入到p后面,先连后面再连前面q->next = p->next;p->next = q;//让q去找tq = t;}
}

总结:先断开头节点与后面数据的连接,然后再定义一个指针去记录要头插节点的后一个节点,一次向后移动一个一个头插如原来链表头节点的后面,实现整个链表的逆置

C.13 完整代码(可运行)

#include <stdio.h>
#include <stdlib.h>typedef int datatype;
typedef struct node_t
{datatype data;struct node_t *next;
}link_node_t,*link_node_p;//创建一个空的有头单向链表
link_node_p createEmptyLinkList()
{//1.开辟头节点空间link_node_p p = (link_node_p)malloc(sizeof(link_node_t));if (NULL == p){perror("p err");return NULL;}//2. 初始化头节点p->next = NULL;//3. 返回头节点地址return p;
}//计算链表长度 length:长度
int lengthLinkList(link_node_p p)
{int len = 0;while (p->next != NULL){p = p->next;len++;}return len;
}//向单向链表的指定位置插入数据
//p保存链表的头指针 post 插入的位置 data插入的数据
int insertIntoPostLinkList(link_node_p p, int post, datatype data)
{// 1. 容错判断: post<0 || post>长度if (post < 0 || post > lengthLinkList(p)){printf("insert err\n");return -1;}// 2. malloc新建一个节点link_node_p pnew = (link_node_p)malloc(sizeof(link_node_t));if (NULL == pnew){perror("pnew err");return -1;}// 3. 初始化新节点pnew->data = data;// 4. 将指针移动到插入位置的前一个for (int i = 0; i < post; i++)p = p->next;// 5. 将新节点连接到链表(先连后面再连前面)pnew->next = p->next;p->next = pnew;return 0;
}//遍历单向链表
void showLinkList(link_node_p p)
{while (p->next != NULL){p = p->next;printf("%d ", p->data);}printf("\n");
}//判断链表为空,为空返回1,不为空返回0
int isEmptyLinkList(link_node_p p)
{return p->next == NULL;
}//删除单向链表中指定位置的数据 post 代表的是删除的位置
int deletePostLinkList(link_node_p p, int post)
{// 1.容错判断:判空 || post<0 || post>=长度if (isEmptyLinkList(p) || post < 0 || post >= lengthLinkList(p)){printf("delete err\n");return -1;}// 2.将指针移动到删除位置的前一个节点for (int i = 0; i < post; i++)p = p->next;// 3.设指针pdel指向要删除节点link_node_p pdel = p->next;// 4.前后跨过要删除节点p->next = pdel->next;// 5.释放要删除节点free(pdel);return 0;
}//思想:一直删除头节点的后一个,直到为空链表为止
void clearLinkList(link_node_p p)
{while (p->next != NULL){link_node_p pdel = p->next;p->next = pdel->next;free(pdel);}
}//修改指定位置的数据 post 被修改的位置 data修改成的数据
int changePostLinkList(link_node_p p, int post, datatype data)
{// 1.容错判断:判空 || post<0 || post>=长度if (isEmptyLinkList(p) || post < 0 || post >= lengthLinkList(p)){printf("change err\n");return -1;}//2.将指针遍历到修改节点位置for (int i = 0; i <= post; i++)p = p->next;//3.修改节点中数据p->data = data;return 0;
}//查找指定数据出现的位置 data被查找的数据 //search 查找
int searchDataLinkList(link_node_p p, datatype data)
{int post = 0; //记录查找的位置while (p->next != NULL){p = p->next;if (p->data == data)return post;post++;}return -1; //说明数据不存在
}//删除单向链表中出现的指定数据,data代表将单向链表中出现的所有data数据删除
void deleteDataLinkList(link_node_p p, datatype data)
{link_node_p t = p->next; //让t指向头节点的后一个节点while (t != NULL)        //相当于让t遍历无头链表{if (t->data == data) //判断成功删除节点并向后继续遍历{//(1)跨过要删除节点p->next = t->next;//(2)释放要删除节点free(t);//(3)让t指向p的下一个继续向后遍历t = p->next;}else //p和t继续向后遍历{p = p->next;t = t->next;}}
}//转置链表
//解题思想:
//(1) 将头节点与当前链表断开,断开前保存下头节点的下一个节点,保证后面链表能找得到,定义一个q保存头节点的下一个节点,断开后前面相当于一个空的链表,后面是一个无头的单向链表
//(2) 遍历无头链表的所有节点,将每一个节点当做新节点插入空链表头节点的下一个节点(每次插入的头节点的下一个节点位置)
void reverseLinkList(link_node_p p)
{link_node_p t = NULL;    //让t在循环里一直记录q的下一个,防止头插q之后链表找不到了link_node_p q = p->next; //让q继续一下头节点的后一个节点p->next = NULL;          //断开头节点while (q != NULL)        //相当于遍历无头单向链表{// 让t记录q的下一个,不然头插以后链表找不到了t = q->next;//头插:将q插入到p后面,先连后面再连前面q->next = p->next;p->next = q;//让q去找tq = t;}
}int main(int argc, char const *argv[])
{link_node_p p = createEmptyLinkList();insertIntoPostLinkList(p, 0, 10);insertIntoPostLinkList(p, 1, 20);insertIntoPostLinkList(p, 2, 30);insertIntoPostLinkList(p, 3, 40);insertIntoPostLinkList(p, 4, 60);showLinkList(p);deletePostLinkList(p, 2);showLinkList(p);changePostLinkList(p, 1, 50);showLinkList(p);printf("50 post is: %d\n", searchDataLinkList(p, 50));insertIntoPostLinkList(p, 2, 50);showLinkList(p);deleteDataLinkList(p, 50);showLinkList(p);reverseLinkList(p);  //转置链表showLinkList(p);// clearLinkList(p);// if (isEmptyLinkList(p))//     printf("empty!\n");return 0;
}

http://www.dtcms.com/a/325903.html

相关文章:

  • Flutter - 应用启动/路由管理
  • 13、Docker Compose 安装 Redis 哨兵集群(一主两从)
  • 容器技术之docker
  • Excel 连接阿里云 RDS MySQL
  • AAAI-2025 | 北理工具身导航新范式!FloNa:基于平面图引导的具身视觉导航
  • Dashboard.vue 组件分析
  • CLIP在文生图模型中的应用
  • 《范仲淹传》读书笔记与摘要
  • sqli-labs通关笔记-第42关 POST字符型堆叠注入(单引号闭合 手工注入+脚本注入两种方法)
  • pdf转word教程
  • ERA5---MATLAB处理水汽数据与臭氧数据的读取与重采样-重复性工作
  • 基于模型预测控制的主蒸汽温度单步预测MATLAB实现
  • 大数据系统架构模式:驾驭海量数据的工程范式
  • 蓝桥杯算法之搜索章 - 4
  • 基于领域事件驱动的微服务架构设计与实践
  • 鸿蒙Des 加密解密 C++版本
  • POI导入时相关的EXCEL校验
  • 使用行为树控制机器人(三) ——通用端口
  • Python面试题及详细答案150道(41-55) -- 面向对象编程篇
  • 《基于Redis实现高效消息队列的完整指南》
  • 在 RHEL9 上搭建企业级 Web 服务(Tomcat)
  • Java Selenium 自动打开浏览器保存截图
  • Spring Cloud系列—Gateway统一服务入口
  • 案例分析2:上层应用不稳定提示注册失败
  • Python(9)-- 异常模块与包
  • CLIP,BLIP,SigLIP技术详解【二】
  • Flink + Hologres构建实时数仓
  • 机器学习:基于OpenCV和Python的智能图像处理 实战
  • 【05】昊一源科技——昊一源科技 嵌入式笔试, 校招,题目记录及解析
  • 提示词注入攻防全解析——从攻击原理到防御浅谈