数据结构——链表

🌈个人主页@ꪔ小林Y

个人专栏《C++小白闯关日记》《C语言小白闯关日记》《数据结构入门------从原理到实战》

🍀代码信条 :每一行代码都是成长的脚印👣,每一次调试成功都是对坚持的回应

目录

学习完上节可知,顺序表的存储空间是静态分配的,在程序执行前必须明确规定它的存储规模,设定过大造成浪费,设定过小,造成溢出,且顺序表在头部/中间的插入操作所需的时间复杂度为O(N),可见若对线性表的长度或存储规模难以估计时,则不宜采用顺序表。那么有没有一种数据结构头部插入删除的 时间复杂度为O(1),且不需要增容且不存在空间浪费 呢?
因此就有了链表的学习。

链表

链表可分为单链表循环单链表双向链表

单链表

1.逻辑结构:线性的 ;物理结构:不一定是线性的

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

链表由一个一个的节点组成,节点有两个组成部分:保存的数据指针 (始终保存下一个节点的地址)

因而在定义链表的数据结构时就是定义结点的结构。

1.链表的定义

  • 头文件"SList.h"
c 复制代码
#include<stdio.h>
#include<stdlib.h>
//链表的结构
typedef int SLTDataType;
struct SListNode
{
	SLTDataType data;
	struct SListNode* next;//指向下一个结点的地址
};
typedef struct SListNode SLTNode;
//打印链表
void SLTPrint(SLTNode* phead);
  • 实现文件"SList.c"
c 复制代码
#include"SList.h"
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur != NULL)
	{
		printf("%d -> ", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
  • 测试文件"test.c"
c 复制代码
#include"SList.h"
int test01()
{
	//创建一个链表
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;
	node2->data = 2;
	node3->data = 3;
	node4->data = 4;
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;
	//打印链表
	SLTNode* plist = node1;
	SLTPrint(plist);
}
int main()
{
	test01();
	return 0;
}

2.在链表中插入数据

(1)尾插:

phead始终指向头节点,创建一个新变量pcur循环遍历链表查找尾节点;如果链表为空,则不需要找尾巴,直接让phead指向newnode,所以这里需要判断一下链表是否为空

  • 头文件"SList.h"
c 复制代码
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
  • 实现文件"SList.c"
c 复制代码
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
//这里是传址调用,因而要使用二级指针
{
	assert(pphead);
	//申请新节点
	SLTNode* newnode = SLTBuyNode(x);
	//链表为空
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else {
		SLTNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			ptail = ptail->next;
		}
		//找到了尾节点ptail newnode
		ptail->next = newnode;
	}
 }
  • 测试文件"test.c"
c 复制代码
void test02()
{
	//创建空链表
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	//形参的改变要改变实参
	// 因而注意这里传的是地址,要加&
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
}
int main()
{
	test02();
	return 0;
}

时间复杂度O(N)

(2)头插:
  • 头文件"SList.h"
c 复制代码
void SLTPushFront(SLTNode** pphead, SLTDataType x);
  • 实现文件"SList.c"
c 复制代码
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//申请新节点
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next=*pphead;
	*pphead = newnode;
}
  • 测试文件"test.c"
c 复制代码
void test02()
{
	//创建空链表
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPrint(plist);
	SLTPushFront(&plist, 2);
	SLTPrint(plist);
	SLTPushFront(&plist, 3);
	SLTPrint(plist);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);
}
int main()
{
	test02();
	return 0;
}

时间复杂度O(1)

3.在链表中删除数据

(1)尾删

不仅要找尾节点用ptail标记,还要找尾节点的前一个指针用prev标记

尾删时只有一个节点则需要特殊处理,不需要要创建新指针,直接释放掉即可

  • 头文件"SList.h"
c 复制代码
void SLTPopBack(SLTNode** pphead);
  • 实现文件"SList.c"
c 复制代码
//尾删
void SLTPopBack(SLTNode** pphead)
{
	//链表不能为空
	assert(pphead && *pphead);
	//链表只有一个结点的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else {
		SLTNode* prev = NULL;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}
  • 测试文件"test.c"
c 复制代码
void test02()
{
	//创建空链表
	SLTNode* plist = NULL;
	//尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
	//尾删
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
}
int main()
{
	//test01();
	test02();
	return 0;
}

时间复杂度O(N)

(2)头删
  • 头文件"SList.h"
c 复制代码
//头删
void SLTPopFront(SLTNode** pphead);
  • 实现文件"SList.c"
c 复制代码
//头删
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}
  • 测试文件"test.c"
c 复制代码
void test02()
{
	//创建空链表
	SLTNode* plist = NULL;
	//尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
	//头删
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
}
int main()
{
	test02();
	return 0;
}

时间复杂度O(N)

4.在链表中查找数据

  • 头文件"SList.h"
c 复制代码
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
  • 实现文件"SList.c"
c 复制代码
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	//从第一个链表遍历至整个链表查找数据
	SLTNode* pcur =phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
		}
	//未找到
	return NULL;
	
}
  • 测试文件"test.c"
c 复制代码
void test02()
{
	//创建空链表
	SLTNode* plist = NULL;
	//尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
	//查找
	SLTNode* pos=SLTFind(plist, 3);
	if (pos)
	{
		printf("找到了\n");
	}
	else {
		printf("未找到\n");
	}
}
int main()
{
	test02();
	return 0;
}

5.在指定位置之前插入数据

若插入点在头节点前,直接头插,不需要前后建立联系:

  • 实现文件"SList.c"
c 复制代码
//在指定位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && pos);
	SLTNode* newnode = SLTBuyNode(x);
	//pos指向头节点
	if (pos == *pphead)
	{
		//头插
		SLTPushFront(pphead, x);
	}
	else{
		//找pos的前一个节点
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//prev newnode pos
		prev->next = newnode;
		newnode->next = pos;
	}
}
  • 测试文件"test.c"
c 复制代码
void test02()
{
	//创建空链表
	SLTNode* plist = NULL;
	//尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
		//指定节点前插入
	SLTNode* pos=SLTFind(plist, 3);
	SLTInsert(&plist, pos, 100);
	SLTPrint(plist);
}
int main()
{
	test02();
	return 0;
}

运行:

时间复杂度O(N)

6.在指定位置之后插入数据

  • 实现文件"SList.c"
c 复制代码
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//pos newnode pos->next
	newnode->next = pos->next;
	pos->next = newnode;
}
  • 测试文件"test.c"
c 复制代码
void test02()
{
	//创建空链表
	SLTNode* plist = NULL;
	//尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
		//指定节点后插入
	SLTNode* pos=SLTFind(plist, 3);
	SLTInsertAfter( pos, 100);
	SLTPrint(plist);
}
int main()
{
	test02();
	return 0;
}

运行结果:

时间复杂度O(1)

7.删除pos节点

若pos刚好是头节点,只需头删

  • 实现文件"SList.c"
c 复制代码
//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && pos);
	//pos刚好就是头节点------直接头删
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else {
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		//prev pos pos->next
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
  • 测试文件"test.c"
c 复制代码
void test02()
{
	//创建空链表
	SLTNode* plist = NULL;
	//尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
		//删除pos节点
	SLTNode* pos=SLTFind(plist, 3);
	SLTErase(&plist,pos);
	SLTPrint(plist);
}
int main()
{
	test02();
	return 0;
}

运行结果:

8.删除pos之后的节点

  • 实现文件"SList.c"
c 复制代码
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	//注意pos的下一个节点也不能为空
	//pos del del->next
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}
  • 测试文件"test.c"
c 复制代码
void test02()
{
	//创建空链表
	SLTNode* plist = NULL;
	//尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
		//删除pos后节点
	SLTNode* pos=SLTFind(plist, 3);
	SLTEraseAfter( pos);
	SLTPrint(plist);
}
int main()
{
	test02();
	return 0;
}

运行结果:

9.销毁链表

一个一个销毁:

next指针指向下一个节点,释放pcur指向的节点,释放完成,pcur继续往下走,跳出循环

next指针是在循环里定义的,跳出循环,自动销毁。函数执行结束,pcur自动销毁

  • 实现文件"SList.c"
c 复制代码
//销毁链表
void SListDestroy(SLTNode** pphead)
{
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}
  • 测试文件"test.c"
c 复制代码
void test02()
{
	//创建空链表
	SLTNode* plist = NULL;
	//尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
		//销毁链表
	SLTNode* pos=SLTFind(plist, 3);
	SListDestroy(&plist);
	SLTPrint(plist);
}
int main()
{
	test02();
	return 0;
}

运行结果:

🎊本期数据结构的内容就结束了。如果文中有表述不准的地方,或是你有更清晰的理解思路,强烈欢迎在评论区留言交流------ 技术路上多碰撞,才能更快进步

觉得内容对你有帮助的话,别忘了点赞❤️➕收藏🌟,方便后续回顾复习;想跟着一起系统学习数据结构的朋友,也可以点击关注,下一期我们会聚焦更进一步的学习,带你从理论走进实操。下期不见不散✌️

相关推荐
自信的小螺丝钉3 小时前
Leetcode 148. 排序链表 归并排序
算法·leetcode·链表·归并
hn小菜鸡4 小时前
LeetCode 2570.合并两个二维数组-求和法
数据结构·算法·leetcode
滋滋不吱吱5 小时前
栈的进阶篇
数据结构·算法·leetcode
YQ_ZJH5 小时前
Java List列表创建方法大总结
java·开发语言·数据结构·算法·list
Rsingstarzengjx6 小时前
【算法】【数学】【质数】质数相关 leetcode 204
数据结构·算法
爱编程的化学家7 小时前
代码随想录算法训练营第21天 -- 回溯4 || 491.非递减子序列 / 46.全排列 /47.全排列 II
数据结构·c++·算法·leetcode·回溯·全排列·代码随想录
_不会dp不改名_7 小时前
leetcode_138 随机链表的复制
算法·leetcode·链表
小欣加油8 小时前
leetcode 129 求根节点到叶节点数字之和
数据结构·c++·算法·leetcode
LeicyII8 小时前
9.B树和B+树的区别【面试题】
数据结构·b树