一、认识链表
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。——百度百科
如图,上面是一个标准的单链表,并且有一个头指针head指向链表的第一个元素。
二、建立链表
现在来建立一个最简单的链表。
1.创建存储结点的结构体
首先,我们需要建立适当的结构体来存储结点。
typedef struct node
{
int val;//一个节点的值
struct node *next;//自引用,实现链表“随用随开”的动态特点
} myLink;
为什么在struct
之前加typedef
?
解释:typedef
为后面的这个复杂的类型
struct node
{
int val;
struct node *next;
}
起了个名字myLink
,此后若想创造一个结点a,只需用
myLink* a;
即可,这和定义整型变量a
int a;
是一样的道理。
写好存储结点的结构体之后,我们来创建这个链表的第一个结点。
2.创建节点
我们要建立的链表为动态链表,“动态”是其一大特点,也就是要实现按需自动开辟新的节点。
malloc()
函数可以分配一块内存空间,并返回一个指向这块内存空间的指针。
如果我们用malloc()
函数分配一块大小等于结点所占空间大小的内存空间,然后想办法把malloc()
函数返回的指针赋给一个我们自己定义的指针,那岂不是实现了开辟结点的操作?
myLink *temp=malloc(sizeof(myLink));
这里,我们用sizeof()
函数获取一个结点的大小,然后用malloc()
函数在内存中开辟一块这么大的空间来存放结点,最后malloc()
函数返回一个指向这个节点的指针便于我们对这个节点进行各种操作。在此,我们用temp指针来进行对这个链表的操作。
为了以后方便遍历这个链表,我们还需要一个指针head
指向链表的最开头。而temp
指针现在正好指向链表头部,那么我们再加上这句话。
myLink *temp=malloc(sizeof(myLink));
myLink* head=temp;
现在,我们得到了链表的第一个节点。
由于现在只有这一个结点,该结点的next
指针无法指向任何一个结点,所以我们让他指向NULL
得了。
myLink *temp=malloc(sizeof(myLink));
myLink* head=temp;
temp->next=NULL;//temp->指针
有些情况下,在开始正式创建链表的第一个结点之前,需要在第一个结点之前再创造一个结点以方便进行后续的某些处理,这个结点就叫头结点。我们可以把现在得到的这个结点初始化为头节点,当然也可以跳过这一步,直接正式建立链表。
myLink *temp=malloc(sizeof(myLink));
myLink* head=temp;
temp->next=NULL;//temp->指针
temp->val=-1; //把头节点的值设为-1
3.正式创建链表
假设我们在正式创建链表的时候,需要加入n个元素。我们可以写一个1到n的for循环来操作。
首先,我们读入新元素的值,并且开辟一块内存空间存放结点,把这个值赋给该新节点,再把这个结点的next指针指向NULL
。
int val;
scanf("%d",&val);
myLink* newNode=malloc(sizeof(myLink));
newNode->val=val;
newNode->next=NULL;
(假设新结点的元素值为3)
然后,我们让头结点的指针temp指向新的节点。
int val;
scanf("%d",&val);
myLink* newNode=malloc(sizeof(myLink));
newNode->val=val;
newNode->next=NULL;
temp->next=newNode;
这样,两个结点便连接在了一起。新结点宣告添加成功。
为了给下一个链表进行操作,temp指针需要指向刚刚添加的新结点。
int val;
scanf("%d",&val);
myLink* newNode=malloc(sizeof(myLink));
newNode->next=NULL;
newNode->val=val;
temp->next=newNode;
temp=newNode;
这样,我们完成了添加一个结点的所有操作。把这些代码写进循环,就可以一直给这条链表增加元素了。
for(int i=1;i<=n;i++)
{
int val;
scanf("%d",&val);
myLink* newNode=malloc(sizeof(myLink));
newNode->next=NULL;
newNode->val=val;
temp->next=newNode;
temp=newNode;
}
我们把所有的代码都写进函数
myLink* createList()
{
myLink* temp=malloc(sizeof(myLink));
temp->next=NULL;
myLink* head=temp;
temp->val=-1;
for(int i=1;i<=n;i++)
{
int val;
scanf("%d",&val);
myLink* newNode=malloc(sizeof(myLink));
newNode->next=NULL;
newNode->val=val;
temp->next=newNode;
temp=newNode;
}
return head;
}
最后一步,我们返回指向链表头部的指针,以便对链表进行各种操作。
三、链表的基本操作
链表的基本操作包括结点的增加、删除、查询、修改、链表的打印,以及获取链表长度等。
由于许多操作其实都是一个道理,在此,我只介绍三种基本操作:遍历链表、插入结点、删除结点。
1.遍历链表
我们现在建立好了链表,并且得到了指向它开头的指针
myLink* p=createList();
我们来写一个函数遍历这个链表。假设我们遍历链表的目的是按顺序打印所有元素
void printLink(myLink* p);