【数据结构】二、单链表的基本操作(C语言)

article/2025/10/7 6:49:28

目录

引用头指针的好处: 

1.结点的定义和初始化单链表

2.判断单链表是否为空表 

3.销毁单链表 

4.清空单链表,头结点和头指针还在

5.求单链表表长

6.取单链表中指定位置的数据

7.按值查找,返回数据所在的地址,时间复杂度为O(n)​编辑

8.按值查找,返回数据所在位置序号,时间复杂度为O(n)

9.在第pos个元素之前插入数据元素e

10.删除第i个结点,时间复杂度为O(1),因为不需要移动结点,只需修改指针

11.单链表的建立:头插法

12.单链表的建立:尾插法

13.合并两个链表 


引用头指针的好处: 

1.使得在链表的第一个位置上的操作和在表的其他位置上的操作一致。

2.空表和非空表的处理一致。 

1.结点的定义和初始化单链表

#include <stdio.h>//定义单链表的结点
typedef struct Lnode{    //node是结点的英文Elemtype data;    //存放数据,Elemtype是虚拟的类型,真实情况这里可以是int、float等类型struct Lnode* next;    //存放指向下一个结点的指针,而这个指针的类型是struct Lnode*型
}Lnode    //将Lnode定义为struct Lnode型
typedef struct Lnode* Linklist;    //Linklist表示指向struct Lnode这个结构体的指针
//L链表可以用Linklist L或Lnode *L表示, 指向某个结点的指针可以用Lnode *p或Linklist p表示//初始化单链表,L是一个指向单链表的指针
void initlist(Linklist L)
{L = (Linklist)malloc(sizeof(Lnode)); //头结点的类型为Lnode,头指针的类型为LinklistL->next = NULL;
}
//总结初始化:
//1.生成一个头结点(分配空间),将头指针L指向头结点,也就是头指针L赋值为头结点的地址
//2.将头结点的指针域赋值为空NULL

2.判断单链表是否为空表 

//判断单链表是否为空表
int is_Emptylist(Linklist L)
{//L指向的是头结点,头结点的指针域(L->next)为空,没有下一个结点,就是空表if(L->next == 0)    return 0;    //是空表elsereturn 1;    //不是空表
}

3.销毁单链表 

//销毁单链表
void destroylist(Linklist L)
{Lnode* p;    //定义一个指针p来指向结点,而结点是Lnode型,所以p的类型为Lnode *while(L != NULL)    //NULL是最后一个结点的指针域,所以当L为NULL时表示最后一个结点已经销毁了{p = L;    //将L的值赋给p,这样p也指向了L指向的结点L = L->next;    //将下一个结点的地址放在当前结点的指针域(L->next)里,这样L就指向了下一个结点free(p);    //销毁p指向的结点}
}
//总结销毁单链表:
//1.引入一个指针p来指向结点
//2.将L的值赋给p
//3.将L指向下一个结点
//4.销毁p指向的结点
//5.将L的值赋给p,此时L不再指向头结点,而是下一个结点,所以p指向了下一个结点
//6.将L指向下一个结点
//重复p指向L结点,L指向下一个结点,销毁p指向的结点,p指向L结点.......直到L为NULL

4.清空单链表,头结点和头指针还在

//清空单链表,头结点和头指针还在
void clearlist(Linklist L)
{Lnode *p,*i;    //引入两个指针变量指向结点,p指向当前要清除的结点,i指向p的下一个结点p = L->next;    //将p指向首元结点,从首元结点开始清空,因为要保留头结点while(p != NULL)    /*最后一个结点时,p->next(为NULL)赋给i,最后一个结点清空后i(为NULL)赋给p,这样就表示整个表就清空了*/{i = p->next;    //i指向了下一个结点,这样上一个结点就可以清除了free(p);    //清除结点p = i;    //将p也指向i指向的结点}L->next = NULL;    //将头结点的指针域设为NULL
}
//总结清空单链表
//1.将p指向首元结点(头结点的下一个结点)
//2.将p结点的指针域(next)赋给i指针,使i指向p的下一个结点
//3.清空p指针指向的结点
//4.将i赋给p,使p指向i指向的结点
//5.将p结点的指针域赋给i指针,使i再指向下一个结点,以此重复到p为NULL
//6.将L指向的结点,也就是头结点的指针域设为NULL

5.求单链表表长

//求单链表表长
int listlength(Linklist L)
{Lnode* p;int i = 0;    //用来计数,表示有几个结点,记得赋初值为0p = L->next;    //L->next指向的是首元结点,所以p指向首元结点while(p != NULL)    {i++;    //第一次进循环,p已经是L->next了,如果p为null就代表没有首元结点,不会进入循环,此时i为0。p = p->next;    //将p指向下一个结点}return i;    //i就是表长
}

6.取单链表中指定位置的数据

//取单链表中指定位置的数据
Elemtype GetElemList(Linklist L, int pos) 
{Lnode* p;int k = 1;    //k当做计数,初值为1,因为pos不是下标,所以不能为0p = L->next;    //从首结点开始找while((p != NULL)&&(k<pos))    //p为NULL退出循环表示链表找完了也没找到或者链表是空的。k等于pos时退出循环,表示找到了{p = p->next;k++;}if((p == NULL)||(k>pos))    //k>pos代表pos为0或负数,因为k初值为1。return ERROR;    //找不到return p->data;    //找到了会跳出循环,直接执行此语句将找到的值return回去。没找到会执行上面的if语句,不会执行此语句
}

7.按值查找,返回数据所在的地址,时间复杂度为O(n)

//按值查找,返回数据所在的地址
Lnode* SearchPos(Linklist L, Elemtype e)
{Lnode* p;p = L->next;    //从首元结点开始找while((e != p->data)&&(p != NULL)){   p = p->next;}return p;  //若找不到,就意味着p为NULL,此时返回NULL。找到了,返回的p就是数据所在地址
}

8.按值查找,返回数据所在位置序号,时间复杂度为O(n)

//按值查找,返回数据所在位置序号
int SearchNum(Linklist L, Elemtype e)
{Lnode* p;int k = 1;    //用来计数p = L->next;    //从首元结点开始找while((p != NULL)&&(e != p->data)){p = p->next;k++;}if(e == p->data)return k;    //找到了,返回位置序号elsereturn ERROR;    //else就是p为NULL,找不到
}

9.在第pos个元素之前插入数据元素e

 找到第pos-1个结点的时间复杂度为O(n),但之后,不管在第pos-1位置插入几个元素,时间复杂度都为O(1)。

//在第pos个元素之前插入数据元素e
void insertlist(Linklist L, Elemtype e, int pos)
{int j = 1;    //用来计数Lnode* p, *s;    //s用来指向新加入的结点p = L;    //从头结点开始,因为如果是在第1个元素之前插入,就应该是在头结点的后面插入while((p != NULL)&&(j<pos))    /* j=pos时循环结束,此时表示找到了,且p指向的是pos的前一个结点。或者,当p=NULL时循环结束,表示找不到*/{p = p->next;    //找下一个结点j++;}if((p == NULL)||(j>pos))    /* pos不能为0或负数,所以j>pos是return ERROR。且pos不能大于表长,当pos大于表长的情况就是循环结束后p==NULL,此时应return ERROR*/return ERROR;    s = (Linklist)malloc(sizeof(Lnode));    //给新插入的结点开辟空间,用来存放es->data = e;    //将e存放在新结点的数据域里s->next = p->next;    //将s的指针域指向原来的下一个结点p->next = s;    //将p的指针域指向新的结点
}

10.删除第i个结点,时间复杂度为O(1),因为不需要移动结点,只需修改指针

找到第i-1个结点的时间复杂度为O(n),但之后不管插入几个元素,时间复杂度为O(1)。

void DeleteNode(Linklist L, int i)
{Lnode *p, *s;int j = 0;p = L;while((p != NULL)&&(j<i-1))    //找第i-1个结点{p = p->next;j++;}if((p == NULL)||(j>i-1))return ERROR;s = p->next;    //循环退出后p指向i-1个结点。这里是将第i个结点的地址暂存到s里p->next = s->next;    //将p指向的结点里面存下一个结点的地址free(s);    //删除s指向的结点
}

11.单链表的建立:头插法

时间复杂度为O(n)

void createlist_H(Linklist L, int n)    //n为创建的结点个数
{int i;Lnode* p;L = (Linklist)malloc(sizeof(Lnode));    //给头指针分配空间,也就是创建头结点L->next = NULL;    //最开始是空表,所以没有首元结点的地址L->next为NULLfor(i = n; i>0; i--)    //循环n次,创建n个结点{p = (Lnode*)malloc(sizeof(Lnode));    //p作为新结点scanf("%d",&p->data);    //让用户输入数据p->next = L->next;    //将L后面的结点(头结点后的结点)连在p的后面L->next = p;    //将p插在头部作为首元结点}
}

12.单链表的建立:尾插法

时间复杂度为O(n)

void createlist_R(Linklist L, int n)
{Lnode *r, *p;    //r始终指向最后一个结点。p始终作为尾结点,插入到尾部int i;L = (Linklist)malloc(sizeof(Lnode));L->next = NULL;r = L;    //首先是空表,最后一个结点也就是头结点,所以r=Lfor(i = 0; i < n; i++)    //循环n次{p = (Lnode*)malloc(sizeof(Lnode));    //开辟新结点scanf("%d", &p->data);    //用户输入数据p->next = NULL;    //p作为尾结点所以指针域为NULLr->next = p;    //将p插入到当前链表的尾部r = p;    //r始终指向尾结点}
}

13.合并两个链表 

Linklist mergeTwoLists(Linklist list1, Linklist list2) {//创建一个新链表,此链表是带头结点的链表,list1和list2不是Linklist list3 = new ListNode();//创建一个新结点来指向链表3ListNode* p3 = list3;//当list1遍历完或list2遍历完时,循环退出while ((list1 != nullptr) && (list2 != nullptr)){//list1的元素小if (list1->val <= list2->val){//将list1指向的结点插入链表3p3->next = list1;//将指向链表3的结点指向下一个结点p3 = p3->next;//list1指向下一个结点list1 = list1->next;}//list2的元素小else{//将list2指向的结点插入链表3p3->next = list2;//将指向链表3的结点指向下一个结点p3 = p3->next;//list2指向下一个结点list2 = list2->next;}}//循环结束,表示有一个链表已经遍历完了,而且因为是有序链表,所以直接将另一个链表的剩下的结点插入到链表3里//或者用三目运算符:list3->next = p1 ? p1 : p2;if (list1 == nullptr){p3->next = list2;}else{p3->next = list1;}return list3->next;
}

http://chatgpt.dhexx.cn/article/l5ZtHqsh.shtml

相关文章

【头歌】单链表的基本操作

单链表的基本操作 第1关&#xff1a;单链表的插入操作 任务描述 本关任务&#xff1a;编写单链表的初始化、插入、遍历三个操作函数。 相关知识 链表是线性表的链式存储结构的别称&#xff0c;特点是以“指针”指示后继元素&#xff0c;因此线性表的元素可以存储在存储器中任意…

【数据结构】单链表的基本操作及实现

线性表的链式存储结构的特点是用一组任意的存储单元存储线性表的数据元素&#xff08;这组存储单元可以是连续的&#xff0c;也可以是不连续的&#xff09;。因此&#xff0c;为了表示每个数据元素与其直接后继数据元素之间的逻辑关系&#xff0c;对数据元素来说&#xff0c;除…

单链表的基本操作C++

定义一个结构体结构体里存有数据域以及指针域&#xff0c;Lnode跟Node意思一样只是为了方便而重新起名&#xff0c;Linklist 也就是Node 的意思也是为了方便 typedef struct Node {int data; //数据域Node* next;//指针域 指针域是用来存放下一个节点的}Lnode, * Linklist;…

数据结构与算法 | 单链表的基本操作

1024G 嵌入式资源大放送&#xff01;包括但不限于C/C、单片机、Linux等。关注微信公众号【嵌入式大杂烩】&#xff0c;回复1024&#xff0c;即可免费获取&#xff01; 线性表的存储结构有顺序存储结构&#xff08;顺序表&#xff09;和链式存储结构&#xff08;链表&#xff09…

单链表的基本操作(完整代码)

函数说明&#x1f603;&#xff1a; LinkList List_HeadInsert(LinkList& L)&#xff1a;用头插法建立单链表 LinkList List_TailInsert(LinkList& L)&#xff1a;用尾插法建立单链表 LNode * GetElem(LinkList L, int i):按照序号查找结点值 LNode * LocateElem(Link…

【链表】单链表的基本操作详解(C语言)

本文是单链表的C语言实现方法&#xff0c;包括单链表的创建、插入、删除、修改、查找等基本操作。 链表的底层是通过指针将一个个零散的内存块连接起来&#xff0c;链表的每个内存块称为结点。 单链表结点结构体 单链表的结点上存储数据data和下个结点的地址——后继指针nex…

单链表的基本操作(详细)

目录 0.本帖的内容&#xff1a; 1.单链表的定义 2.初始化 3.这个帖子中的功能&#xff08;函数块&#xff09; 4.利用为尾插法创建单链表 5.打印单链表 6.在带有头结点的单链表L中第i个位置之前的插入元素e 7.当第i个元素存在时&#xff0c;把第i个元素赋值给e并返回ok…

单链表基本操作

目录 结构体&#xff0c;这里讨论的都是带头节点的 一、单链表建立 1、头插法&#xff1a;利用头指针控制链表节点的增加 代码&#xff1a; 2、尾插法&#xff1a; 二、遍历 三、插入&#xff1a;&#xff08;pos代表要插入的位置&#xff09; 四、删除 五、销毁 六、…

单链表的基本操作

文章目录 单链表(含头结点)一、单链表二、单链表的创建(有头结点)三、单链表的结点查找(按位置查找)四、单链表的插入操作五、链表的删除操作六、链表的逆置七、链表的置空八、链表的销毁 单链表(含头结点) 一、单链表 1.1 关于单链表 单链表是一种采用链式存储的线性结构&am…

单链表的操作(超详细),保证你看完不后悔

&#x1f30d;新人小白的博客 ⌛️希望大家多多关注 &#x1f331;一起加油&#xff0c;共同成长 &#x1f383;以后会经常更新哒~&#x1f648; ⭐️个人主页&#xff1a; 收藏加关注&#xff0c;永远不迷路~⭐️ 数据结构系列&#x1f440; 一&#xff1a;顺序表的操作&…

C语言单链表实现初始化、创建、增、删、查等基本操作(详细)

C语言单链表实现初始化、创建、增、删、查等基本操作 #include <stdio.h> #include <stdlib.h> #include <malloc.h> typedef int ElemType; //定义单链表结构 typedef struct Node {ElemType data;//数据域struct Node *next;//指针域&#xff0c;指向下一…

java线程池的工作原理_JAVA线程池原理详解一

线程池的优点 1、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。 2、可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。 线程池的创建 1 public ThreadPoolExecutor(int corePoolSize, 2 in…

线程池原理解析

1、为什么要用线程池&#xff0c;线程池的作用&#xff08;意义&#xff09;&#xff0c;如果使用线程池会有什么好处&#xff0c;说说你对线程池的了解&#xff1f; 创建和销毁线程的代价是很大的&#xff0c;线程不像创建普通object对象那样&#xff0c;在堆中分配点内存就好…

Java 线程池原理及四种常用的线程池使用

推荐阅读&#xff1a;Java线程池实现原理及其在美团业务中的实践 文章目录 什么是线程池使用线程池的好处线程池的实现原理流程图分析源码分析 线程池的使用向线程池中提交任务newCachedThreadPoolnewFixedThreadPoolnewScheduledThreadPoolnewSingleThreadExecutor自定义线程池…

Tomcat线程池监控及线程池原理分析

目录 一、背景 二、tomcat线程池监控 三、tomcat线程池原理 四、总结 一、背景 我们都知道稳定性、高可用对于一个系统来讲是非常重要的&#xff0c;而为了保证系统的稳定性&#xff0c;我们一般都会进行各方面的监控&#xff0c;以便系统有任何异常情况时&#xff0c;开发人员…

Python学习:线程池原理及实现

传统多线程方案会使用“即时创建&#xff0c; 即时销毁”的策略。尽管与创建进程相比&#xff0c;创建线程的时间已经大大的缩短&#xff0c;但是如果提交给线程的任务是执行时间较短&#xff0c;而且执行次数极其频繁&#xff0c;那么服务器将处于不停的创建线程&#xff0c;销…

超详细的线程池原理解析

说明 线程池作为常用的并发工具重要性不言而喻&#xff0c;本文针对线程池进行了抽丝剥茧般的深入解析&#xff0c;希望大家看后会有帮助。 1 ThreadPoolExecutor结构关系图 2 参数结构 public ThreadPoolExecutor( int corePoolSize,//核心线程数量int maximumPoolSize,…

从简单代码入手,分析线程池原理

一、线程池简介 1、池化思想 在项目工程中&#xff0c;基于池化思想的技术应用很多&#xff0c;例如基于线程池的任务并发执行&#xff0c;中间件服务的连接池配置&#xff0c;通过对共享资源的管理&#xff0c;降低资源的占用消耗&#xff0c;提升效率和服务性能。 池化思想…

线程池原理与实现

目录 1. 线程池是什么 2. 线程池的优点&#xff1a; 3. 线程池的应用场景 4. 线程池的实现 4.1 线程池实现原理 4.2 线程池基本框架 4.3 结构体&#xff1a; 4.4 提供的接口 4.5 线程池测试代码 5 线程池提高demo thrd_pool.h thrd_pool.c main.c 运行结果 6 re…

线程池原理——高频面试题

1.高频面试题&#xff1a; 1.为什么使用线程池&#xff0c;优势是什么&#xff1b; 2.线程池如何使用&#xff1b; 3.线程池的几个重要的参数介绍&#xff1b; 4.线程池底层工作原理&#xff1b; 5.线程池用过吗&#xff1f;生产上你如何设置合理参数&#xff1b; 2.线程…