数据结构之单链表最最最基本教学,耐心看完就会了。

提示:数据结构单链表

目录

前言

一、单链表的简介

二、单链表的基本操作

1.单链表初始化

2.尾部插入(增)

3.中间插入(增)

4.遍历链表(查)

5.修改链表节点的值(改)

6.删除链表节点(删)

总结



前言

看这篇文章的小伙伴还是需要一定的c语言基础的,至少要看懂结构体,看懂申请堆空间。后面的代码里面我也会仔细讲解的


一、单链表的简介

       单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元素就是存储数据的存储单元,指针就是连接每个结点的地址数据。

       链表和顺序表(数组)的优缺点:

数组    优点:系统自己分配空间操作,简单;缺点:进行删除插入操作麻烦。空间利用效率不高。

链表    优点:空间利用率高,增删查改操作都很简单。缺点:算法逻辑相对数组难理解。

二、单链表的基本操作

1.单链表初始化

注:一下所有代码都可以单独运行,我是一步步增加的代码。

链表的初始化是非常重要的,后面的操作都有用到初始化的内容。

链表初始化如图所示:

代码如下:

#include <stdio.h>
#include <stdlib.h>

struct student{ //student可以自己定义 
	int data;//存放数据 
	struct student *next=NULL;//用于指向下一个节点  也就是下一个结构体的意思 
};
int main()
{
	//第一步初始化链表,申请堆空间
	//下面是申请堆空间的模板   student 是上面结构体定义的类型 
	struct student *head=(struct student *)malloc(sizeof(struct student));
	//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点 
	head->next=NULL;//还不知道下一个节点,先指向空; 
	head->data=10;//第一个节点存放数据,我定义为10 
	
	//以下代码为打印代码,后面会说
	struct student *p=NULL;
	p=head;//定义一个结构体指针指向头指针,方便遍历链表
	while(p!=NULL) //当前指向不为空 
	{
		printf("第一次打印链表:%d\n",p->data);//打印当前节点存放的数据 
		p=p->next;//指向下一个节点 
	
	}
	//***************************************** 
}
 

2.尾部插入(增)

插入可分为尾部插入和中间插入。因为刚初始化链表,链表只有一个节点;所以第二部操作就是尾部插入。

尾部插入如下图所示:

让头节点的指针next  代替  指向新节点的指针newnode  指向   新节点,这样链表就连起来了!注意记得把新节点的next指向NULL;

 

代码如下(示例):

#include <stdio.h>
#include <stdlib.h>

struct student{ //student可以自己定义 
	int data;//存放数据 
	struct student *next=NULL;//用于指向下一个节点  也就是下一个结构体的意思 
};
int main()
{
	//第一步初始化链表,申请堆空间
	//下面是申请堆空间的模板   student 是上面结构体定义的类型 
	struct student *head=(struct student *)malloc(sizeof(struct student));
	//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点 
	head->next=NULL;//还不知道下一个节点,先指向空; 
	head->data=10;//第一个节点存放数据,我定义为10 
	
	
	
		//*********尾部插入*********************
	
	struct student *newnode1=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第二个节点 
    newnode1->data=20;//给第二个节点的data赋值 
	newnode1->next=NULL;//让第二个节点 的next指向NULL 
	struct student *p=NULL;//新建一个结构体指针,用于循环遍历 
	p=head;//p指向头节点 
	while(p!=NULL)
	{
		if(p->next==NULL)//如果p当前指向最后一个节点
		{
			p->next=newnode1;//让最后一个节点的next指向新节点 
			break;//不退出的话会一直循环 
		 } 
		 p=p->next;//没到最后一个节点就让p指向下一个节点 
	 } 
	
	 
	
	//************************************* 
	//以下代码为打印代码,后面会说
	printf("尾部插入后:\n");
	
	p=head;//p重新指向头节点
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	//***************************************** 
	return 0;
}
 

代码运行效果如下:

可以看到10就是第一个节点(头节点)存放的数据,20则是第二个几点存放的数据

我们还可以再来一次尾部插入试试 

#include <stdio.h>
#include <stdlib.h>

struct student{ //student可以自己定义 
	int data;//存放数据 
	struct student *next=NULL;//用于指向下一个节点  也就是下一个结构体的意思 
};
int main()
{
	//第一步初始化链表,申请堆空间
	//下面是申请堆空间的模板   student 是上面结构体定义的类型 
	struct student *head=(struct student *)malloc(sizeof(struct student));
	//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点 
	head->next=NULL;//还不知道下一个节点,先指向空; 
	head->data=10;//第一个节点存放数据,我定义为10 
	
	
	
		//*********尾部插入*********************
	
	struct student *newnode1=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第二个节点 
    newnode1->data=20;//给第二个节点的data赋值 
	newnode1->next=NULL;//让第二个节点 的next指向NULL 
	struct student *p=NULL;//新建一个结构体指针,用于循环遍历 
	p=head;//p指向头节点 
	while(p!=NULL)
	{
		if(p->next==NULL)//如果p当前指向最后一个节点
		{
			p->next=newnode1;//让最后一个节点的next指向新节点 
			break;//不退出的话会一直循环 
		 } 
		 p=p->next;//没到最后一个节点就让p指向下一个节点 
	 } 
	
	//*************再来一次尾部插入********
	struct student *newnode2=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第三个节点 
	newnode2->data=30;//给第三个节点的data赋值  我定义为30 
	newnode2->next=NULL;//让第三个节点 的next指向NULL 
	p=head;
	while(p!=NULL)
	{
		if(p->next==NULL)//如果p当前指向最后一个节点
		{
			p->next=newnode2;//让最后一个节点的next指向新节点 
			break;//不退出的话会一直循环 
		 } 
		 p=p->next;//没到最后一个节点就让p指向下一个节点 
	 } 
	
	//************************************* 
	//以下代码为打印代码,后面会说
	printf("第二次尾部插入后:\n");
	
	p=head;//p重新指向头节点
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	//***************************************** 
	return 0;
}

运行结果为:

 很多朋友到这就会问这样操作链表岂不是很麻烦,其实我是为了方便大家理解,一步步操作的,等大家都有一定了解了后就可以用循环甚至用函数模块化编程就会方便许多。


3.中间插入(增)

 会尾部插入之后,我们再来看看中间插入,直接上图:

 

 代码如下:

#include <stdio.h>
#include <stdlib.h>

struct student{ //student可以自己定义 
	int data;//存放数据 
	struct student *next=NULL;//用于指向下一个节点  也就是下一个结构体的意思 
};
int main()
{
	//第一步初始化链表,申请堆空间
	//下面是申请堆空间的模板   student 是上面结构体定义的类型 
	struct student *head=(struct student *)malloc(sizeof(struct student));
	//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点 
	head->next=NULL;//还不知道下一个节点,先指向空; 
	head->data=10;//第一个节点存放数据,我定义为10 
	
	
	
	
		//*********尾部插入*********************
	
	struct student *newnode1=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第二个节点 
    newnode1->data=20;//给第二个节点的data赋值 
	newnode1->next=NULL;//让第二个节点 的next指向NULL 
	struct student *p=NULL;//新建一个结构体指针,用于循环遍历 
	p=head;//p指向头节点 
	while(p!=NULL)
	{
		if(p->next==NULL)//如果p当前指向最后一个节点
		{
			p->next=newnode1;//让最后一个节点的next指向新节点 
			break;//不退出的话会一直循环 
		 } 
		 p=p->next;//没到最后一个节点就让p指向下一个节点 
	 } 
	
	//*************再来一次尾部插入********
	struct student *newnode2=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第三个节点 
	newnode2->data=30;//给第三个节点的data赋值  我定义为30 
	newnode2->next=NULL;//让第三个节点 的next指向NULL 
	p=head;
	while(p!=NULL)
	{
		if(p->next==NULL)//如果p当前指向最后一个节点
		{
			p->next=newnode2;//让最后一个节点的next指向新节点 
			break;//不退出的话会一直循环 
		 } 
		 p=p->next;//没到最后一个节点就让p指向下一个节点 
	 } 
	
	//************************************* 
		//以下代码为打印代码,后面会说
	printf("第二次尾部插入后:\n");
   
	p=head;//重新指向头节点 
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	//************中间插入****************
		struct student *newnode3=(struct student *)malloc(sizeof(struct student));//新建一个节点 
		newnode3->data=40;//给第三个节点的data赋值  我定义为30 
	    newnode3->next=NULL;//让第三个节点 的next指向NULL 
	int NO;
	printf("如果你想插入那一个节点的后面,那请输入该节点的data:\n");
	//这里我们可以根据自己需要选择 ,为了方便我用data作为寻找目标
	scanf("%d",&NO);//这样待会输入20吧

	p=head;//上面p已经定义,直接用,下一次记得用的时候记得重新指向头 
	while(p!=NULL)
	{
		if(p->data==NO)//找到了 
		break;//退出循环 
		p=p->next;//没找到,一直向后移动 
	}
	if(p==NULL)
	 printf("没找到\n");
	 
	 //找到的话p还指着那个节点的
	  newnode3->next=p->next;//步骤1  将要插入的新节点的next   指向    要插入的那个节点的后面节点 
	   p->next=newnode3;//步骤2 将要那一个节点的next指向新节点 
	//以下代码为打印代码,后面会说
	printf("中间插入后:\n");

	p=head;//重新指向头节点 
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	//***************************************** 
	return 0;
}

 运行结果如下:

 

输入10或者30也是可以的:

 

4.遍历链表(查)

遍历链表上边的多次用到,原理很简单:就是定义一个结构体变量的指针,先指向头节点,再依次循环下去。如果需要找指定的节点,就需要一个遍历目标,中间插入有用到。

主要代码就是那几行,偷个懒了

5.修改链表节点的值(改)

修改链表节点的值,首先就是要先找到,然后再修改他的data就行了

直接上代码了:

#include <stdio.h>
#include <stdlib.h>

struct student{ //student可以自己定义 
	int data;//存放数据 
	struct student *next=NULL;//用于指向下一个节点  也就是下一个结构体的意思 
};
int main()
{
	//第一步初始化链表,申请堆空间
	//下面是申请堆空间的模板   student 是上面结构体定义的类型 
	struct student *head=(struct student *)malloc(sizeof(struct student));
	//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点 
	head->next=NULL;//还不知道下一个节点,先指向空; 
	head->data=10;//第一个节点存放数据,我定义为10 
	
	
	
		//*********尾部插入*********************
	
	struct student *newnode1=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第二个节点 
    newnode1->data=20;//给第二个节点的data赋值 
	newnode1->next=NULL;//让第二个节点 的next指向NULL 
	struct student *p=NULL;//新建一个结构体指针,用于循环遍历 
	p=head;//p指向头节点 
	while(p!=NULL)
	{
		if(p->next==NULL)//如果p当前指向最后一个节点
		{
			p->next=newnode1;//让最后一个节点的next指向新节点 
			break;//不退出的话会一直循环 
		 } 
		 p=p->next;//没到最后一个节点就让p指向下一个节点 
	 } 
	
	//*************再来一次尾部插入********
	struct student *newnode2=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第三个节点 
	newnode2->data=30;//给第三个节点的data赋值  我定义为30 
	newnode2->next=NULL;//让第三个节点 的next指向NULL 
	p=head;
	while(p!=NULL)
	{
		if(p->next==NULL)//如果p当前指向最后一个节点
		{
			p->next=newnode2;//让最后一个节点的next指向新节点 
			break;//不退出的话会一直循环 
		 } 
		 p=p->next;//没到最后一个节点就让p指向下一个节点 
	 } 
	
	//************************************* 
		//以下代码为打印代码,后面会说
	printf("第二次尾部插入后:\n");
    
	p=head;//重新指向头节点 
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	//************中间插入****************
		struct student *newnode3=(struct student *)malloc(sizeof(struct student));//新建一个节点 
		newnode3->data=40;//给第三个节点的data赋值  我定义为30 
	    newnode3->next=NULL;//让第三个节点 的next指向NULL 
	int NO;
	printf("如果你想插入那一个节点的后面,那请输入该节点的data:\n");
	//这里我们可以根据自己需要选择 ,为了方便我用data作为寻找目标
	scanf("%d",&NO);//这样待会输入20吧

	p=head;//上面p已经定义,直接用,下一次记得用的时候记得重新指向头 
	while(p!=NULL)
	{
		if(p->data==NO)//找到了 
		break;//退出循环 
		p=p->next;//没找到,一直向后移动 
	}
	if(p==NULL)
	 printf("没找到\n");
	 
	 //找到的话p还指着那个节点的
	  newnode3->next=p->next;//步骤1  将要插入的新节点的next   指向    要插入的那个节点的后面节点 
	   p->next=newnode3;//步骤2 将要那一个节点的next指向新节点 
	//以下代码为打印代码,后面会说
	printf("中间插入后:\n");

	p=head;//重新指向头节点 
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	//***************************************** 
    //***************链表节点的修改***********
    int last;//存放修改前的值,一会儿会输入 
    int newnum;//存放心的值 
	printf("如果你要修改某个节点的值,那请输入他的原本值\n");
	 scanf("%d",&last);
	 //遍历 
	 p=head;//上面p已经定义,直接用,下一次记得用的时候记得重新指向头 
	while(p!=NULL)
	{
		if(p->data==last)//找到了 
		break;//退出循环 
		p=p->next;//没找到,一直向后移动 
	}
	if(p==NULL)
	{
	
	 printf("没找到\n");
	 return 0; 
	}
	 
	printf("请输入一个新的值\n");
	 scanf("%d",&newnum);
	 p->data= newnum;
	 //************************************* 
		//打印 
	printf("第二次尾部插入后:\n");
    
	p=head;//重新指向头节点 
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	return 0;
}

运行结果如下:

 我都是一个代码重复利用的,要是认真看懂的小伙伴应该能看懂吧,如果不妥请私信我改成一个章节写一块代码!

6.删除链表节点(删)

终于来到最后一个删除节点了,删除节点同样需要先找到指定节点。同时要准备两个结构体指针,一前一后,因为断开节点的话,断开节点的前后都找不到对方了,就需要两个指针分别指向断开节点的前后节点,就像两只手一样拉住他们。

1.定义一个结构体指针 first_p   再定义一个结构体指针 second_p

first_p指向头节点,second_p指向头节点的next  如下图所示:

2.这里我让second_p先动,second_p=head->next;这样逻辑比较清晰一点,因为second_p是指向head的next,而head的next是指向第二个节点的,所以second_p=second_p->next之后,second_p就指向第二个节点的next

3.再让first_p动  first_p=head->next;

 

4.重复2,3步骤,知道second_p指向的节点的data为目标值后进行删除操作

5.删除指定节点原理如图:

             如图second_p已经指向第二个节点的next了,也就是second_p已经指向第三个节点了
             first_p已经指向第一个节点的next了,也就是first_p已经指向第二个结构体了
             那么可以进行 first_p->next=second_p->next;该代码的意思就是第二个节点的next指向第三个节点。
             这样就跳过了第三个节点,完成了删除操作

 之后可以将second_p指向的删除节点的next指向NULL ,再使用free();函数释放被删除节点所在的堆空间,达到节约空间的目的。

#include <stdio.h>
#include <stdlib.h>

struct student{ //student可以自己定义 
	int data;//存放数据 
	struct student *next=NULL;//用于指向下一个节点  也就是下一个结构体的意思 
};
int main()
{
	//第一步初始化链表,申请堆空间
	//下面是申请堆空间的模板   student 是上面结构体定义的类型 
	struct student *head=(struct student *)malloc(sizeof(struct student));
	//上一行代码意思为定义一个结构体指针head作为头指针,指向头节点 
	head->next=NULL;//还不知道下一个节点,先指向空; 
	head->data=10;//第一个节点存放数据,我定义为10 
	
	
	
	
		//*********尾部插入*********************
	
	struct student *newnode1=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第二个节点 
    newnode1->data=20;//给第二个节点的data赋值 
	newnode1->next=NULL;//让第二个节点 的next指向NULL 
	struct student *p=NULL;//新建一个结构体指针,用于循环遍历 
	p=head;//p指向头节点 
	while(p!=NULL)
	{
		if(p->next==NULL)//如果p当前指向最后一个节点
		{
			p->next=newnode1;//让最后一个节点的next指向新节点 
			break;//不退出的话会一直循环 
		 } 
		 p=p->next;//没到最后一个节点就让p指向下一个节点 
	 } 
	
	//*************再来一次尾部插入********
	struct student *newnode2=(struct student *)malloc(sizeof(struct student));//新建一个节点,为第三个节点 
	newnode2->data=30;//给第三个节点的data赋值  我定义为30 
	newnode2->next=NULL;//让第三个节点 的next指向NULL 
	p=head;
	while(p!=NULL)
	{
		if(p->next==NULL)//如果p当前指向最后一个节点
		{
			p->next=newnode2;//让最后一个节点的next指向新节点 
			break;//不退出的话会一直循环 
		 } 
		 p=p->next;//没到最后一个节点就让p指向下一个节点 
	 } 
	//************************************* 
		//以下代码为打印代码,后面会说
	printf("第二次尾部插入后:\n");
   
	p=head;//重新指向头节点 
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	//************中间插入****************
		struct student *newnode3=(struct student *)malloc(sizeof(struct student));//新建一个节点 
		newnode3->data=40;//给第三个节点的data赋值  我定义为30 
	    newnode3->next=NULL;//让第三个节点 的next指向NULL 
	int NO;
	printf("如果你想插入那一个节点的后面,那请输入该节点的data:\n");
	//这里我们可以根据自己需要选择 ,为了方便我用data作为寻找目标
	scanf("%d",&NO);//这样待会输入20吧

	p=head;//上面p已经定义,直接用,下一次记得用的时候记得重新指向头 
	while(p!=NULL)
	{
		if(p->data==NO)//找到了 
		break;//退出循环 
		p=p->next;//没找到,一直向后移动 
	}
	if(p==NULL)
	 printf("没找到\n");
	 
	 //找到的话p还指着那个节点的
	  newnode3->next=p->next;//步骤1  将要插入的新节点的next   指向    要插入的那个节点的后面节点 
	   p->next=newnode3;//步骤2 将要那一个节点的next指向新节点 
	//以下代码为打印代码,后面会说
	printf("中间插入后:\n");

	p=head;//重新指向头节点 
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	//***************************************** 
    //***************链表节点的修改***********
    int last;//存放修改前的值,一会儿会输入 
    int newnum;//存放心的值 
	printf("如果你要修改某个节点的值,那请输入他的原本值\n");
	 scanf("%d",&last);
	 //遍历 
	 p=head;//上面p已经定义,直接用,下一次记得用的时候记得重新指向头 
	while(p!=NULL)
	{
		if(p->data==last)//找到了 
		break;//退出循环 
		p=p->next;//没找到,一直向后移动 
	}
	if(p==NULL)
	{
	
	 printf("没找到\n");
	 return 0; 
	}
	 
	printf("请输入一个新的值\n");
	 scanf("%d",&newnum);
	 p->data= newnum;
	 //************************************* 
		//打印 
	printf("指定修改后:\n");
    
	p=head;//重新指向头节点 
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	//********指定链表删除*****************
	int NO2;
	printf("请输入你要删除节点的data:\n");
	scanf("%d",&NO2);
    struct student *first_p=head;//第一个指针
	struct student *second_p=head->next;//第二个指针
 //一前一后
	
	while(second_p!=NULL)
	{
		if(second_p->data==NO2)//找到了 
		break;//退出
		
	// 	没找到继续循环 
		second_p=second_p->next; 
		first_p=first_p->next; 
			 
		
	}
	//找到之后的操作 
	first_p->next=second_p->next;//被删除节点的上一节点的next   直接指向 被删除节点的下一个节点 
	 second_p->next=NULL;
	 free(second_p);//释放被删除节点所在的堆空间 
	 
	
	  //************************************* 
		//打印 
	printf("指定删除后:\n");
    
	p=head;//重新指向头节点 
	while(p!=NULL) //当前指向不为空 
	{
		printf("%d\n",p->data);//打印当前数据 
		p=p->next;//指向下一个节点 
	
	}
	
	
	//***************************
	return 0;
}

总结

好久没有写博客了,有点生疏,由于每个小章节的代码都是在上一个章节的基础上添加的,那在看每个章节的代码时可以找代码最下面可以直接看到核心代码。

最后要想学好链表还是有点难的,在学习单链表的过程中可以自己画一个图辅助自己理解,这样能起到很好的效果!此文章若有错误之处请大家评论或者私信,我好修改,谢谢!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值