链表

链表

链表是一种常见的重要的数据结构,是一种动态进行存储分配的结构。

前面学过的数组存放数据时,有一定的局限性,不仅只能存放同一类型的数据,还要事先定义固定的数组长度。在某些情况下会造成内存浪费。

而链表则没有这种缺点,链表是根据需要来开辟内存单元,需要多少,就可以开辟多少,不会造成内存浪费。

最简单的链表(单向链表)的结构。
在这里插入图片描述

上图中的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(“修改失败,请输入正确的学号!”);
}


> 查找和删除,修改函数思想基本一致,都是先找到对应的结点,然后再进行后续操作。

链表的操作稍有难度,重点是各个功能的指针移动和结点关系。理解了这部分,对后面的学习会有较大的帮助。





  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值