[C语言]线性表之单链表

本文详细介绍了单链表的定义、创建节点、打印方法,以及增删查改操作,包括头插、尾插、删除节点、查找和销毁链表,同时讨论了单链表的优缺点。
摘要由CSDN通过智能技术生成

顺序表容易造成空间浪费等缺点,至此弥补这类缺点的链表应运而生。但单链表还不是常规线性表的最佳线性结构,任有许多缺点。

单链表是一个逻辑连续而空间不连续的线性结构。
单链表操作逻辑图:
单链表.gif

单链表的优缺点

优:

  1. 灵活的空间使用率。
  2. 优秀的插删效率。
  3. 没有扩容风险。

缺:

  1. 容易造成空间碎片(内存堆区)
  2. 不支持随机访问

单链表的定义和创建节点、打印

这次单链表是无头节点的单链表,所以无需顺序表似的初始化。

单链表的定义

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链 接次序实现的 。

typedef int SLTDateType; //给数据类型起别名
typedef struct SListNode
{
	SLTDateType data; //数据元素
	struct SListNode* next; //下个节点的地址
}SListNode;

单链表创建新节点

SListNode* BuySListNode(SLTDateType x) {
	SListNode* ret = (SListNode*)calloc(1, sizeof(SListNode));//申请一个节点空间
	ret->data = x;//给新节点赋值
	return ret;//返回新结点地址
}

这里创建结点空间使用calloc函数,这样创建出来的新结点所有内容为0,next则为NULL,以便下面,尾插和尾删。

单链表打印

void SListPrint(SListNode* plist) {
	SListNode* tmp = plist;
	while (tmp)
	{
		printf("%d->", tmp->data);//打印data
		tmp = tmp->next;//进入下一个节点
	}
	printf("NULL");
}

  • 打印只需要便利指针next是否为空,然后打印data即可。

单链表的增、删、查、改

增加

单链表常规添加也分为:头插和尾插两种方法,以及任意位置插入。

头插

头插分两种情况:

  1. 头指针所指向的地址有数据。
  2. 头指针所指向的地址没有数据。

第一种情况,逻辑图:
单链表头插一.gif
第二种情况,逻辑图:
单链表头插二.gif
所以我们在设计头插方法需要着重注意这两个情况

void SListPushFront(SListNode** pplist, SLTDateType x) {//考虑到第二种情况,使用二级指针即可改变外部指针链表情况。
	assert(pplist);//判断传入的二级指针是否为空指针
	SListNode* newNode = BuySListNode(x);//创建数据结点
	if (*pplist == NULL)//区分情况。
	{
		*pplist = newNode;//第二种情况给头指针指向新结点
		return;
	}
	newNode->next = *pplist;//将头指针所指向的结点赋值给newNode的next变量
	*pplist = newNode;//给头指针指向新结点
}

尾插

尾插也分两种情况:

  1. 链表里没有数据,则直接给头指针赋值新结点地址即可。
  2. 链表里拥有数据,则需要便利链表找到最后一个结点。

情况一与头插法情况二相同,所以看头插情况二逻辑图即可,这里描述第二个情况逻辑图:
单链表尾插.gif

void SListPushBack(SListNode** pplist, SLTDateType x) {
	assert(pplist);
	SListNode* newNode = BuySListNode(x);
	if (*pplist == NULL)//判断链表是否是空链表
	{
		*pplist = newNode;//给空链表指针指向新结点
		return;
	}
	SListNode* tmp = *pplist;//创建一个临时变量,用于找到尾结点
	while (tmp->next)//寻找尾结点
	{
		tmp = tmp->next;
	}
	tmp->next = newNode;//让尾结点指向新结点
}

删除

头删和尾删、以及任意位置删除。

头删

头删也是两种情况:

  1. 若是链表为空,则无需删除
  2. 链表有数据,则改变指针。

情况二 逻辑图:
单链表头删.gif

void SListPopFront(SListNode** pplist) {
	assert(pplist);
	assert(*pplist);//若链表为空,报错
	SListNode* phead = *pplist;//为注销第一个结点做准备
	*pplist = phead->next;//让头指针指向下一个结点
	free(phead);//释放第一个结点
}

尾删

头删也是两种情况:

  1. 若是链表为空,则无需删除。
  2. 链表有数据,则找到倒数第二个节点;并释放掉最后一个结点。

情况二,逻辑图:
单链表尾删.gif

void SListPopBack(SListNode** pplist) {
	assert(pplist);
	assert(*pplist);//检测单链表是否为空表
	SListNode* tmp = *pplist;//不改变链表指针,创建一个变量
	while (tmp->next->next)//寻找倒数第二个结点
	{
		tmp = tmp->next;
	}
	SListNode* Del = tmp->next;//为释放最后一个结点做准备
	tmp->next = NULL;//将倒数第二个结点做为最后一个结点
	free(Del);//释放原最后一个结点
}

查找和修改、以及任意位置的添加和删除

查找

SListNode* SListFind(SListNode* plist, SLTDateType x) {
	assert(plist);
	while (plist->data != x)//寻找数据为x的结点
	{
		plist = plist->next;
	}
	return plist;//若找到则返回该节点的地址
}				//若找不到则返回NULL

修改

修改则是在外部测试函数内修改即可

SListNode * findNode = SListFind(plsit,6);//寻找该数据的地址
if(findNode != NULL)
{
    findNode->data = 66;//修改数据。
}

任意位置的添加

逻辑图:
单链表任插.gif
由于pos位置我们可以通过查找函数来获取,所以我们只需创建结点和修改指针即可。

void SListInsertAfter(SListNode* pos, SLTDateType x) {
	assert(pos);
	SListNode* newNode = BuySListNode(x);//创建结点
	newNode->next = pos->next;//让newNode下一个结点为pos的下一个结点
	pos->next = newNode;//让pos指向newNode,即在pos后插入newNode
}
任意位置的插入的复用

头插:

void SListPushFront(SListNode** pplist, SLTDateType x) {//考虑到第二种情况,使用二级指针即可改变外部指针链表情况。
	assert(pplist);
    SListInsertAfter(*pplist,x);
}

尾插:

void SListPushBack(SListNode** pplist, SLTDateType x) {
	assert(pplist);
	SListNode* tmp = *pplist;//创建一个临时变量,用于找到尾结点
	while (tmp->next)//寻找尾结点
	{
		tmp = tmp->next;
	}
	SListInsertAfter(tmp,x);
}

任意位置的删除

只需位置即可删除
逻辑图:
单链表任删.gif

void SListEraseAfter(SListNode* pos) {
	assert(pos);
	SListNode* tmp = pos->next;//使用tmp保存待会需要释放的结点地址
	pos->next = tmp->next;//给pos的next变量指向待删除结点的下一个结点
	free(tmp);//释放需要删除的结点
}
任意位置的删除的复用

头删:

void SListPopFront(SListNode** pplist) {
	assert(pplist);
	assert(*pplist);//若链表为空,报错
	SListEraseAfter(*pplist);
}

尾删:

void SListPopBack(SListNode** pplist) {
	assert(pplist);
	assert(*pplist);//检测单链表是否为空表
	SListNode* tmp = *pplist;//不改变链表指针,创建一个变量
	while (tmp->next->next)//寻找倒数第二个结点
	{
		tmp = tmp->next;
	}
	SListEraseAfter(tmp);
}

销毁单链表

遍历结点,在遍历的过程中挨个释放结点。
逻辑图:
单链表销毁.gif

void SListDestroy(SListNode* plist) {
	while (plist)
	{
		SListNode* fe = plist;//保存待释放的结点
		plist = plist->next;//更新循环调节,plist走向下个结点
		free(fe);//释放结点。
	}
}

附原码:

#include "SListNode.h"


SListNode* BuySListNode(SLTDateType x) {
	SListNode* ret = (SListNode*)calloc(1, sizeof(SListNode));
	ret->data = x;
	return ret;
}


void SListPrint(SListNode* plist) {
	SListNode* tmp = plist;
	while (tmp)
	{
		printf("%d->", tmp->data);
		tmp = tmp->next;
	}
	printf("NULL");
}


void SListPushBack(SListNode** pplist, SLTDateType x) {
	assert(pplist);
	SListNode* newNode = BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newNode;
		return;
	}
	SListNode* tmp = *pplist;
	while (tmp->next)
	{
		tmp = tmp->next;
	}
	tmp->next = newNode;
}


void SListPushFront(SListNode** pplist, SLTDateType x) {
	assert(pplist);
	SListNode* newNode = BuySListNode(x);
	if (*pplist == NULL)
	{
		*pplist = newNode;
		return;
	}
	newNode->next = *pplist;
	*pplist = newNode;
}


void SListPopBack(SListNode** pplist) {
	assert(pplist);
	assert(*pplist);
	SListNode* tmp = *pplist;
	while (tmp->next->next)
	{
		tmp = tmp->next;
	}
	SListNode* Del = tmp->next;
	tmp->next = NULL;
	free(Del);
}


void SListPopFront(SListNode** pplist) {
	assert(pplist);
	assert(*pplist);
	SListNode* phead = *pplist;
	*pplist = phead->next;
	free(phead);
}


SListNode* SListFind(SListNode* plist, SLTDateType x) {
	assert(plist);
	while (plist->data != x)
	{
		plist = plist->next;
	}
	return plist;
}


void SListInsertAfter(SListNode* pos, SLTDateType x) {
	assert(pos);
	SListNode* newNode = BuySListNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}


void SListEraseAfter(SListNode* pos) {
	assert(pos);
	SListNode* tmp = pos->next;
	pos->next = tmp->next;
	free(tmp);
}


void SListDestroy(SListNode* plist) {
	while (plist)
	{
		SListNode* fe = plist;
		plist = plist->next;
		free(fe);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值