之前的文章里,我已经对链表和数组的区别做了较为详细的比较(具体连接:https://blog.csdn.net/Mr_J0304/article/details/79678873)
下面实际操作一下,看看链表到底该怎么操作
链表是一种最简单常见的数据结构,常见操作包括建立链表,删除节点,删除链表,查找节点,置换节点,插入节点等,下面依次对这些操作进行处理
1.建立链表
如果是以C语言入门编程语言,对计算机的底层硬件结构有过多了解的话,其实是容易在内存地址这些概念上出现困惑的,这些困惑的具体表现就在指针上,而链表的建立恰恰离不开指针。
借用上一篇文章的概念:如果我们需要存放擎天柱的新款激光炮,而柜子里又放不下,这时候,我们需要向老妈口头申请一个放激光炮的空间(比如书架上),然后把上一个零件附带的纸条改为“下一个零件的位置在书架第二层”,最后把激光炮安置好,再继续向老妈申请下一个放零件用的空间
申请空间的时候,你只是向管理员(老妈/操作系统)提出了一个“需要一个激光炮那么大空间”的请求,管理员会根据实际情况,给你“随机”分配一个地址,为了记住这个地址,你需要一张小纸条(指针)记下来,防止防止错地方,导致最后好好的手办被老妈当成垃圾给扔了(放错地址后,数据会在后面的运算中被冲洗掉)
解释了这么多,我们来看具体的操作:
首先是定义链表的节点
struct ListNode{
int data;//存放数据,即擎天柱的零件
struct ListNode *next;//记录下一个存放地址的小纸条
};
然后开辟单个节点空间
struct ListNode *Create_Point()//创建一个节点
{
struct ListNode *p;//准备一张记录空间地址的小纸条
p=(struct ListNode*)malloc(sizeof(struct ListNode));//申请一个存放零件用的空间
p->next=NULL;//记住,时刻要标记,新申请的零件是最后一个零件,后面没有新的存在,否则搜寻会出问题
return p;//将地址返回
}
将单个节点空间组合为一个完整链表
struct ListNode *Create_List()//创建链表
{
int number;//读取数据
struct ListNode *head,*p1,*p2;//head头结点只作为开头,不存放数据,有利于后期代码书写的整洁性和易维护性,*p1,*p2用于开辟新的节点
head=Create_Point();
p2=head;
scanf("%d",&number);
while(number!=-1)//以-1作为停止录入数据的信号
{
p1=Create_Point();//正式开辟空间存放零件
p1->data=number;
p2->next=p1;//修改上一个零件所带的纸条,提示下一个零件的位置位于这个新开辟的空间里
p2=p2->next;//因为零件在不停的增加,我们的”最后一个零件“所在的地点也是要不停更新为这个新开辟空间的
scanf("%d",&number);//准备下一个要存放的零件
}
return head;//录入完成后,将这个零件的系列开头的纸条返回到自己手里,不然整个玩具都会迷失在8000平米的豪宅里
}
2.打印链表(查找链表中某个节点)
这两个任务其实非常相似,能够把链表完整打印一遍,就已经说明能够查找元素了
如果搞懂了创建链表,那打印是一件非常简单的事情了
void Print_List(struct ListNode *head)//打印以head作为开头的纸条标记的零件系列,稍作修改即可改为查找数据是否存在的函数
{
struct ListNode *temp=head->next;//头节点没有存储数据,所以不需要打印,直接从下一个开始
while(temp)
{
printf("%d\t",temp->data);
temp=temp->next;//读取这个零件后,要及时去下一个零件的地点读取再下一个零件的位置
}//这个循环的条件如果改为:temp->data!=number,即可变为搜索数据的存在
printf("\n");
}
3.指定位置插入节点
这一步需要对链表的本质有所熟悉,我画一个图示意一下
按照图示,我们写出代码
void Insert_Point(struct ListNode *head, int number,int position)//在链表中第position个节点之后插入一个数据number
{
struct ListNode *p1=head;
struct ListNode *p2=head->next;//前后指针,作用继续看后面的操作
for(int i=0;i<position;i++)//循环结束后,p1恰好位于要插入的节点,p2位于p1后的那个节点上
{
p1=p1->next;
p2=p2->next;
}
//开始插入数据
struct ListNode *temp=Create_Point();//先申请存放新数据的空间
temp->data=number;
p1->next=temp;
temp->next=p2;//记住,要把p1上零件的纸条修改为新零件所在的空间地址,新零件的纸条修改为原来p2所在数据的地址,这样才能保证链表连贯,不至于断开
}
4.删除指定位置的节点
有了第三点作为参考,第四个任务显得轻松多了
void Cancel_Point(struct ListNode *head,int position)//删除位于第position个位置上的节点,这里不考虑position溢出的情况
{
struct ListNode *p1=head;
struct ListNode *p2=head->next;
for(int i=1;i<position;i++)
{
p1=p1->next;
p2=p2->next;
}
p1->next=p2->next;
free(p2);
}
5.删除整个链表
这里会有不少人犯下经验性错误,只删除了头结点就认为完事了
但你想啊,你只不过是把零件链条的第一张纸条丢了,但是零件可依旧占据着整个空间呢,你可没有上报释放这些空间!
小数据倒无所谓,但是百万个数据可是相当吃内存的啊!所以节点要一个个释放
void Cancel_List(struct ListNode *head)
{
struct ListNode *temp1=head,*temp2=head->next;
while(temp2)
{
free(temp1);
temp1=temp2;
temp2=temp2->next;
}
free(temp1);
printf("Have canceled");
}
6.交换任意两个位置的节点
这个是最难的一个操作
附上代码
void Exchange_Point(struct ListNode *head,int a,int b)//交换位于a,b位置上的两个节点
{
struct ListNode *a1,*a2,*b1,*b2;
a1=b1=head;
a2=b2=head->next;
for(int i=1;i<a;i++)
{
a1=a1->next;
a2=a2->next;
}
for(int i=1;i<b;i++)
{
b1=b1->next;
b2=b2->next;
}
a1->next=b2;
struct ListNode *temp=a2->next;
a2->next=b2->next;
b2->next=temp;
b1->next=a2;
}
鉴于语言描述略显苍白,我直接上图解释:
最后附上完整代码以供参考,代码没有处理诸如无效位置,溢出等问题,仅供新手向理解
#include <stdio.h>
#include <stdlib.h>
struct ListNode{
int data;//存放数据,即擎天柱的零件
struct ListNode *next;//记录下一个存放地址的小纸条
};
struct ListNode *Create_Point()//创建一个节点
{
struct ListNode *p;//准备一张记录空间地址的小纸条
p=(struct ListNode*)malloc(sizeof(struct ListNode));//申请一个存放零件用的空间
p->next=NULL;//记住,时刻要标记,新申请的零件是最后一个零件,后面没有新的存在,否则搜寻会出问题
return p;//将地址返回
}
struct ListNode *Create_List()//创建链表
{
int number;//读取数据
struct ListNode *head,*p1,*p2;//head头结点只作为开头,不存放数据,有利于后期代码书写的整洁性和易维护性,*p1,*p2用于开辟新的节点
head=Create_Point();
p2=head;
scanf("%d",&number);
while(number!=-1)//以-1作为停止录入数据的信号
{
p1=Create_Point();//正式开辟空间存放零件
p1->data=number;
p2->next=p1;//修改上一个零件所带的纸条,提示下一个零件的位置位于这个新开辟的空间里
p2=p2->next;//因为零件在不停的增加,我们的”最后一个零件“所在的地点也是要不停更新为这个新开辟空间的
scanf("%d",&number);//准备下一个要存放的零件
}
return head;//录入完成后,将这个零件的系列开头的纸条返回到自己手里,不然整个玩具都会迷失在8000平米的豪宅里
}
void Print_List(struct ListNode *head)//打印以head作为开头的纸条标记的零件系列,稍作修改即可改为查找数据是否存在的函数
{
struct ListNode *temp=head->next;//头节点没有存储数据,所以不需要打印,直接从下一个开始
while(temp)
{
printf("%d\t",temp->data);
temp=temp->next;//读取这个零件后,要及时去下一个零件的地点读取再下一个零件的位置
}//这个循环的条件如果改为:temp->data!=number,即可变为搜索数据的存在
printf("\n");
}
void Insert_Point(struct ListNode *head, int number,int position)//在链表中第position个节点之后插入一个数据number
{
struct ListNode *p1=head;
struct ListNode *p2=head->next;//前后指针,作用继续看后面的操作
for(int i=0;i<position;i++)//循环结束后,p1恰好位于要插入的节点,p2位于p1后的那个节点上
{
p1=p1->next;
p2=p2->next;
}
//开始插入数据
struct ListNode *temp=Create_Point();//先申请存放新数据的空间
temp->data=number;
p1->next=temp;
temp->next=p2;//记住,要把p1上零件的纸条修改为新零件所在的空间地址,新零件的纸条修改为原来p2所在数据的地址,这样才能保证链表连贯,不至于断开
}
void Cancel_Point(struct ListNode *head,int position)//删除位于第position个位置上的节点,这里不考虑position溢出的情况
{
struct ListNode *p1=head;
struct ListNode *p2=head->next;
for(int i=1;i<position;i++)
{
p1=p1->next;
p2=p2->next;
}
p1->next=p2->next;
free(p2);
}
void Exchange_Point(struct ListNode *head,int a,int b)//交换位于a,b位置上的两个节点
{
struct ListNode *a1,*a2,*b1,*b2;
a1=b1=head;
a2=b2=head->next;
for(int i=1;i<a;i++)
{
a1=a1->next;
a2=a2->next;
}
for(int i=1;i<b;i++)
{
b1=b1->next;
b2=b2->next;
}
a1->next=b2;
struct ListNode *temp=a2->next;
a2->next=b2->next;
b2->next=temp;
b1->next=a2;
}
void Cancel_List(struct ListNode *head)
{
struct ListNode *temp1=head,*temp2=head->next;
while(temp2)
{
free(temp1);
temp1=temp2;
temp2=temp2->next;
}
free(temp1);
printf("Have canceled");
}
int main()
{
struct ListNode *head;
head=Create_List();
Print_List(head);
Exchange_Point(head, 2, 5);
Print_List(head);
Insert_Point(head, 100, 2);
Print_List(head);
Cancel_Point(head, 3);
Print_List(head);
Cancel_List(head);
return 0;
}