2-链表操作

1. 链表的基本概念

  1. 数组的特点
  • 缺点
    (1)数组的大小不够灵活,其元素个数在定义时必须确定。若定义太小则不够用时无法改变数组大小,若定义太大则会导致内存浪费。
    (2)对于元素的增删操作效率太低。当堆数组中某个元素进行增删时,其后面元素必须跟着整体后移或前移;
  • 优点
    (1)读写操作的访问速度快。因为其在内存中连续分布,读写其他元素时只需偏移指针即可;
    (2)占用内存小,每个元素只占用本身的内存空间。链表则需要下一节点的地址。
  1. 链表的组成结构
  • 链表由一个或多个节点组成,每个节点包含数据域和指针域两部分。
    (1)数据域存储数据;
    (2)指针域存储下一个节点的地址;
    在这里插入图片描述
  1. 链表的优点
    (1)链表中可以在任何两节点之间进行增删,而不影响其他节点;
    (2)节点的增删只需要将上一个节点指针域指向新节点,新节点指针域指向下一节点即可。
    在这里插入图片描述
  2. 节点的表示方法
struct LinkNode
{
	int num;				//数据域
	struct LinkNode* next;	//指针域
}

2. 链表的分类

  1. 链表的分类
  • 动态链表和静态链表
    (1)动态链表分配在堆区
struct LinkNode 
{
	int num;
	struct LinkNode* next;
};
/*创建静态链表*/
void test01()
{
	//1.初始化节点
	struct LinkNode node1 = { 10, NULL };
	struct LinkNode node2 = { 20, NULL };
	struct LinkNode node3 = { 30, NULL };
	struct LinkNode node4 = { 40, NULL };
	struct LinkNode node5 = { 50, NULL };
	//2.创建节点之间关系
	node1.next = &node2;
	node2.next = &node3;
	node3.next = &node4;
	node4.next = &node5;
	//3.遍历节点
	struct LinkNode* pCurrent = &node1;
	while (NULL != pCurrent)
	{
		printf("%d\n", pCurrent->num);
		pCurrent = pCurrent->next;
	}
}
int main(int argc, char* argv[])
{
	test01();
	return 0;
}

(2)静态链表分配在栈区

/*创建动态链表*/
void test02()
{
	//1.初始化节点
	struct LinkNode* node1 = malloc(sizeof(struct LinkNode));
	struct LinkNode* node2 = malloc(sizeof(struct LinkNode));
	struct LinkNode* node3 = malloc(sizeof(struct LinkNode));
	struct LinkNode* node4 = malloc(sizeof(struct LinkNode));
	struct LinkNode* node5 = malloc(sizeof(struct LinkNode));
	//2.建立节点之间联系
	node1->num = 10;
	node1->next = node2;
	node2->num = 20;
	node2->next = node3;
	node3->num = 30;
	node3->next = node4;
	node4->num = 40;
	node4->next = node5;
	node5->num = 50;
	node5->next = NULL;
	//3.遍历节点
	struct LinkNode* pCurrent = node1;
	while (NULL != pCurrent)
	{
		printf("%d\n", pCurrent->num);
		pCurrent = pCurrent->next;
	}
}
int main(int argc, char* argv[])
{
	test02();
	return 0;
}
  • 单向链表、双向链表、单向循环链表、双向循环链表
    (1)单向链表:只指向下一节点地址;
    (2)双向链表:指向前后两节点地址;
    (3)单向循环链表:尾节点指向首节点地址;
    (4)双向循环列表:首尾节点互指向地址。
    在这里插入图片描述
  1. 带头节点的链表
  • 特点
    (1)带头节点的链表头节点永远固定。当在链表头部增加新节点时,避免重新为链表赋予新的头节点地址;
    (2)头节点只维护指针域而不维护数据域,因此头节点的数据是无意义的。
    在这里插入图片描述

2. 链表的设计

2.1 链表的原理

  1. 目的:用户可以通过提供的接口来操作链表,并且无法对链表的本身信息进行修改。即对链表的操作进行封装。
  2. 创建一个链表的结构体
    (1)成员包括一个链表的头结点;
    (2)该链表的节点长度。
/*定义链表结构体*/
typedef struct LinkList
{
	LinkNode pHeader;
	int m_Size;
}LinkList;
  1. 创建一个节点的节点的结构体
    (1)数据域为用户的未知数据类型的地址;
    (2)指针域为下一节点地址;
/*定义节点结构体*/
typedef struct LinkNode 
{
	void* data;
	struct LinkNode* next;
}LinkNode;
  1. 对链表的类型进行封装,使用户无法得知传入的具体数据类型;
typedef void* LList;

2.2 链表的初始化

  1. 申请链表的内存空间;
  2. 初始化链表以及其内部的节点;
  3. 返回链表的地址。
/*初始化链表*/
LList init_LinkList()
{
	//1.申请内存空间
	LinkList* list = malloc(sizeof(LinkList));
	if (NULL == list)
	{
		return NULL;
	}

	//2.初始化链表及节点
	list->pHeader.data = 0;
	list->pHeader.next = NULL;
	list->m_Size = 0;

	//3.返回链表地址
	return list;
}

2.3 链表的插入

  1. 将用户传入的被封装的数据类型恢复;
  2. 判断插入的未知是否合法,若不合法则进行尾插;
  3. 插入新节点
    (1)建立新节点;
    (2)定义辅助指针来指定插入未知
    (3)插入新节点,建立节点之间的联系
    (4)更新链表的大小信息
    在这里插入图片描述
/*插入链表*/
void insert_LinkList(LList p_list, int pos, int p_data)
{
	if(NULL == p_list)
	{
		return;
	}
	//0.回复数据类型
	LinkList* list = p_list;
	//1.判断插入位置是否合法
	if (pos < 0 || pos >= list->m_Size)
	{	//1.1不合法则进行尾插
		pos = list->m_Size;
	}
	//2.插入新节点
		//2.1建立新节点
	LinkNode* newNode = malloc(sizeof(LinkNode));
	newNode->data = p_data;
	newNode->next = NULL;
		//2.2定义辅助指针确定插入位置
	LinkNode* pCurrent = &list->pHeader;
	for (int i = 0; i < pos; i++)
	{
		pCurrent = pCurrent->next;
	}
		//2.3插入新节点
	newNode->next = pCurrent->next;
	pCurrent->next = newNode;
	//3.更新链表信息
	list->m_Size++;
}

2.4 链表的遍历

  1. 将封装的数据结构解封;
  2. 定义辅助指针指向当前节点
  3. 通过循环遍历链表的结点
    (1)由于用户的数据类型无法确定,因此通过回调函数对遍历的数据传输到用户手中。
/*遍历链表*/
void foreach_LinkList(LList p_list, void(*Cmp)(void*))
{
	if (NULL == p_list)
	{
		return;
	}
	//0.恢复数据结构
	LinkList* list = p_list;
	//1.定义辅助指针指向当前节点
	LinkNode* pCurrent = list->pHeader.next;
	//2.遍历链表
	for (int i = 0; i < list->m_Size; i++)
	{
		Cmp(pCurrent->data);
		pCurrent = pCurrent->next;
	}
}

2.5 节点的删除

在这里插入图片描述

2.5.1 按位置删除
  1. 判断删除位置是否合法,不合法直接返回;
  2. 定义辅助指针,确定删除的位置;
  3. 删除节点
    (1)定义辅助指针记录要删除节点的指针域;
    (2)释放要删除节点的内存空间;
    (3)将上一节点指针域指向下一结点
    4.更新链表信息;

/*删除节点*/
void deleteByPos_LinkList(LList p_list, int pos)
{
	if (NULL == p_list)
	{
		return;
	}
	LinkList* list = p_list;
	//1.判断删除位置是否合法
	if (pos < 0 || pos > list->m_Size - 1)
	{
		return;
	}
	//2.定义辅助指针,确定删除的位置
	LinkNode* pCurrent = &list->pHeader;
	for (int i = 0; i < pos; i++)
	{
		pCurrent = pCurrent->next;
	}
	//3. 删除结点
		//3.1定义辅助指针记录要删除节点的信息
	LinkNode* pDel = pCurrent->next;
	pCurrent->next = pDel->next;
		//3.2释放要删除的节点
	free(pDel);
	pDel = NULL;
	//4.更新链表信息
	list->m_Size--;
}
2.5.2 按位置删除
  1. 定义辅助指针指向上一节点和当前节点
  2. 遍历节点查找要删除的节点;
    (1)根据回调函数,让用户实现如何确定判断要删除节点的条件;
    (2)若当前节点并非要删除的节点,继续遍历;
    (3)若当前节点使要删除的节点,删除节点停止遍历;
  3. 删除节点
    (1)将上一节点指针域指向下一节点;
    (2)释放当前节点的指针域;
    (3)停止遍历;
    (4)更新链表信息;
void deleteByVal_LinkList(LList p_list, void* p_data, int (*Comp)(void*, void*))
{
	if (NULL == p_list)
	{
		return;
	}
	LinkList* list = p_list;
	//1.定义辅助指针指向当前和上一节点
	LinkNode* pPrev = &list->pHeader;
	LinkNode* pCurrent = pPrev->next;
	//2.循环遍历查找要删除的点
	for (int i = 0; i < list->m_Size; i++)
	{
		//2.1若当前节点为要删除的点
		if (Comp(pCurrent->data, p_data))
		{
		//2.2释放当前内存,更新链表信息,并停止查找
			pPrev->next = pCurrent->next;
			free(pCurrent);
			pCurrent = NULL;
			list->m_Size--;
			break;
		}
		//2.2若当前节点不是要删除的点
			//继续遍历
		pPrev = pCurrent;
		pCurrent = pCurrent->next;
	}
}

2.6 清空链表

  1. 定义辅助指针指向当前节点;
  2. 遍历链表逐个释放节点
    (1)定义辅助指针指向当前节点的指针域;
    (2)释放当前节点内存;
    (3)继续遍历下一个节点;
  3. 将头节点的指针域指向空
  4. 更新链表信息大小;
/*清空链表*/
void clear_LinkList(LList p_list)
{
	if (NULL == p_list)
	{
		return;
	}
	LinkList* list = p_list;
	//1.定义辅助指针
	LinkNode* pCurrent = list->pHeader.next;
	//2.遍历逐个释放节点
	for (int i = 0; i < list->m_Size; i++)
	{
		//2.1定义复制指针记录当前节点的指针域
		LinkNode* pClr = pCurrent->next;
		//2.2释放当前节点内存
		free(pCurrent);		
		//2.4继续释放下一节点
		pCurrent = pClr;
	}
	list->pHeader.next = NULL;
	list->m_Size = 0;
}

2.7 销毁链表

  1. 清空链表;
  2. 释放链表结构体
*销毁链表*/
void destory_LinkList(LList p_list)
{
	if (NULL == p_list)
	{
		return;
	}
	//1.清空链表;
	clear_LinkList(p_list);
	//2.释放链表结构体
	free(p_list);
	p_list = NULL;
}

2.8 链表的反转

  1. 定义三个辅助指针指向上一节点、当前节点、下一节点;
  2. 遍历节点,将上一节点的地址赋给当前节点的指针域;
  3. 更新头结点。
    在这里插入图片描述
void reverse_LinkList(LinkNode* pHeader)
{
	if (NULL == pHeader)
	{
		return;
	}
	//1.定义辅助指针
	LinkNode* pPrev = NULL;
	LinkNode* pCurrent = pHeader->next;
	LinkNode* pNext = NULL; 
	while(NULL != pCurrent)
	{
		//2.1pNext指向下一节点;
		pNext = pCurrent->next;
		//2.2当前节点指针域指向上一个节点
		pCurrent->next = pPrev;
		//移动辅助指针
		pPrev = pCurrent;
		pCurrent = pNext
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值