链表的使用
前言
用c语言实现链表各种的操作,以及需要注意的事项
【链表理论知识】科普链接: https://blog.csdn.net/weixin_43382136/article/details/130531377
一、链表和数组的区别及实现
数组和链表的区别在于:
数组取的内存空间是连续的,
而链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
优点是数据的插入或删除都相当方便,有新数据加入就向系统要一块内存空间,数据删除后,就把空间还给系统,不需要移动大量数据。
缺点是设计数据结构时较为麻烦,另外在查找数据时,也无法像静态数据一样可随机读取数据,必须按顺序找到该数据为止。
//数组
#include<stdio.h>
int main()
{
int i;
int array[] = {1,3,5,7};
for(i=0;i<sizeof(array)/sizeof(array[0]);i++)
{
printf("%d ",array[i]);
}
putchar('\n');
}
//静态链表
#include<stdio.h>
struct Test
{
int data;
struct Test *next;
};
int main()
{
struct Test t1={1,NULL};
struct Test t2={3,NULL};
struct Test t3={5,NULL};
struct Test t4={7,NULL};
t1.next = &t2;
t2.next = &t3;
t3.next = &t4;
printf("%d %d %d", t1.data, t1.next->data, t1.next->next->data, t1.next->next->next->data);
return 0;
}
二、链表操作
1.链表遍历
#include<stdio.h>
struct Test
{
int data;
struct Test *next;
};
//动态遍历
void printLink(struct Test *head)
{
struct Test *point;
point = head;
while(point != NULL){
printf("%d",point->data); //输出当前指针指向的数据
point = point->next; //指针向后移
}
putchar('\n');
}
int main()
{
//静态创建链表
struct Test t1={1,NULL};
struct Test t2={3,NULL};
struct Test t3={5,NULL};
struct Test t4={7,NULL};
t1.next = &t2;
t2.next = &t3;
t3.next = &t4;
//静态遍历链
printf("%d %d %d %d \n", t1.data, t1.next->data, t1.next->next->data,t1.next->next->next->data);
//动态遍历
printLink(&t1);//把链表的头指针传进去
return 0;
}
2.链表节点个数统计
//获取链表节点的个数
int getLinkTotalNum(struct Test *head)
{
int cnt =0;//定义一个变量来计算链表节点的个数
while(head != NULL){
cnt++;
head = head->next; //指针向后移
}
return cnt;
}
3.(改查)链表查找、修改
//链表查找数据
int searchLinkData(struct Test *head,int data)//传入链表头指针和需要查找的数据
{
while(head!= NULL)
{
if(head->data == data) //查找到数据,返回1
{
return 1;
}
head = head->next; //指针向后移,与while结合遍历链表
}
return 0; //遍历完链表都没有查找到,返回0
}
//链表查找到数据然后【改数据】,head为链表头指针,data为旧数据,newdata为新数据
int gaiLinkData(struct Test *head,int data,int newdata)//传入链表头指针和需要查找的数据
{
while(head!= NULL)
{
if(head->data == data) //查找到数据并修改数据,返回1表示修改成功
{
head->data = newdata;
return 1;
}
head = head->next; //指针向后移,与while结合遍历链表
}
return 0; //遍历完链表都没有查找到,返回0
}
4.(增)链表插入(后面插入、前面插入)
4.1 后插法:
//链表增加节点(后插法),传入链表的头指针head遍历链表,在data的后面插入新节点new
int insertFromBehind(struct Test *head, int data, struct Test *new)
{
struct Test *p = head;//把链表的头指针赋给p
while(p!= NULL)
{
if(p->data == data)//找到数据,在数据后面插入新节点
{
new->next = p->next;
p->next = new;
return 1; //返回1代表插入成功
}
p=p->next; //指针向后移,与while结合遍历链表
}
return 0; //遍历完整个链表都没有找到数据,返回0代表插入失败
}
4.2 前插法:
//链表增加节点【前插法】
//传入链表的头指针head遍历链表,在data的前面插入新节点new,
//并返回新链表的指针
struct Test* insertFromFront(struct Test *head, int data, struct Test *new)
{
struct Test *p = head;//把链表的头指针赋给p
//如果是链表头的前面插入
if(p->data == data)
{
new->next = p;
return new; //返回新链表的头指针
}
//如果不是链表头的前面插入,判断下一个数据是否为空
while(p->next!= NULL)
{
if(p->next->data == data)//判断下一个数据的值是不是我们要的,是的话就在它前面插入
{
new->next = p->next;
p->next = new;
return head; //因为链表头没有发生改变,返回链表原本的头指针
}
p=p->next; //指针向后移,与while结合遍历链表
}
printf("插入失败,没有找到这个数据\n");
return head; //遍历完整个链表都没有找到数据,链表头没有发生改变,返回链表原本的头指针
}
5.(删)链表删除
//【链表删除节点】
//传入链表的头指针head遍历链表,data为要删除的数据
//并返回新链表的指针
struct Test* deleteData(struct Test *head, int data)
{
struct Test *p = head;//把链表的头指针赋给p
//如果是删除链表头节点
if(p->data == data)
{
head = head->next;
//free(p);//释放头节点的内存,这里是静态创建的节点,释放不了,注释掉了
return head; //返回新链表的头指针
}
//如果不是删除链表头节点,判断下一个数据是否为空
while(p->next!= NULL)
{
if(p->next->data == data)//判断下一个数据的值是不是我们要删除的
{
//struct Test *tmp = p->next; //这里可能有点争议噢,可能是p,可能是p->next,创建个指针变量释放删除的节点内存空间。静态创建的节点释放不了
p->next = p->next->next;
//free(tmp);//释放头节点的内存,这里是静态创建的节点,释放不了,注释掉了
return head; //因为链表头没有发生改变,返回链表原本的头指针
}
p=p->next; //指针向后移,与while结合遍历链表
}
printf("删除失败,没有找到这个数据\n");
return head; //遍历完整个链表都没有找到需要删除的数据,返回链表原本的头指针
}
6.完整代码(link.c)
#include<stdio.h>
struct Test
{
int data;
struct Test *next;
};
//动态遍历
void printLink(struct Test *head)
{
struct Test *point;
point = head;
while(point != NULL){
printf("%d ",point->data); //输出当前指针指向的数据
point = point->next; //指针向后移
}
putchar('\n');
}
//获取链表节点的个数
int getLinkTotalNum(struct Test *head)
{
int cnt =0;//定义一个变量来计算链表节点的个数
while(head != NULL){
cnt++;
head = head->next; //指针向后移
}
return cnt;
}
//链表查找数据
int searchLinkData(struct Test *head,int data)//传入链表头指针和需要查找的数据
{
while(head!= NULL)
{
if(head->data == data) //查找到数据,返回1
{
return 1;
}
head = head->next; //指针向后移,与while结合遍历链表
}
return 0; //遍历完链表都没有查找到,返回0
}
//链表查找到数据然后【改数据】,head为链表头指针,data为旧数据,newdata为新数据
int gaiLinkData(struct Test *head,int data,int newdata)//传入链表头指针和需要查找的数据
{
while(head!= NULL)
{
if(head->data == data) //查找到数据并修改数据,返回1表示修改成功
{
head->data = newdata;
return 1;
}
head = head->next; //指针向后移,与while结合遍历链表
}
return 0; //遍历完链表都没有查找到,返回0
}
//链表增加节点【后插法】,
//传入链表的头指针head遍历链表,在data的后面插入新节点new
int insertFromBehind(struct Test *head, int data, struct Test *new)
{
struct Test *p = head;//把链表的头指针赋给p
while(p!= NULL)
{
if(p->data == data)//找到数据,在数据后面插入新节点
{
new->next = p->next;
p->next = new;
return 1; //返回1代表插入成功
}
p=p->next; //指针向后移,与while结合遍历链表
}
return 0; //遍历完整个链表都没有找到数据,返回0代表插入失败
}
//链表增加节点【前插法】
//传入链表的头指针head遍历链表,在data的前面插入新节点new,
//并返回新链表的指针
struct Test* insertFromFront(struct Test *head, int data, struct Test *new)
{
struct Test *p = head;//把链表的头指针赋给p
//如果是链表头的前面插入
if(p->data == data)
{
new->next = p;
return new; //返回新链表的头指针
}
//如果不是链表头的前面插入,判断下一个数据是否为空
while(p->next!= NULL)
{
if(p->next->data == data)//判断下一个数据的值是不是我们要的,是的话就在它前面插入
{
new->next = p->next;
p->next = new;
return head; //因为链表头没有发生改变,返回链表原本的头指针
}
p=p->next; //指针向后移,与while结合遍历链表
}
printf("插入失败,没有找到这个数据\n");
return head; //遍历完整个链表都没有找到数据,链表头没有发生改变,返回链表原本的头指针
}
//【链表删除节点】
//传入链表的头指针head遍历链表,data为要删除的数据
//并返回新链表的指针
struct Test* deleteData(struct Test *head, int data)
{
struct Test *p = head;//把链表的头指针赋给p
//如果是删除链表头节点
if(p->data == data)
{
head = head->next;
//free(p);//释放头节点的内存,这里是静态创建的节点,释放不了,注释掉了
return head; //返回新链表的头指针
}
//如果不是删除链表头节点,判断下一个数据是否为空
while(p->next!= NULL)
{
if(p->next->data == data)//判断下一个数据的值是不是我们要删除的
{
//struct Test *tmp = p->next; //这里可能有点争议噢,可能是p,可能是p->next,创建个指针变量释放删除的节点内存空间。静态创建的节点释放不了
p->next = p->next->next;
//free(tmp);//释放头节点的内存,这里是静态创建的节点,释放不了,注释掉了
return head; //因为链表头没有发生改变,返回链表原本的头指针
}
p=p->next; //指针向后移,与while结合遍历链表
}
printf("删除失败,没有找到这个数据\n");
return head; //遍历完整个链表都没有找到需要删除的数据,返回链表原本的头指针
}
int main()
{
//静态创建链表
struct Test t1={1,NULL};
struct Test t2={3,NULL};
struct Test t3={5,NULL};
struct Test t4={7,NULL};
t1.next = &t2;
t2.next = &t3;
t3.next = &t4;
//静态遍历链
//printf("%d %d %d %d \n", t1.data, t1.next->data, t1.next->next->data,t1.next->next->next->data);
//动态遍历
printLink(&t1); //把链表的头指针传进去
//计算链表节点的个数
//int total =getLinkTotalNum(&t1);
//printf("链表的节点个数为:%d \n",total);
/*
//链表查找数据
int data;
scanf("%d",&data);//输入需要查找的数据
int ifData = searchLinkData(&t1,data); //接收返回的结果1或0
if(ifData == 1)
{printf("链表存在数据:%d \n",data);}
else
{printf("数据不存在");}
*/
//链表查找到数据然后【改数据】
int data;
int newdata;
printf("输入需要修改的旧数据:");
scanf("%d",&data);//输入需要修改的旧数据
printf("输入需要修改的新数据:");
scanf("%d",&newdata);//输入需要修改后的新数据
int ifData = gaiLinkData(&t1,data,newdata); //接收返回的结果1或0
if(ifData == 1)
{
printf("修改完毕,已经将%d修改成%d \n",data,newdata);
printLink(&t1);//动态遍历链表
}
else
{printf("修改失败,需要修改的数据不存在");}
/*
//链表增加节点(后插法)
//创建新节点
struct Test new={100,NULL};
//在链表数据3的后面插入新节点
insertFromBehind(&t1,3,&new);
//动态遍历打印链表
printLink(&t1);
*/
/*
//链表增加节点(前插法)
//创建新节点
struct Test new2={102,NULL};
//在链表头数据1的前面插入新节点,并返回新链表的头指针(因为在前面插入,头指针会发生改变)
struct Test *head = insertFromFront(&t1,1,&new2);
//动态遍历打印链表
printLink(head);
*/
/*
//链表删除节点
//删除数据5,并返回新链表的指针
struct Test *head2 = deleteData(&t1,5);
//动态遍历打印链表
printLink(head2);
*/
return 0;
}
三、链表动态创建
1.头插法
2.尾插法
、注意细节
链表插入时注意细节:
链表的存在主要是**【头指针】**,因为头指针指向链表的头节点,头指针它保存着头节点的地址,又因为链表是靠前面数据的指针一个一个指向后面形成的,所以知道头指针的地址就可以知道整条链表。
在进行链表插入的时候,如果是链表的第一个数据前插入一个新的数据。那么新插入的节点就成为了链表的第一个节点,同时指向第一个节点的头指针就成为了链表的头指针。
那么要记得把:【原先链表的头指针】改为【指向新节点的指针】。
然后把:链表新的头指针地址return返回给主函数中的指针变量。
链表删除时注意细节:
我们一般创建链表都是动态创建链表,所以在删除节点的时候要记得释放那个节点的内存。释放节点的时候free()函数只能释放malloc创建的空间。
未完待续…