作者名:Demo不是emo
主页面链接:主页传送门
创作初心:对于计算机的学习者来说,初期的学习无疑是最迷茫和难以坚持的,中后期主要是经验和能力的提高,我也刚接触计算机1年,也在不断的探索,在CSDN写博客主要是为了分享自己的学习历程,学习方法,总结的经验等等,希望能帮助到大家
座右铭:不要让时代的悲哀成为你的悲哀
专研方向:网络安全,数据结构每日emo:我想走路有风,也想做你的英雄
线性表的顺序存储称为顺序表,而链表就是线性表的链式存储,而链表相对于顺序表的一个特点就是可以实现存储空间的动态管理,另一个就是每个节点的地址可能是不连续的,所以可以提高空间利用率,并且每个节点都由数据域和指针域构成。如果每个节点中只有指向后继节点的指针,那这就是我今天要分享的内容——单链表。
链表有很多种写法,有很多人定义一个节点结构体,又有部分人喜欢像顺序表一样定义一个头结点还有各种各样的写法,而我比较喜欢定义一个头结点的来保存链表的信息——(在我看来,第一个数据节点前面有头结点,最后一个数据节点后面有空指针,就可以使头插入,尾插入更加简单明了)那简单理解也就到这里了,下面给大家写一下以便大家可以更加直观的理解单链表。
定义头结点和数据节点:我们创建链表首先就需要创建头结点和定义节点结构体,链表的大小等信息就保存在头结点中,需要时从头结点中获取即可,代码如下:
typedef struct header//头结点
{
int length;//记录链表大小
struct node* next;//指向第一个节点的指针
}head;//别名方便使用
typedef struct node//数据节点
{
int date;//数据域
struct node* next;//指向下一个结点(指针域)
}node;
这样一个头结点和数据节点就定义好了,下面的代码中就可以直接引用啦;
链表的创建:创建链表只需要创建一个头结点,链表每储存一个元素就分配一个内存单元,再讲储存单元的地址保存在上一个节点中就可以了,并不需要在创建时把所有空间都分配好;链表普遍让最后一个节点的指针指向NULL方便遍历,查找等操作,代码如下:
head* createlist()//建立头结点保存链表信息
{
head* temp;
temp=(head*)malloc(sizeof(head));//为头结点分配空间,分配失败temp就等于NULL;
temp->length=0;//链表初始大小
temp->next=NULL;//重点
return temp;//返回创建好的头结点
}
这样一个包含头结点的链表就创建好了(思维图如上).
链表数据节点的插入:链表中插入元素要比顺序表方便,不用像顺序表那样移动元素,所以效率更快。举个例子,如下图,要将2这个节点插入到1和3两个节点中间,只需要先断开1和3之间的连接,再让1指向2,2向3即可。
链表数据节点插入的基本思路就差不多了,当然,这只是举个例子。具体的加入代码如下(这里只展示功能为链表插入数据节点的封装函数,完整代码在文章末尾):
int listinsert(head* p,int pos,int val)//头结点,需要插入的位置的下标值,插入的元素
{
if(p==NULL||pos<0||pos>p->length)
{
return -1;
}
node* temp=(node*)malloc(sizeof(node));//为要插入的节点分配内存
temp->date=val;//填充数据域
node* pcur=p->next;//定义指向第一个节点的指针(方便遍历链表)
if(pos==0)//头插
{
p->next=temp;
temp->next=pcur;//与第一个节点相连,可以确保最后一个节点指向NULL
}
else
{
for(int i=1;i<pos;i++)//遍历链表
{
pcur=pcur->next;//指针指向的地址右移,直到找到数据;
}
temp->next=pcur->next;
pcur->next=temp;
}
p->length++;//记录链表信息
return 0;
}
链表数据节点的删除:相对于数据节点的插入,数据节点删除的思路就比较简单,只需要找到要删除元素的位置,使它的上一个节点中的指针直接指向删除位置的下一个节点就可以了。代码如下:
void listdelete(head *p,int val)//删除数据域为val的节点
{
node *temp=p->next;//定义指向第一个数据节点的指针
while(temp->date!=val)//遍历temp直到找到我们要删除的元素位置并让temp指向它
{
temp=temp->next;
}
if(temp->date!=val)//判断是否找到我们要删除的元素
{
printf("listdelete():error");
return;//链表不存在该元素,退出程序
}
temp=p->next;//temp重新指向第一个数据节点,待会用来指向要删除的数据节点的前一个位置
node* pcur=NULL;//用于指向要输出的数据节点的位置
if(temp->date==val)//如果删除的是第一个节点
{
p->next=temp->next;//头结点直接指向第二个数据节点,达到删除的效果
p->length--;//记录链表信息
free(temp);//释放内存
return ;//退出程序
}
else//如果删除的不是第一个元素
{
for(int i=0;i<p->length;i++)
{
pcur=temp->next;//pcur指向temp的下一个位置
if(pcur->date==val)//pcur指向到要删除的的元素数据节点位置
{
temp->next=pcur->next;
//pcur的上一个数据节点指向pcur的下一个数据节点,实现数据节点删除
p->length--;//记录链表信息
free(pcur);//都懂
return;//结束程序
}
temp=temp->next;//不断右移,遍历链表
}
}
}
链表中数据节点的删除就可以实现了,但会不会认为后面表里打印链表的时候很麻烦?代码重复率会很高,所以我们为了方便也可以实现一个遍历打印链表的函数,而链表最后的数据节点又指向NULL,那么链表的打印函数就可以简单的实现了,具体代码如下。
void print(head *p)//打印链表
{
if(p==NULL)
{
printf("listprint():error");
}
node *temp=p->next;
while(temp!=NULL)//遍历链表
{
printf("%d ",temp->date);
temp=temp->next;
}
printf("\n");
}
遍历打印链表的函数就实现了,我将链表的创建,插入,删除和输出这几个操作用一段代码一起演示出来,大家应该能够看的更清晰,注释就没有这么明了了,还是建议大家先看前面的。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct header
{
int length;//链表大小
struct node* next;//指向节点
}head;
typedef struct node
{
int date;//数据域
struct node* next;//指向下一个结点
}node;
//注意结构体的本名,别名以及typedef的用法
head* createlist()//建立头结点
{
head* temp;
temp=(head*)malloc(sizeof(head));//为头结点分配空间,分配失败temp就等于NULL;
temp->length=0;
temp->next=NULL;//重点
return temp;//返回创建好的头结点
}
int listinsert(head* p,int pos,int val)//头结点,需要插入的位置的下标值,插入的元素
{
if(p==NULL||pos<0||pos>p->length)
{
return -1;
}
node* temp=(node*)malloc(sizeof(node));//分配内存
temp->date=val;//填充数据域
node* pcur=p->next;//定义指向第一个节点的指针(方便遍历链表)
if(pos==0)//头插
{
p->next=temp;
temp->next=pcur;//与第一个节点相连
}
else
{
for(int i=1;i<pos;i++)
{
pcur=pcur->next;//指针指向的地址右移,直到找到数据;
}
temp->next=pcur->next;
pcur->next=temp;
}
p->length++;
return 0;
}
void print(head *p)//打印链表
{
if(p==NULL)
{
printf("listprint():error");
}
node *temp=p->next;
while(temp!=NULL)//遍历链表
{
printf("%d ",temp->date);
temp=temp->next;
}
printf("\n");
}
void listdelete(head *p,int val)//删除数据域为val的节点
{
node *temp=p->next;
while(temp->date!=val)
{
temp=temp->next;
}
if(temp->date!=val)
{
printf("listdelete():error");
return;
}
temp=p->next;//当前节点
node* pcur=NULL;
if(temp->date==val)
{
p->next=temp->next;
p->length--;
free(temp);//释放内存
return ;
}
else
{
for(int i=0;i<p->length;i++)
{
pcur=temp->next;
if(pcur->date==val)
{
temp->next=pcur->next;
p->length--;
free(pcur);
return;
}
temp=temp->next;
}
}
}
int main()
{
head* p;
p=createlist();
if(p==NULL)
{
printf("listcreat():error");
}
int n;//赋值的数据个数
printf("请输入创建的链表节点个数\n");
scanf("%d",&n);
int arr[n];
printf("请依次填充链表的数据域\n");
for(int i=0;i<n;i++)
{
scanf("%d",&arr[i]);
}
for(int i=n-1;i>=0;i--)//将数组的值赋值给链表
{
listinsert(p,0,arr[i]);
}
printf("输出当前链表\n");
print(p);
int pos,val;
printf("请依次输入需要插入的位置和元素\n");
scanf("%d%d",&pos,&val);
int m=listinsert(p,pos,val);
if(m==-1)
{
printf("listinsert():error\n");
}
printf("输出插入后的链表\n");
print(p);
printf("请输入要删除的数据\n");
scanf("%d",&val);
listdelete(p,val);
printf("输出删除后的链表\n");
print(p);
}
好了,随便写点数据运行一下就是下面这个效果啦 ,感谢阅读。
请输入创建的链表节点个数
5
请依次填充链表的数据域
1 2 3 4 5
输出当前链表
1 2 3 4 5
请依次输入需要插入的位置和元素
4 6
输出插入后的链表
1 2 3 4 6 5
请输入要删除的数据
6
输出删除后的链表
1 2 3 4 5
值得一提的是,链表初始赋值的时候因为我用的都是头插(即从第一个数据节点插入),所以我如果直接输入1,2,3进行赋值,初始链表就会变成3,2,1,所以我用了数组保存我要赋值的链表元素,再让数组倒序对链表赋值,这个方法我觉得还是比较繁琐,大家有更好的方法也可以提出来,c语言小白一名,文章中如果有什么错误请大家见谅,当然,如果能够指出来就更棒了,十分感谢。