C语言链表

文章讨论了数组的固定长度限制以及其内存管理问题,然后介绍了链表,包括单链表、双链表和它们在数据管理中的优势,如动态增长、快速插入删除,以及头插法、尾插法创建链表的方法。还涵盖了链表的遍历、销毁和查找结点等操作。
摘要由CSDN通过智能技术生成

数组的局限性

使用数组时必须事先定义好数组长度(即元素个数),这个长度一经定义就是固定不变的。如果事先难以确定元素个数,则必须把数组长度定义得足够大,这将占用许多内存。另一方面,在数组中若要插入或删除某个元素,需要移动插入点或删除点后面所有的数组元素,这将运行大量时间。

链表的概念

链表是一种存储空间能动态进行增长或缩小的数据结构。

链表主要用于两个目的:

一是建立不定长度的数组。

二是链表可以在不重新安排整个存储结构的情况下,方便且迅速地插入和删除数据元素。

由于这些原因,链表广泛地运用于数据管理中。

链表原理

链表实际上是一些以指针为链接的结构体。每一个结点都是一个结构体,其中包含着所要储存的数据(数据域),和一个指针,指向下一个结点。最后一个结点的指针指向NULL。

以一个储存学号和姓名的结构体为例:

typedef struct student
{
   int num;
   char name[20];
   struct student* next;//next是结构体指针
}STU;

链表的分类

单链表和双链表

链表分为单链表和双链表两种。上图所示的就是单链表的结构。

双链表与单链表的区别是:双链表中不仅存放了一个指向下一个节点的指针,还存放了一个指向前一个节点的指针。

typedef struct student
{
   int num;
   char name[20];
   struct student* next;//next是结构体指针
   struct stuent* prev;
}STU;

头结点的prev为NULL,尾结点的next为NULL。

双链表的优点:与单链表相比,双链表可以从前向链和后向链遍历整个链表,这样简化了链表排序方法及运算。同时,当一个链数据受损(如数据库设备故障)时可以根据另一个链来恢复它。

循环链表

若单链表的尾结点指向的不是NULL,而是头结点,则为循环单链表。

若双链表的头结点的prev指向尾结点,尾结点的next指向头结点,则为循环双链表。

链表的创建

因为所有链表的创建和使用大同小异,下面以单链表为例。

利用动态内存分配可以产生新结点的内存单元。

例如:

STU* p;//结构体指针,在这里可以称为是链表指针
p=(STU*)malloc(sizeof(STU));

调用malloc realloc 内存分配函数和free释放函数时,需要包含命令:
#include <stdlib.h>

int main()
{
	int n;
	scanf("%d",&n); 
	STU* head=NULL;//开始的时候头结点为空 
	STU* new1=NULL;//用来指向新的结点 
	for(;n>0;n--)
	{
		new=(STU*)malloc(sizeof(STU));//为新节点申请一块空间
		printf("请输入数据:"); 
		scanf("%d %s",&new1->num,&new1->name);
		Link(&head,new1);//调用连接函数,把新的结点连接到链表。 
                        //连接函数的实现见下文
	 } 
	return 0;
}

链表的连接分为头插法和尾插法。两者只是在将新的结点链接到链表时的连接方式不同。

尾插法创建链表

尾插法即把新的结点链接到链表的末尾。如下图:

代码如下:

void Link(STU** head,STU* new1);//连接函数,把新的结点连接到已有的链表 (尾插法)
int main()
{
	int n;
	scanf("%d",&n); 
	STU* head=NULL;//开始的时候头结点为空 
	STU* new1=NULL;//用来指向新的结点 
	for(;n>0;n--)
	{
		new1=(STU*)malloc(sizeof(STU));//为新节点申请一块空间
		printf("请输入数据:"); 
		scanf("%d %s",&new1->num,&new1->name);
		Link(&head,new1);//调用连接函数,把新的结点连接到链表。 
	 } 
	return 0;
}
//new是特殊字,故new1代替 
void Link_tail(STU** head,STU* new1)//连接函数,把新的结点连接到已有的链表 (尾插法) 
{
	STU* move=*head;
	if(*head==NULL)//当第一个结点加入链表时 
	{
		*head=new1;
		new1->next=NULL; 
	}
	else
	{
		while(move->next!=NULL)//move的下一个结点不是NULL,说明还没到尾结点 
		{
			move=move->next;
		}
		//经过移动后move指向尾结点
		move->next=new1;
		new1->next=NULL;
	}
}

注意这里用了指向指针的指针,即二级指针。因为在传参的时候,如果使用一级指针,函数里的head只是head的一个副本,从而不能对head进行修改。因此使用二级指针,传递的时head的地址,从而达到更新head的目的。

头插法创建链表

头插法是将新的结点加在头结点之后,第二个结点之前。

原来:

插入后:

新结点作为链表的第一个元素。因此,头结点不储存数据。

头插法的连接代码如下:

void Link_head(STU** head,STU* new1)//连接函数,把新的结点连接到已有的链表 (头插法) 
{
	new1->next=*head->next;
	*head->next=new1;
}

链表的遍历

链表遍历算法的实现步骤为:

①令指针move指向L的开始结点。

②若move为NULL,表示已到链尾,遍历结束。

③令move指向直接后继结点,即move=move->next。重复②~③步骤直至遍历结束。

在尾插法时已经用过了数组的遍历。

代码如下:

      STU* move=head;//move开始等于头结点
       while(move!=NULL)//到达尾结点的时候退出循环
		{
            //根据需要进行操作(省略)
			move=move->next;//到达尾结点,move=NULL
		}

应用遍历输出链表

void print_Link(STU* head)
{
	STU* move=head;
	while(move!=NULL)
	{
		printf("%d %s",move->num,move->name);
		move=move->next;
	}
}

此时不需要修改head,用一级指针即可。

销毁链表

void Link_free(STU** head)
{
	STU* move=head;
	while(*head!=NULL)
	{
		move=*head;
		*head=move->next;//让head保存下一个结点 
		free(move);//释放move指向的动态空间 
	}
}

查找结点

//返回第i个结点 
int search_elm(STU* head,int i,STU** ans)
{
	STU* move=head;
	while(move!=NULL && i>0)
	{
		move=move->next;
		i--;
	}
	if(move==head || move==NULL)  return 0;//第i个结点不存在
	*ans=move;
	return 1;//操作成功返回1
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值