【数据结构】C语言实现链表的相关操作

链表

概念与讨论

以链式结构存储的线性表称之为线性链表,线性链表中逻辑上相邻的数据元素的存储空间可以是不连续的,为表示逻辑上的顺序关系,对线性链表中的每个数据元素除存储本身的信息之外,还需存储其后继的地址(即用指针表示逻辑关系)。线性链表中的每个元素(由数据域和指针域构成)称为结点(node)。
在这里插入图片描述

首元结点:链表中存储第一个数据的结点

头结点:在首元结点前附设的一个结点(不存储数据,指针指向首元结点)

头指针:指向链表中第一个结点的指针(有头结点就指向头结点,无头结点则指向首元结点)
在这里插入图片描述

链表可以带头结点,也可以不带头结点,推荐使用带头结点的链表

如何表示空表?

不带头结点时,头指针为空表示空表

带头结点时,头结点的指针域为空表示空表

为什么在链表设置头结点?

  1. 便于处理首元结点,首元结点的地址保存在头结点的指针域中,所以对链表上首元结点的操作和之后的其他结点一样,无需特殊处理
  2. 便于统一处理空表和非空表,无论链表是否为空,头指针都是指向头结点的非空指针。

头结点的数据域内装的是什么?

头结点的数据域可以为空,也可以存放线性表长度等附加消息,但此结点不能计入链表长度值

链表的特点

  1. 结点在内存中的位置是任意的,即逻辑上相邻的数据元素在物理地址上不一定相邻
  2. 访问时只能通过头指针进入链表,并通过每个结点的指针域依次向后顺序扫描其余结点,所以寻找第一个结点和最后一个结点所花费的时间是不等的

链表类型

根据指针域的不同,链表的表示形式也不同,最普通的链表是单链表,其指针域为一个指向后继结点的指针

除此以外还有循环链表和双链表,后文中会有介绍,以下内容都是关于单链表,如无特殊说明,后文中的链表指的就是单链表

表示

带头结点的单链表

单链表是由表头(这里的表头指的是头结点)唯一确定的,因此单链表可以用头指针的名字来命名,若头指针的名字是 L,则把该单链表称为表 L

typedef int ElemType;
typedef struct LNode {
	ElemType data;
	struct LNode* next;
}LNode, * LinkList;

LNode:结构体,表示单链表的结点

data:存储的数据

next:结点指针,指向下一个结点

LinkList:头指针,用来表示单链表

初始化

生成一个结点为头结点,头指针指向头结点,此时单链表为空表,将头结点的指针域置空

返回NULL表示初始化失败,非NULL则表示初始化成功

LinkList init(void)
{
	LinkList L = (LNode*)malloc(sizeof(LNode));
	if (L == NULL)
	{
		return NULL;
	}
	L->next = NULL;
    L->data = 0;	//头结点的数据域有没有初始化都可以
	return L;
}

主函数main中的操作

	LinkList L = NULL;
	L = init();

在这里插入图片描述

插入

在结点 n1 之后插入一个值为 x 的新结点 n2
在这里插入图片描述

先让结点 n2 的指针域指向结点 n1 的后继结点

再让结点 n1 的指针域指向结点 n2

参数:单链表 L,结点 n1,新结点 n2 的值 x

void insert(LinkList L, LNode* n1, ElemType x)
{
	LNode* n2 = (LNode*)malloc(sizeof(LNode));
	if (n2 == NULL)
	{
		return;
	}
	n2->data = x;

	n2->next = n1->next;
	n1->next = n2;
}

删除

在链表中删除结点 n
在这里插入图片描述

先找到要删除的结点 n 的前驱结点 p

再让结点 p 的指针域指向结点 n 的后继结点

释放结点 n 的内存空间

参数:链表 L,要删除的结点 n

void del(LinkList L, LNode* n)
{
	LNode* p = L;
	while (p->next != n)
	{
		p = p->next;
	}

	p->next = p->next->next;
	free(n);
}

访问

访问链表中索引为 index 的结点(index 大于等于1,首元结点的 index 值为1)

参数:链表 L,索引 index

LNode* access(LinkList L, int index)
{
	LNode* p = L;
	int i = 0;
	for (i = 0; i < index; i++)
	{
		p = p->next;
  		if (p == NULL)
		{
			return NULL;
		}
	}
	return p;
}

判断 p == NULL 可有可不有,有的话,作用在于防止访问到链表之后的空间

查找

查找链表中第一个值为 x 的结点,返回该结点在链表中的索引值(索引从首元结点开始,值大于等于1)

参数:链表 L,数据 x

返回值:返回值为索引值,若返回0,表示找不到

int find(LinkList L, ElemType x)
{
	LNode* p = L;
	int i = 1;
	p = p->next;
	while (p != NULL)
	{
		if (p->data == x)
		{
			return i;
		}
		p = p->next;
		i++;
	}
	return 0;
}

是否为空

判断链表是否为空

参数:链表 L

返回值:返回1表示链表为空,返回0表示链表不为空

int isEmpty(LinkList L)
{
	LNode* p = L;
	if (p->next == NULL)
	{
		return 1;
	}
	return 0;
}

求表长

求链表表长(从首元结点开始计算结点个数)

参数:链表 L

返回值:返回值为链表长度

int get_length(LinkList L)
{
	LNode* p = L;
	int i = 0;
	p = p->next;
	while (p != NULL)
	{
		p = p->next;
		i++;
	}
	return i;
}

清空

清空单链表,只保留头结点和头指针

参数:链表 L

void clear(LinkList L)
{
	LNode* p = L;
	LNode* pFree = L;
	p = p->next;
	pFree = p;

	while (p != NULL)
	{
		p = p->next;
		free(pFree);
		pFree = p;
	}
	L->next = NULL;
}

注意最后要将头结点的指针域指向 NULL

销毁

销毁单链表,所有结点,包括头结点全部销毁,头指针置 NULL

参数:链表 L

返回值:返回 NULL,在主函数中将头指针置 NULL

LinkList destroy(LinkList L)
{
	LNode* p = L;
	LNode* pFree = L;

	while (p != NULL)
	{
		p = p->next;
		free(pFree);
		pFree = p;
	}
	return NULL;
}

打印

查看链表中各元素的数据

void print_LinkList(LinkList L)
{
	LNode* p = L->next;

	while (p != NULL)
	{
		printf("%d ", p->data);
		p = p->next;
	}
	printf("\n");
}

建立链表

头插法

创建新结点,新结点的指针域指向链表的首元结点,头结点的指针域指向新结点,新结点成为新的首元结点
在这里插入图片描述

void creat_head(LinkList L)
{
	ElemType x = 0;
	ElemType flag = -999;
	printf("input x(ElemType:int)(flag == -999):");
	scanf("%d", &x);
	while (x != flag)
	{
		LNode* p = (LNode*)malloc(sizeof(LNode));
		if (p == NULL)
		{
			return;
		}
		p->data = x;

		p->next = L->next;
		L->next = p;
		printf("input x(ElemType:int)(flag == -999):");
		scanf("%d", &x);
	}
}

尾插法

创建新结点,新结点的指针域指向NULL,首元结点的指针域指向新结点

需要设立一个尾指针,来指向链表中的最后一个结点
在这里插入图片描述

void creat_tail(LinkList L)
{
	LNode* r = L;	//尾指针,指向链表中的最后一个结点

	ElemType x = 0;
	ElemType flag = -999;
	printf("input x(ElemType:int)(flag == -999):");
	scanf("%d", &x);
	while (x != flag)
	{
		LNode* p = (LNode*)malloc(sizeof(LNode));
		if (p == NULL)
		{
			return;
		}
		p->data = x;

		p->next = NULL;
		r->next = p;
		r = p;
		printf("input x(ElemType:int)(flag == -999):");
		scanf("%d", &x);
	}
}

链表类型

单向链表:即单链表,指针域指向下一结点

循环链表:表中最后一个结点的指针域指向头结点(如果不设头结点,则指向首元结点),循环链表通常不使用头指针,而是用一个尾指针来表示循环链表,尾指针指向循环链表中最后一个结点

双向链表:在单链表的每个结点里再增加一个指向其直接前驱的指针域 prior,这样链表中就有了两个不同方向的链

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值