链表
链表是一种常见的重要的数据结构,是一种动态进行存储分配的结构。
前面学过的数组存放数据时,有一定的局限性,不仅只能存放同一类型的数据,还要事先定义固定的数组长度。在某些情况下会造成内存浪费。
而链表则没有这种缺点,链表是根据需要来开辟内存单元,需要多少,就可以开辟多少,不会造成内存浪费。
最简单的链表(单向链表)的结构。
上图中的head叫做头指针变量,head存放的是链表的地址。链表中每一个元素都叫做一个结点,每个结点都包括两个部分,数据域
(用户需要用的实际数据),指针域
(存放是下一个结点的地址)。
head指向第一个元素,第一个元素又指向第二个元素,·····直到最后一个元素,该元素不再指向其他元素,它被称作为表尾
,他的地址部分存放一个NULL
(表示空地址),链表到此结束。
头结点的数据域
可以
不存储任何信息,头结点的指针域存储指向第一个结点的指针(即第一个元素结点的存储位置)。头结点的作用是使所有链表
(包括空表)的头指针非空,并使对单链表的插入、删除操作不需要再区分是否为空表或是否在第一个位置进行,从而与其他位置的插入、删除操作一致,简化了对链表的操作。
链表在内存中的地址可以是不连续的,想要找到链表中的某一个元素,就要找到上一个元素,根据上一个元素提供的下一个元素的地址来找到下一个元素。所以头指针head
是必须存在的,如果没有头指针,则整个链表都无法访问。
struct student{
int num;
float score;
struct Student *next;//next是一个指针变量,用来指向结构体变量
};
next指针用来存放下一结点的地址,所以,不必要知道各个结点的地址,只要保证下一个结点的地址存放到前一个结点的成员next中即可。
建立简单的静态链表
静态链表的结点都是在程序中定义的,不是临时开辟的,用完后不需要手动释放。
#include<stdio.h>
struct Student{
int num;
float score;
struct Student *next;
};
int main(){
struct Student a,b,c,*head,*p;
a.num=1,a.score=89;//将每一个结点的地址,赋值给上一个结点的next成员。这样就可以把结点都连起来了。
b.num=2,b.score=88;
c.num=3,c.score=87;
head=&a;
a.next=&b;
b.next=&c;
c.next=NULL;
p=head;//使用p指针指向头指针,同时指向第一个结点。下面的操作用p指针来完成,head标记了链表的地址后,就不需要改变了。
while(p)//此处p和p!=NULL的意义相同。
{
printf("学号为%d,成绩为%f\n",p->num,p->score);
p=p->next;//此处含义为遍历链表。让p指针每次都后移一个节点,然后输出p指针指向的结点的数据。
//输出完C结点以后,p再下移就为NULL,循环终止。
}
return 0;
}
一般情况下头结点只需要标记链表的地址就够了,头结点不需要频繁的操作,想要操作链表,就定义一个新的指针变量p来指向头结点,然后用p来操作链表。
动态链表
动态链表是在程序运行中从无到有的建立起一个链表,即一个一个的开辟结点和输入各个结点数据。
创建头结点
struct Student *creat(){
struct Student *head;
head=(struct Student *)malloc(sizeof(struct Student));
if(head==null){
printf("头节点申请失败\n");
}else{
head->next==null;//创建头结点,结构体指针head
}
return head;
}
在main函数内定义结构体指针变量p,然后把p和head指向一起,就是指向头结点。在增删查改操作中,只需要把p当作参数传入增删查改的函数中。相当于把头结点传了进去,就进行后续的操作。
creat函数的作用是申请一个头节点,然后用head指针指向头节点。creat函数的返回值是头节点的地址。用malloc申请完空间后,head指针存放的就是头结点的地址。
头指针的意义
访问链表时,总要知道链表存储在什么位置(从何处开始访问),由于链表的特性(next指针),知道了头指针,那么整个链表的元素都能够被访问,也就是说头指针是必须存在的。
增加
void init_data(stu *p)
{
int n,i,a;
stu *q = NULL;
printf("请输入要添加的学生的个数: ");
scanf("%d", &n);
for(i = 0; i< n; i++){//通过for循环控制添加人数
q = (struct Student *)malloc(LEN);//每次都新建一个结点q并开辟空间
printf("请输入第%d个学生的学号\n", i+1);
scanf("%d", &q->num);
printf("请输入第%d个学生的姓名\n", i+1);
scanf("%s", q->name);
printf("请输入第%d个学生的性别\n", i+1);
scanf("%s", q->sex);
printf("请输入第%d个学生的成绩\n", i+1);
scanf("%d", &q->score);
p->next = q;//把结点p和新开辟的结点q连接起来
p = p->next;//遍历
}
q->next = NULL;//尾结点的指针域为空
printf("信息添加成功!");
}
增加功能中函数参数p是头节点的地址,新定义q为要添加的新的结点。用for循环控制添加人数,每次都给q分配空间,然后输入新建结点的数据,p->next=q是把新建的结点q和前一个结点p连接起来。 p=p->next是把p指针指向了新建的结点q。在进行下一次循环时,又会新建一个结点q,然后又进行上述操作。
删除
void Delete(stu *p)//删除
{
stu *q=p->next;
stu *l=p;
int id;
int count=0;
printf("请输入要删除学生的学号:");
scanf("%d",&id);
while(q)
{
if(q->num==id)//判断如果输入的要删除的学号和结点里面的学号相等,则进入循环
{
l->next=q->next;//让L的下一个结点指向q的下一个结点,相当于把q所在结点的前后连线断开
free(q);//释放结点
count = 1;
break;
}
q=q->next;//如果if循环不满足条件,则q和l同时往下遍历,进行下一次循环
l=l->next;
};
if(count==1)//定义count作为标志物,如果删除成功,count的值改变,否则不变。
{
printf("信息删除成功");
}
else
{
printf("删除失败,请输入正确的学号");
};
}
头结点内没存放信息时,删除的时候不用另外判断是否是第一个元素。操作起来比较方便。
查找
void find(stu *p)
{
int id;
stu *q=p->next;
printf("请输入需要查找学生的学号:");
scanf("%d",&id);
int count=0;
while(q)//用q来判断链表是否结束
{
if(q->num==id)
{
printf("查找成功!\n");
printf("学号:%d\t",q->num);
printf("姓名:%s\t",q->name);
printf("性别: %s\t",q->sex);
printf("分数: %d\t",q->score);
count=1;//定义一个标志,如果查找成功则改变其值。
break;
}
q=q->next;//遍历链表
}
if(count==0)
printf("信息查找失败!");
}
修改
void change(stu *p)
{
stu *q=p->next;
int id,x=0;
printf("请输入需要修改学生的学号:");
scanf("%d",&id);
while(q)
{
if(q->num==id)
{
printf("请输入修改后的学号\n");
scanf("%d",&q->num);
printf("请输入修改后的姓名\n");
scanf("%s",q->name);
printf("请输入修改后的性别\n");
scanf("%s",&q->sex);
printf("请输入修改后的成绩\n");
scanf("%d",&q->score);
x=1;
}
q=q->next;
}
if(x==1)
{
printf("信息修改成功!");
}
else
printf("修改失败,请输入正确的学号!");
}
查找和删除,修改函数思想基本一致,都是先找到对应的结点,然后再进行后续操作。
链表的操作稍有难度,重点是各个功能的指针移动和结点关系。理解了这部分,对后面的学习会有较大的帮助。
e);
x=1;
}
q=q->next;
}
if(x==1)
{
printf(“信息修改成功!”);
}
else
printf(“修改失败,请输入正确的学号!”);
}
> 查找和删除,修改函数思想基本一致,都是先找到对应的结点,然后再进行后续操作。
链表的操作稍有难度,重点是各个功能的指针移动和结点关系。理解了这部分,对后面的学习会有较大的帮助。