在讲链表之前,先讲一个小故事:
在很久很久以前,我从集市上买了一条哈士奇,众所周知,哈士奇,俗称撒手没,为了不让他跑掉,我需要一条链子来拴住它,但是我已经回家了,怎么办呢? 我需要自己动手来做一条,一条链子由什么组成?当然是一节节小链子,链子的样子你一定见过,每一节都有一个小口用来连接下一节,于是我做好了一节节的小链子,并一节一节连起来,然后,我们还需要确定哪一端是人拿的,哪一端是给狗套的,于是我又做了一根绳子接在了链子其中的一段。后来,二哈给我准备了一个20万的装修计划,,,,,,好了,小故事讲完了。配张图!!
正题之前,请先记住链子,小链子,绳子这三个词和对应的链表中的名词。
词 | 链表对应名词 |
---|---|
链子 | 链表 |
小链子 | 节点 |
绳子 | 头节点 |
现在来讲讲单向链表,首先,链表和数组不同,数组中元素的内存地址是连续的,而链表中的元素内存地址是随机的。由于链表中的节点的数据类型是相同的,并且是随机存储,所以我们不能像数组那样只需要知道头节点就可以访问整个链表,我们必须在每个节点中存储下一个节点的地址,就好比狗链子中每一节的口子,用来连接下一节链子,只有这样,我们才能一节一节访问整个链表。
数组是这样使用内存,内存地址是连续的
而链表是这样使用内存的,所有他们必须有指向下一节点的指针
看到到这里,你对链表应该有一个小的认识了吧,那么下面我们就来定义一个链表吧。
1.定义链表
typedef struct list
{
int x;
struct list * next;
}list;
想一想,定义了链表就相当于我们有了每节小链子的模具,接下来,我们就该造小链子以及绳子了。
2.头节点初始化
list * InitListHead() //初始化
{
list *Phead = (list *)malloc(sizeof(list));//创建头结点
Phead->x = 0; //该变量可存放该链表长度
Phead->next = NULL;
return Phead; //创建后返回该头指针
}
头节点的数据可以存放比如链表的长度等等,接下来,我们只需要重复创建节点,这便是链表,这里要注意一下的是,单向链表包括头插入和尾插入,下面将分开讲解。
3.头插法
void HeadList(list * L,int e)//头插法
{
list * Pbady = (list *)malloc(sizeof(list));//创建一个节点
Pbady->x = e;
Pbady->next = NULL;
if (L->next == NULL)
{
L->next=Pbady;
}
else
{
Pbady->next = L->next;
L->next = Pbady;
}
L->x++;
}
}
如果看代码有些生硬,那我们用图来说明。
不难看出,使用头插法的数据是逆序排列的,后来的反而排在前面,这也是头插和尾插的区别之一。
4.尾插法
void TrailList(list * LA,list ** LB,int e)//尾插法
{
list *Pbady = (list*)malloc(sizeof(list));
Pbady->x = e;
if (LA->next == NULL)
{
LA->next = Pbady;
}
else
{
(*LB)->next = Pbady;
}
Pbady->next = NULL;
*LB = Pbady;
LA->x++;
}
尾插法相对于头插法多了一个指针,如果代码看着懵,那我们依旧上图来说。
尾插法不像头插法,只需要在开头插入就可以,而尾插入需要遍历整个链表并找到最后,如果链表足够长,每次都遍历一遍,显然不科学,所以我们想到用一个指针来随时记录最后一个节点的位置,并指向最后一个节点,这样,每当我们有新节点的时候,只需要在该指针后面插入即可。并更新该指针,至于为什么使用二级指针可查看该文。
指针作形参的一些问题
接下来来看一下链表的基本运算。
5.按序号查找内容
int NumFindList(list *L,int e)//按序号查找
{
if (e <= 0)
{
return -1;
}
int i = 0;
int j = L->x;
list * P = L;
while (P != NULL&&i <= j)
{
if (i == e)
{
printf("该内容为%d\n", P->x);
break;
}
P = P->next;
i++;
}
}
6.按值查找
void ValueFindList(list * L,int e)//按值查找
{
int i = 0;
int j = L->x;
list * P = L;
while (P != NULL&&i <= j)
{
if (P->x == e)
{
printf("已找到\n");
}
P = P->next;
i++;
}
}
7.求链表长度
int LengthList(list * L)//链表长度
{
return L->x;//这里我们只需要访问头节点中的数据即可
}
8.插入操作
void InsertList(list * L,int i,int e)//插入操作
{
if (i > L->x)
{
printf("插入位置不合法\n");
return ;
}
list * Pbady = (list *)malloc(sizeof(list));
list *P = L->next;
int a = 1;
Pbady->x = e;
while (P != NULL)
{
if (a == i-1)
{
Pbady->next = P->next;
P->next = Pbady;
L->x++;
break;
}
P = P->next;
a++;
}
}
9.删除操作
void DelList(list * L,int i)//删除操作
{
if (i > L->x)
{
printf("插入位置不合法\n");
return;
}
list * P = L->next;
list * P_2 = NULL;
int a = 1;
while (P != NULL)
{
if (a == i-1)
{
P_2 = P->next;
P->next = P->next->next;
P_2->next = NULL;
free(P_2);
L->x--;
break;
}
P = P->next;
a++;
}
}
10.显示链表现有内容
void ShowList(list * L)//显示链表
{
list * P = L;
printf("该链表共有%d个元素\n", P->x);
P = L->next;
while (P!= NULL)
{
printf("%d\n", P->x);
P = P->next;
}
}
11.释放内存
void FreeList(list * L)
{
list * P;
if (L == NULL)
{
return ;
}
while (L)
{
P = L->next;
free(L);
L = P;
}
}
好了,单向链表的讲解就到这里。如果有什么错误,欢迎指出。
每文一句:不要总是用眼泪去打动去挽留,有时侯你的眼泪适得其反,微笑,一定要自信的微笑着面对一切!