线性表之单链表

目录

一,链表的概念及结构

二,单链表的实现

1.头文件

2.接口的实现

2.1 结点的申请与打印

2.2单链表的尾插与尾删

2.3单链表的头插与头删

2.4单链表的查找

2.5 pos位置的节点前的插入与删除

2.6 pos位置后的插入与删除

三,总代码


一,链表的概念及结构

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

 注意:

  1. 从上图可看出,链式结构在逻辑上是连续的,但是在物理上不一定连续
  2. 现实中的节点一般都是从堆上申请出来的
  3. 从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续。

链表分类有单向或者双向,带头或不带头,循环或者不循环。但常用的有无头单向非循环链表和带头双向循环链表。

1.无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶,图的邻接表等等。

2.带头双向循环链表:结构最复杂,一般用在单独存放数据。实际中使用的链表数据结构,都是带头双向循环链表。这个结构虽然结构复杂,但使用代码实现后会发现结构会带来很多优势。

二,单链表的实现

1.头文件

我们将库文件,单链表的定义,接口的声明,放在头文件。

//Slist.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;
//动态申请一个结点
SListNode* BuySListNode(SLTDateType x);
//单链表打印
void SListPrint(SListNode* phead);
//单链表尾插
void SListPushBack(SListNode** pphead, SLTDateType x);
//单链表头插
void SListPushFront(SListNode** pphead, SLTDateType x);
//单链表头删
void SListPopFront(SListNode** pphead);
//单链表尾删
void SListPopBack(SListNode** pphead);
//单链表查找,返回查找的地址
SListNode* SListFind(SListNode* pphead, SLTDateType x);	
//pos位置前插入	,pos是链表中的一个节点的地址。
void SListInsert(SListNode** pphead,SListNode* pos, SLTDateType x);
//pos位置删除
void SListErase(SListNode** pphead, SListNode* pos);
//pos后面插入,直接从pos代表的节点地址插
void SListInsert_after( SListNode* pos, SLTDateType x);
//pos后面删除
void SListErase_after( SListNode* pos);

2.接口的实现

接口的具体实现,我们在程序中注释,哪点有不懂的评论区可以解答。

2.1 结点的申请与打印

SListNode* BuySListNode(SLTDateType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));	//用malloc申请一个结构体空间
	if (newnode == NULL)	//申请错误,提示
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;	//存入结构体中的数据
	newnode->next = NULL;	//下个节点指向空
	return newnode;	//返回新创建的数组。
}
void SListPrint(SListNode* phead)
{
	SListNode* cop = phead; //创建一个新空间cop,地址phead的数据赋值给cop
	while (cop != NULL)		//如果不为空,打印结构体中的数据,直到为空
	{
		printf("%d->", cop->data);
		cop = cop->next;
	}
	printf("NULL\n");	//打印空
}

2.2单链表的尾插与尾删

void SListPushBack(SListNode** pphead, SLTDateType x)	//一级指针地址用二级指针接收,*pphead指向结构体list的地址
{

	SListNode* ccop = BuySListNode(x);	//用ccop接收创建的结构体
	if (*pphead == NULL)	//如果list为空,直接将ccop地址的数据赋值给*pphead指向的结构体List
	{
		*pphead = ccop;
	}
	else {
		SListNode* tail = *pphead; //创建一个指针tail,指向list地址
		while (tail->next != NULL)	//当tail下一个数据是空时,停止(找尾部)
		{
			tail = tail->next;
		}

		tail->next = ccop;	//将list的下一个数据指向ccop的地址
	}
}
void SListPopBack(SListNode** pphead)	//尾删
{
	assert(pphead);
	assert(*pphead);				//暴力检查
	if ((*pphead)->next == NULL)	//判断是否剩一个数据
	{
		free(*pphead);
		*pphead = NULL;
	}
	else 
	{
		SListNode* tail = *pphead;
		while (tail->next->next)	//找到尾部tail->next->next
		{							//释放tail->next(最后一个节点)的地址
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

2.3单链表的头插与头删

void SListPushFront(SListNode** pphead, SLTDateType x)
{
	SListNode* ccop = BuySListNode(x);//用ccop接收创建的结构体
	ccop->next = *pphead;	//直接将接收的结构体下一个数据指向list的头部
	*pphead = ccop;	//然后将list的头部换成ccop的头部
}
void SListPopFront(SListNode** pphead)
{
	assert(*pphead);
	SListNode* ccop = *pphead;
	*pphead = (* pphead)->next;	//注意:*pphead优先级,->比*的优先级高
}

2.4单链表的查找

SListNode* SListFind(SListNode* pphead, SLTDateType x)	//查找
{
	SListNode* tail = pphead;
	int i = 0;
	while (tail)	//如果pphead 为空的话直接返回空,否则返回数据为x的地址
	{
		if (tail->data == x)
		{
			return tail;
		}
		tail= tail->next;
	
	}
	return NULL;

}

2.5 pos位置的节点前的插入与删除

void  SListInsert(SListNode** pphead, SListNode* pos, SLTDateType x)//在pos点前插入
{
	assert(pphead);	//防止使用接口时要的目标链表地址传错
	assert(*pphead);	//检查是否为空链表
	assert(pos);	//检查链表中pos位置的节点是否为空
	if (pos == *pphead)	//如果在第一个节点插入,直接运用头插。
	{
		SListPushFront(pphead,x);	//pphead里存储的是list的地址,*pphead指向的是list地址的数据
		return;
	}
	else
	{
		SListNode* tail = *pphead;
		while (tail->next != pos)	//找到pos点前一个节点
		{
			tail = tail->next;
		}
		SListNode* ccop = BuySListNode(x);
		ccop->next = tail->next;	//让新申请的ccop指向下一个节点
		tail->next = ccop;	//pos前一个节点指向新申请的ccop
	}

}
void SListErase(SListNode** pphead, SListNode* pos)	//在pos位置删除
{
	assert(pphead);
	assert(pos);
	assert(*pphead);
	//如果pos点为第一个节点,直接调用头删
	//否则,找到pos的位置,将(pos位置前一个节点)指向(pos节点指向的下个节点)。
	//再释放pos位置的节点
	if (*pphead == pos)	
	{
		SListPopFront(pphead);
		return;
	}
	else
	{
		SListNode* tail = *pphead;
		while (tail->next != pos)
		{
			tail = tail->next;
		}
		tail->next = pos->next;
		free(pos);
		pos = NULL;
	}

}

2.6 pos位置后的插入与删除

void SListInsert_after( SListNode* pos, SLTDateType x)	//在pos位置后插入
{
	assert(pos);
	SListNode*tail = BuySListNode(x);	//直接将(新申请的节点)指向(pos指向的下一个节点)
	tail->next = pos->next;	 //然后将pos指向的下一个节点改为新申请的空间
	pos->next = tail;
}
void SListErase_after(SListNode* pos)	//在pos位置后删除
{
	assert(pos);
	//温柔的检查
	if (pos->next == NULL)
	{
		return;
	}
	//暴力检查:assert(pos->next);
	else
	{
		SListNode* tail = pos->next;
		pos->next = tail->next;
		free(tail);
	}
}

三,总代码

总代码见码云:初阶数据结构/SListNode1 · 雨天code/CSDN1 - 码云 - 开源中国 (gitee.com)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会背雨霖铃

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值