链表的构造
利用结构体构造链表
对于初学者接触的最先都是单链表,所以我们在这里所讲的都为基础单链表
下面,我们来构造链表
#include <stdio.h>
#include <stdlib.h>
struct link_node
{
int num;
struct link_node *next;
};
利用指针进行空间申请和释放定义链表头指针
这里struct link_node *next 就是该类型的指针,进行穿线,记录下一个结构体的地址。这时候我们就需要一个指针来当起始点,就是头,一般定义为pHead,同时简单介绍一下申请和释放内存的相关知识。
函数名 | 函数原型 | 功能 | 返回值 |
---|---|---|---|
calloc | void * calloc (unsigned n,unsigned size); | 分配n个数据项的连续内存空间,每个数字项的大小为size个字节 | 分配内存单元的起始地址。如不成功,返回0 |
free | void free (void *p); | 释放p所指向的内存区 | 无 |
malloc | void * malloc (unsigned size); | 分配size个字节的内存区 | 所分配的内存地址,如内存不够,返回0 |
realloc | void * realloc (void *p,unsigned size); | 将p所指向的已分配的内存区的大小改为size。size可以比原来分配的空间大或小 | 返回指向该内存区的指针。若重新分配失败,返回NULL |
定义头指针和申请空间;
int main ()
{
struct link_node *pHead;
pHead=(struct link_node *)malloc(sizeof(struct link_node));
pHead->next=NULL; //指针初始化,养成好习惯
return 0;
}
这样一个头指针就完成了
链表的插入
链表的存储有两种方法,头插法和尾插法。
头插法
可以把链表头想成火车头,需要插入的数据当成车厢,把车厢就插到火车头后面。如果火车头后面原本有车厢,我们定义原车厢为1号,插入为2号,把火车头与1号车厢的联系传递给2号车厢与1号车厢联系,并将2号车厢再与火车头联系。
上图
通过图片,我们看到了二号车厢在一号车厢前面,所以头插法就是越先插入越靠后(逆序),下面我们通过函数来实现;
void CreatLink_node(struct link_node *pHead,int n)//n为插入的数据数量
{
struct link_node *pNew,*p=pHead;
while(n--)
{
int ans;
scanf("%d",&ans);
pNew=(struct link_node*)malloc(sizeof(struct link_node));
pNew->num=ans;
pNew->next=pHead->next;
pHead->next=pNew;
}
}
尾插法
与头插法相反,尾插法是在火车尾部插入,比头插法简单,直接上代码
void CreatLink_node(struct link_node *pHead,int n)
{
struct link_node *pTail=pHead,*pNew;//只存在火车头,也是车尾
while(n--)
{
int ans;
scanf("%d",&ans);
pNew=(struct link_node*)malloc(sizeof(struct link_node));
pNew->num=ans;
pNew->next=NULL;
pTail->next=pNew;
pTail=pNew;
}
}
随机插入
随机插入就是我们自己来决定在哪节车厢插入,若在第几节车厢后插入,那我们把这节车厢当作火车头,那么就和头插法一样,这个问题就迎刃而解了。
链表的查询
从火车头走到火车尾,找到符合关系的数据
int SearchLink_node(struct link_node *pHead,int k) //查询链表中元素等于k的个数
{
struct link_node *p=pHead->next;
int ans=0;
while(p!=NULL)
{
if(p->num==k) ans++;
p=p->next;
}
return ans;
}
链表的输出
链表输出就是通过火车头与车厢的关系,来进行向下传递,到达每个车厢都进行一次输出,代码如下。
void OutputLink_node(struct link_node *pHead)
{
struct link_node *p=pHead->next;//火车头不存数据
while(p!=NULL)
{
printf("%d ",p->num);
p=p->next;
}
}
但是一般情况下我们要输出的数据并不是全部,而是个别,那么我们可以先判断一下该车厢的数据是否符合规定,如果事的话,那么就输出,和全输出代码相差不大,大家可以私下进行。
链表的删除
这里介绍的是链表的全删除,称为清空。
代码如下
void DeleteLink_node(struct link_node *pHead)
{
struct link_node *p=pHead->next,*pCut;
pHead->next=NULL;
while(p!=NULL)
{
pCut=p;
p=p->next;
free(pCut);
}
}
大家若要删除个别车厢,可以把要删除的车厢定义为2号,前面的车厢定义为1号,后面的定义为3号,把2号与3号车厢的关系转移到1号与3号车厢的关系上,再把2号车厢内存释放,就成功实现了。
void DeleteLink_node(struct link_node *pHead,int k) //k为要删除的第几节车厢
{
struct link_node *p=pHead,*pCut;
int i=1;
for(int i=1; i<k; i++)//找到要删除车厢的前一个车厢
p=p->next;
pCut=p->next;//pCut为要删除的车厢
p->next=pCut->next;//转移关系
free(pCut);//释放内存
}
上述所说的车厢,在链表中称为结点,不要被我的比喻所迷惑。
链表的习题
这里我们联系一道题,大家如果想尝试的话我建议先自己写并且测试,再看下面的代码
查询链表中有多少个值为偶数的结点
-
输入数据有多组
每组先输入n,之后输入n个整数,利用头插法建立链表。 -
输出格式
输出链表中值为偶数的结点(我所比喻的车厢)的个数,没有则输出0。 -
intput
5
1 2 3 4 5
9
1 1 1 0 0 0 1 1 1
6
321 123 655 457 51 55 -
output
2
3
0
代码如下
#include <stdio.h>
#include <stdlib.h>
struct link_node
{
int num;
struct link_node *next;
};
void CreatLink_node(struct link_node *pHead,int n)
{
struct link_node *pNew,*p=pHead;
while(n--)
{
int ans;
scanf("%d",&ans);
pNew=(struct link_node*)malloc(sizeof(struct link_node));
pNew->num=ans;
pNew->next=pHead->next;
pHead->next=pNew;
}
}
int SearchLink_node(struct link_node *pHead)
{
struct link_node *p=pHead->next;
int ans=0;
while(p!=NULL)
{
if(p->num%2==0) ans++;
p=p->next;
}
return ans;
}
void DeleteLink_node(struct link_node *pHead)
{
struct link_node *p=pHead->next,*pCut;
pHead->next=NULL;
while(p!=NULL)
{
pCut=p;
p=p->next;
free(pCut);
}
}
int main ()
{
struct link_node *pHead;
pHead=(struct link_node *)malloc(sizeof(struct link_node));
pHead->next=NULL;
int n;
while(~scanf("%d",&n))
{
CreatLink_node(pHead,n);
int number=SearchLink_node(pHead);
printf("%d\n",number);
DeleteLink_node(pHead);
}
return 0;
}
总结
上述内容就是链表的基础知识,大家熟悉之后就可以像数组一样灵活应用它,并且感受链表的魅力与优点,唯一不好的就是代码量稍长。
欢迎大家提出错误与想法,我会及时修改与回复。