网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
在这里要强调一点:"->"是一个指针类型的运算符,它是用于指向结构体子数据的指针
3. 对线性表进行赋值
//对线性表进行赋值
Status ValueList\_L(LinkList &L,ElemType e){
LinkList s,p;
p = L;
while(p->next){
p = p->next;
}
s = (LinkList)malloc(sizeof(LNode)); //生成一个新结点
s->data = e; //将e赋值给新结点的数据域
s->next = p->next; //将新结点与后一个结点的地址连接
p->next = s;
return OK;
}
因为要实现构造一个单链表,所以在main函数中会循环调用ValueList_L方法,所以通过以下的循环是用来使p指向要赋值的位置
while(p->next){
p = p->next;
}
之后利用malloc()函数开辟新的结点来存放数据和下一个结点的位置即可,因为malloc()函数开辟空间之后返回的不是LinkList结点类型,所以在利用malloc()函数开辟新的结点时要将其通过强制类型转换使其转换成LinkList类型
4.对线性表进行销毁
//对线性表进行销毁
Status DistoryList\_L(LinkList &L) {
if(!L){ //如果线性表不存在,返回ERROR
printf("线性表不存在\n");
return ERROR;
}
LinkList q = L->next; //使q指向单链表的首元结点
while(q != NULL){ //当q结点不为空时一直进入循环
free(L); //释放L结点
L = q; //将q结点赋值给L结点
q = L->next; //将q结点赋值给L结点以后使q结点指向L的下一个结点
}
free(L); //此时q的值为NULL,L指向尾结点,将其释放
L = NULL;
printf("线性表已销毁\n");
}
在对单链表进行销毁操作时,从头结点开始逐一释放,释放前使q指向开始释放的结点,当开始结点不为空时,执行释放过程,先释放头结点,然后将L,q都向后移,依次释放,因为q始终是L的后继,所以最后一定是L留到最后,最后释放L结点
L = NULL;
为什么在feel(L);之后还要将L赋值为空?
因为free函数只是将之前动态分配给L的内存归还给系统,但是指针类型的结点L仍然存在,为了防止之后发生野指针访问,将L赋值为NULL
5.对线性表进行重置
//对线性表进行重置
Status ClearList\_L(LinkList &L)
{
if(!L->next){
printf("线性表为空表,不需要重置\n");
return ERROR;
}
LinkList p,q;
p = L->next; //将单链表的头结点赋值给p
while(p){
q = p->next; //将单链表的首元结点赋值给q
free(p);
p = q;
}
L->next = NULL; //将头结点的指针域赋值为空
printf("线性表已重置\n");
return OK;
}
在对线性表进行重置前首先要判断线性表是为空表,当其不为空时构造两个LinkList类型的结点p和q,使p指向L的首元结点,当p不为空即单链表不为空时进入while循环,将p的下一个结点复制给q,将p释放后再将q赋值给p。p为空时说明此时单链表只剩下了头结点,将头结点的指针域设置为NULL,完成单链表的重置(因为LinkList为指针类型的数据,所以赋值的内容都是地址)
图示:
6.判断线性表是否为空
//判断线性表是否为空
Status ListEmpty\_L(LinkList L)
{
if(L){
if(L->next == NULL) //如果首元结点不存在
printf("线性表是空表\n");
else
printf("线性表不是空表\n");
}
else{
printf("线性表不存在,无法判断\n");
}
return OK;
}
在判断线性表是否为空时,首先判断头结点是否存在,当头结点存在时看头结点的指针域是否为空,当指针域为空时说明首元结点不存在,单链表是空表;当指针域不为空时说明存在首元结点,单链表不是空表。如果头结点不存在的话说明单链表不存在,无法判断是否为空表。
7.获取线性表的长度
//获取线性表的长度
Status ListLength\_L(LinkList L,int count)
{
//L为带头结点的单链表的头指针,count为计数器
LinkList p = L->next; //定义p为单链表L的指针域
while(p){
p = p->next;
count++;
}
return count;
}
获取线性表长度的核心思路是遍历单链表,定义LinkList类型的变量p,将单链表的首元结点赋值给p。在该函数中count为计数器,形参count传来的值始终为1,count的值为1代表从首元结点开始计数,所以才将L->next赋值给p,目的是为了让count与存储数据元素的结点位置对应一致。(在单链表中有头结点的存在,所以这种方法计算出的长度最后的值要比线性表的实际长度大1)进入循环后每当p不断向后移动,每当p后移一次,计数器count的值就加1,直到p为空,此时count的位置就对应着最后一个存储着数据元素的结点位置。
8.获取线性表某一位置对应的元素
//获取线性表某一位置对应的元素
Status GetElem\_L(LinkList L,int index)
{
LinkList p;
p = L->next; //使p指向L的首元结点
int count = 1; //count为计数器 ,赋值等于1的原因是从首元结点开始计数
while(p && count < index){ //顺着指针向后查找,直到p指向第index个元素或p为空
p = p->next;
count++; //此时p一直指向第count个元素
}
if(!p || count > index){
printf("当前位置没有元素\n");
return ERROR;
}
printf("第%d个元素的值是:%d\n",index,p->data);
return OK;
}
与获取单链表的长度思路一样,获取单链表某一位置的元素也需要遍历单链表,只不过什么时候停止遍历由自己决定,可能不需要全部遍历。定义LinkList类型的变量p并且将首元结点的地址赋值给p。定义计数器count的初始值为1之后,进入while循环,循环判断有两个:
- p 因为p一直指向第count个结点,所以此循环判断条件的意思是当第count个结点存在时才能进入循环
- count < index 当count还不是我们想要获取的结点位置时继续循环
退出循环以后p指向的位置就是我们想要获取的结点位置,这个时候要先进行越界判断,!p的意思是如果在之前的循环中index的值大于单链表的长度,那么退出循环的原因就是p为NULL,那么!p就为true,满足if语句中的条件,返回ERROR,所以 !p的作用就是限制index不大于单链表的长度。
count > index的目的是为了限制index的值小于1
9.在线性表某一位置插入元素
//在线性表某一位置插入元素
Status ListInsert\_L(LinkList &L,int index,ElemType e)
{
LinkList p,q;
p = L; //将线性表的头结点赋值给p
int count = 0; //count为计数器
while(p && count < index - 1){ //寻找第index-1个结点
p = p->next; //此时的p结点指向第index-1个结点
count++;
}
if(!p || count > index -1){ //越界判断,index小于1或是index大于表长加1
printf("当前结点无法插入元素\n");
return ERROR;
}
q = (LinkList)malloc(sizeof(LNode));
q->data = e; //将e赋值到q的数据域当中
q->next = p->next;
p->next = q;
printf("元素插入成功\n");
return OK;
}
与寻找某个位置结点的值思路一致,需要先找到要插入结点的位置。但是这里不同的地方在于要插入结点的话,可以在单链表的表尾插入元素,也可以在头结点和首元结点间插入元素,所以计数器count的初值为0(为了保证从头结点开始遍历,count的值与实际的结点位置相匹配),所以判断条件变为index - 1。在结束循环和越界判断结束后p之后的位置就是要插入结点的位置,先构造出一个空结点并赋值给q,将p的下一个结点位置赋值给q的指针域,再将p的下一个结点位置赋值给q完成插入操作。
图示:
10.删除线性表某一位置的元素
//删除线性表某一位置的元素
Status DeleteList\_L(LinkList &L,int index)
{
LinkList p,q;
p = L; //将线性表的头结点赋值给p
int count = 0; //计数器
while(p->next && count < index - 1){
p = p->next;
count++; //此时p一直指向第count个结点
}
if(!(p->next) || count > index - 1){ //越界判断
printf("当前位置无法删除元素\n");
return ERROR;
}
q = p->next;
p->next = q->next;
free(q);
q = NULL;
printf("当前位置元素已删除\n");
return OK;
}
删除某一结点的思路仍然是从头结点开始遍历,找到要删除的结点的位置的前一个结点,此时p就是要删除结点位置的前一个结点。将p的后一个结点赋值给q,此时q就是要删除的结点,将q的下一个结点与p的下一个结点连接,释放q结点,完成删除操作。
11.求线性表某一元素的前驱
//求线性表某一元素的前驱
Status PriorElem\_L(LinkList L,int index)
{
LinkList p;
int count = 0; //count为计数器
p = L;
while(p->next && count < index - 1){ //寻找第index-1个结点
p = p->next; //p一直指向第count个结点
count++;
}
if(!(p->next) || count > index - 1){ //越界判断
printf("当前位置无法求该元素的前驱\n");
return ERROR;
}
if(p != L) //如果要获取第一个元素的前驱,就是获取头结点数据域的值
printf("该元素的前驱为:%d\n",p->data);
else
printf("该位置的前驱是头结点\n头结点的数据域中存储的值为:%d\n",p->data);
return OK;
}
和删除结点的思路完全一致,只不过求前驱时不需要进行删除结点,在循环中控制循环条件使p在index - 1位置结束循环,此时p指向的就是第index的前驱,直接将p结点的数据域输出即可
12.求线性表某一元素的后继
//求线性表某一元素的后继
Status NextElem\_L(LinkList L,int index)
{
LinkList p;
int count = 0;
p = L->next;
while(p && count < index){ //不断遍历寻找第index之后的结点
p = p->next; //p一直指向index-1的后一个结点
count++;
}
//!p的目的是为了确保i不大于表长-1,count>index的目的是为了确保index不小于0
if(!p || count > index){ //越界判断
printf("当前位置无法求该元素的后继\n");
return ERROR;
}
printf("该元素的后继为:%d\n",p->data);
}
在声明LinkList类型变量p时将L的首元结点赋值给p,在循环中p一直指向第index的下一个结点,所以直接将p结点的数据域输出即可
13.打印线性表
//打印线性表
Status PrintList\_L(LinkList L)
{
if(!L){ //如果线性表不存在,返回ERROR
printf("线性表不存在,无法打印\n");
return ERROR;
}
LinkList p;
p = L->next; //将L的首元结点赋值给p ,为了不将头结点打印出来
while(p){
printf(" %d",p->data); //将p结点的数据域输出
p = p->next; //结点不断的向后移动
}
printf("\n");
return OK;
}
打印单链表的思路也是进行对单链表的遍历,在遍历的过程中将每个结点的数据域中存储的值输出
运行结果演示:
为了方便演示,在这里为单链表一次赋值为1,2,3,4,5
构造一个空的头结点
赋值操作
判断此时线性表是否为空
获取单链表的长度
获取2号位置的元素
在3号位置插入520并打印单链表
获取此时单链表的长度
删除3号位置的元素并打印单链表
求3号位置元素的前驱和后继
重置单链表并获取长度以及判断是否为空表
销毁单链表并进行赋值和判断是否为空
以上便是线性表链式表示和实现,由于链表在空间的合理利用上和插入、删除是不需要移动等的优点,因此在很多场合下它是线性表的首选存储结构。然而,它也存在着实现某些基本操作的缺点,比如:求线性表长度时不如顺序存储结构…
源码:
#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<windows.h>
//函数结果状态代码
#define TRUE 1 //代码中出现TRUE相当于出现了1
#define FALSE 0 //出现FALSE相当于出现了0
#define OK 1 //出现OK相当于出现了1
#define ERROR 0 //出现ERROR相当于出现了0
#define INFEASIBLE -1
#define OVERFLOW -2
typedef int Status; //定义函数的返回状态
typedef int ElemType;
typedef struct LNode{
ElemType data; //数据域
struct LNode \* next; //指针域,指向一整个结点(结构体,该结构体中包含数据域和指针域)
}LNode , \* LinkList; //\* LinkList是结点的类型,在之后的代码中出现了它就相当于出现了指向这个结点的指针
//构造一个空的头结点
Status InitList\_L(LinkList &L){ //之前因为没有输入"&"符号,没有使用引用传递也就意味着没有开辟了新的内存空间,所以在之后赋值的时候会出现无法赋值的情况
L = (LinkList)malloc(sizeof(LNode)); //产生头结点,并使L指向该头结点(L也称头指针)
if(!L) return ERROR; //如果存储空间分配失败,返回ERROR
L->next = NULL; //将指针域赋值为NULL,在这里要强调一点:"->"是一个整体,它是用于指向结构体子数据的指针
printf("空的头结点创建成功\n");
return OK;
}
//对线性表进行赋值
Status ValueList\_L(LinkList &L,ElemType e){
LinkList s,p;
p = L;
while(p->next){
p = p->next;
}
s = (LinkList)malloc(sizeof(LNode)); //生成一个新结点
s->data = e; //将e赋值给新结点的数据域
s->next = p->next; //将新结点与后一个结点的地址连接
p->next = s;
return OK;
}
//对线性表进行销毁
Status DistoryList\_L(LinkList &L) {
/\*从头结点开始逐一释放,释放前使p指向头结点,q指向开始释放的结点
当开始结点不为空时,执行释放过程
先释放头结点,然后将p,q都向后移,依次释放
因为q始终是p的后继,所以最后一定是p留到最后,最后释放p
\*/
if(!L){ //如果线性表不存在,返回ERROR
printf("线性表不存在\n");
return ERROR;
}
LinkList q = L->next; //使q指向单链表的首元结点
while(q != NULL){ //当q结点不为空时一直进入循环
free(L); //释放L结点
L = q; //将q结点赋值给L结点
q = L->next; //将q结点赋值给L结点以后使q结点指向L的下一个结点
}
free(L); //此时q的值为NULL,L指向尾结点,将其释放
L = NULL; //free函数只是将之前动态分配给L的内存归还给系统,但是指针类型的结点L仍然存在
//为了防止之后发生野指针访问,将L赋值为NULL
printf("线性表已销毁\n");
}
//对线性表进行重置
Status ClearList\_L(LinkList &L)
{
if(!L->next){
printf("线性表为空表,不需要重置\n");
return ERROR;
}
LinkList p,q;
p = L->next; //将单链表的头结点赋值给p
while(p){
q = p->next; //将单链表的首元结点赋值给q
free(p);
p = q;
}
L->next = NULL; //将头结点的指针域赋值为空
printf("线性表已重置\n");
return OK;
}
//判断线性表是否为空
Status ListEmpty\_L(LinkList L)
{
if(L){
if(L->next == NULL) //如果首元结点不存在
printf("线性表是空表\n");
else
printf("线性表不是空表\n");
}
else{
printf("线性表不存在,无法判断\n");
}
return OK;
}
//获取线性表的长度
Status ListLength\_L(LinkList L,int count)
![img](https://img-blog.csdnimg.cn/img_convert/720506a7aef762181f12976d03c32afe.png)
![img](https://img-blog.csdnimg.cn/img_convert/d79727c6ee69390ba9e58b8410d77efb.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
s ListEmpty\_L(LinkList L)
{
if(L){
if(L->next == NULL) //如果首元结点不存在
printf("线性表是空表\n");
else
printf("线性表不是空表\n");
}
else{
printf("线性表不存在,无法判断\n");
}
return OK;
}
//获取线性表的长度
Status ListLength\_L(LinkList L,int count)
[外链图片转存中...(img-xUnMhxrC-1715731963114)]
[外链图片转存中...(img-ocfQjM1u-1715731963115)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**