单链表的增删查改

一.顺序表的优缺点

上一篇博客讲解了顺序表,我们来回顾一下顺序表的优缺点

缺点:
(1). 中间/头部的插入删除,时间复杂度为O(N)

(2). 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

(3). 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

优点:
(1). 可用下标进行随机访问

(2).缓存命中率比较高

在访问内存中的一个数据时,不会只加载一个数据到缓存,而是从这个数据开始加载一段数据到缓存,即预加载操作

由于数组中的数据是连续存储的,因此顺序表缓存的命中率相对于链表更高

二.链表

1.链表的概念和结构

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

在这里插入图片描述

2.链表的实现

SList.h 文件中功能的声明

//  无头+单向+非循环链表增删查改实现
typedef int SLTDateType;
typedef struct SListNode
{
 	SLTDateType data;
 	struct SListNode* next; 
 }SListNode;
// 动态申请一个节点
SListNode* CreateSListNode(SLTDateType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDateType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDateType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDateType x);
// 单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDateType x);
// 单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);

说明:

pList为指向第一个结点的指针,ppList为指向 pList 的二级指针

在test.c进行测试时,首先将 pList 置为了空指针

功能实现:
(1). 动态申请一个结点

使用malloc函数分配一个结构体类型大小的空间,放入数据,置空指针

// 动态申请一个结点
SListNode* CreateSListNode(SListDataType x)
{
	// 分配空间
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
	if (newNode == NULL)
	{
		printf("申请结点失败\n");
		exit(-1);
	}
	// 放入数据
	newNode->data = x;
	// 置空指针
	newNode->next = NULL;
	return newNode;
}

(2).单链表打印

// 单链表打印
void SListPrint(SListNode* pList)
{
	SListNode* cur = pList;
	while (cur != NULL)
	{
		// 打印结点数据
		printf("%d->", cur->data);
		// 不断移动cur指针,直到cur指针指向为空
		cur = cur->next;
	}
	printf("NULL\n");
}

(3).单链表尾插

首先动态申请一个结点,调用CreateSListNode函数即可,接着分两种情况讨论

1 . 未插入过数据,此时 pList为空指针,使pList指向我们新开辟的空间即可

2 . 插入过数据,pList不为空,找到尾节点,使尾结点的next指针指向我们新开辟的空间


// 单链表尾插
void SListPushBack(SListNode** ppList, SListDataType x)
{
	// 开辟结点
	SListNode* newNode = CreateSListNode(x);
	// 第一次插入数据
	if (*ppList == NULL)
	{
		*ppList = newNode;
	}
	// 插入过数据
	else
	{
		SListNode* tail = *ppList;
		while (tail->next != NULL)
		{
			// 找到尾结点
			tail = tail->next;
		}
		// 尾结点的next指针指向新开辟的空间
		tail->next = newNode;
	}
}

(4).单链表尾删

单链表尾删分为三种情况:

1 . 链表为空,不需要进行操作,直接返回

2 .链表有一个结点,释放掉当前结点,将 pList 指针置为空

3 .链表有两个及以上结点,找到尾结点,释放掉尾结点,并将尾结点之前的结点的指针域置为空

// 单链表尾删
void SListPopBack(SListNode** ppList)
{
	// 1.空结点
	if (*ppList == NULL)
	{
		return;
	}
	// 2.一个结点
	else if ((*ppList)->next == NULL)
	{
		free(*ppList);
		*ppList = NULL;
	}
	// 3.两个及以上结点
	else
	{
		SListNode* prev = NULL;
		SListNode* tail = *ppList;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;
	}
}

(5). 单链表头插

头插和尾插同样需要考虑两种情况:

1 .未插入数据,pList指针为空指针,我们新开辟一块结构体类型大小的空间,使其 next 域指向空,最后使 pList 指针指向这块空间

2 .已插入数据,新开辟一块结构体类型大小的空间,使其 next 域指向 pList 所指向的空间 , 最后使 pList 指针指向这块空间

可以看出,这两种情况操作都是一样的。

// 单链表头插
void SListPushFront(SListNode** ppList, SListDataType x) 
{
	// 链表为空和不为空操作一样
	SListNode* newNode = CreateSListNode(x);
	newNode->next = *ppList;
	*ppList = newNode;
}

(6).单链表头删

头删和尾删同样分为三种情况:

1 .链表为空,不需要进行操作,直接返回即可

2 .链表有一个结点,释放掉该结点,将 pList 指针置为空

3 .链表有两个结点及以上,释放第一个结点,使 pList 指针指向下一个结点

其中2,3情况的操作是一样的

// 单链表头删
void SListPopFront(SListNode** ppList)
{
	// 1. 链表为空
	if (*ppList == NULL)
	{
		return;
	}
	// 2. 链表有一个及以上结点
	else
	{
		SListNode* head = *ppList;
		*ppList =(*ppList)->next;
		free(head);
	}
}

(7).单链表查找

原理很简单 , 只需遍历链表即可

// 单链表查找
SListNode* SListFind(SListNode* pList, SListDataType x)
{
	SListNode* cur = pList;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

(8).单链表在pos位置后插入 x

// 单链表在pos位置后插入 x 
void SListInsertAfter(SListNode* pos, SListDataType x)
{
	assert(pos);
	SListNode* newNode = CreateSListNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}

(9).单链表在pos位置后删除 x

// 单链表在pos位置后删除 x
void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (pos->next != NULL)
	{
		SListNode* next = pos->next;
		SListNode* nextnext = next->next;
		pos->next = nextnext;
		free(next);
	}
}

三.完整代码实现

SList.c文件

#include"SList.h"
// 动态申请一个结点
SListNode* CreateSListNode(SListDataType x)
{
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
	if (newNode == NULL)
	{
		printf("申请结点失败\n");
		exit(-1);
	}
	newNode->data = x;
	newNode->next = NULL;
	return newNode;
}
// 单链表打印
void SListPrint(SListNode* pList)
{
	SListNode* cur = pList;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
// 单链表尾插
void SListPushBack(SListNode** ppList, SListDataType x)
{
	SListNode* newNode = CreateSListNode(x);
	if (*ppList == NULL)
	{
		*ppList = newNode;
	}
	else
	{
		SListNode* tail = *ppList;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newNode;
	}
}
// 单链表尾删
void SListPopBack(SListNode** ppList)
{
	// 1.空结点
	// 2.一个结点
	// 3.两个及以上结点
	if (*ppList == NULL)
	{
		return;
	}
	else if ((*ppList)->next == NULL)
	{
		free(*ppList);
		*ppList = NULL;
	}
	else
	{
		SListNode* prev = NULL;
		SListNode* tail = *ppList;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		prev->next = NULL;
	}
}
// 单链表头插
void SListPushFront(SListNode** ppList, SListDataType x) 
{
	// 链表为空和不为空操作一样
	SListNode* newNode = CreateSListNode(x);
	newNode->next = *ppList;
	*ppList = newNode;
}
// 单链表头删
void SListPopFront(SListNode** ppList)
{
	// 1. 链表为空
	if (*ppList == NULL)
	{
		return;
	}
	// 2. 链表有一个及以上结点
	else
	{
		SListNode* head = *ppList;
		*ppList =(*ppList)->next;
		free(head);
	}
}
// 单链表查找
SListNode* SListFind(SListNode* pList, SListDataType x)
{
	SListNode* cur = pList;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
// 单链表在pos位置后插入 x 
void SListInsertAfter(SListNode* pos, SListDataType x)
{
	assert(pos);
	SListNode* newNode = CreateSListNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}
// 单链表在pos位置后删除 x
void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	if (pos->next != NULL)
	{
		SListNode* next = pos->next;
		SListNode* nextnext = next->next;
		pos->next = nextnext;
		free(next);
	}
}

test.c文件

#include"SList.h"
void TestSList()
{
	SListNode* pList = NULL;
	SListPushBack(&pList, 1);
	SListPushBack(&pList, 2);
	SListPushBack(&pList, 3);
	SListPushBack(&pList, 4);

	SListPrint(pList);

	SListPopBack(&pList);
	SListPopBack(&pList);
	SListPopBack(&pList);
	SListPopBack(&pList);
	SListPopBack(&pList);

	SListPrint(pList);

	SListPushFront(&pList, 5);
	SListPushFront(&pList, 4);
	SListPushFront(&pList, 3);
	SListPushFront(&pList, 2);
	SListPushFront(&pList, 1);

	SListPrint(pList);

	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);
	SListPopFront(&pList);

	SListPrint(pList);
}
int main()
{
	TestSList();
}
  • 25
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值