C语言详解无头单向非循环链表各种操作方法

目录
  • 链表引入
  • 链表介绍
  • 创建链表
  • 打印链表
  • 创建结点
  • 单链表尾插
  • 单链表头插
  • 单链表尾删
  • 单链表头删
  • 在pos位置之前插入数据
  • 在pos位置之后插入数据
  • 删除pos位置结点
  • 删除pos位置之后的结点
  • 销毁链表
  • 链表查找

链表引入

问:上次我们看了顺序表,那么顺序表有些什么优缺点呢?

优点:

顺序表是连续的物理空间,方便下标的随机访问。

缺点:

1.增容需要申请新空间,拷贝数据,释放旧的空间。会有一定消耗。

2.头部或者中间位置插入或者删除数据,需要挪动数据,效率较低。

3.可能存在一定空间占用,浪费空间,不能按需申请和释放空间。

为了解决上诉问题,我们引入了链表。首先我们先来看看单链表。

链表介绍

链表是一种物理存储结构上非连续、非顺序的存储结构、数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个节点包含两部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

实际上,链表的结构多样,如下:

1.单向或者双向

2.带头或者不带头

3.循环或者非循环

创建链表

链表是由结点链接而成的,所以创建链表需要创建一个结点类型。该类型由指针域和数据域组成。

typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;//用来存放该结点的数据
	struct SListNode* next;//用来存放下一个结点的地址
}SListNode;

打印链表

从头指针指向的位置开始,依次向后打印,知道cur访问到NULL就结束循环。

void SListPrint(SListNode* phead);
void SListPrint(SListNode* phead)
{
	SListNode* cur = phead;//我们一般不会改变头指针,所以我们把头指针赋值给cur
	while (cur)//链表结束条件
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");//表示数据已经打印完毕
}

创建结点

每当我们需要插入一个数据,我们就需要申请一个结点,如果每次都重新申请就会很麻烦,所以我们将创建一个新结点封装为一个函数。

SListNode* BuySListNode(SLTDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		printf("BuySListNode::%s\n", strerror(errno));//若申请失败,则打印错误信息
		exit(-1);
	}
	else
	{
		newnode->data = x;//将数据赋值到新点的数据域
		newnode->next = NULL;//新结点指针域置为空指针
	}
	return newnode;//返回新结点的地址
}

单链表尾插

我们需要将尾插分为两种情况:

情况一: 链表为空,我们直接让头指针指向新的结点即可。

情况二: 链表已经有多个结点,我们需要找到链表的最后一个结点,然后让最后一个结点指向新的结点。

void SListPushBack(SListNode** pphead, SLTDataType x);
void SListPushBack(SListNode** pphead, SLTDataType x)
{
	assert(pphead);
	SListNode* newnode = BuySListNode(x);
	//链表为空
	if (*pphead == NULL)
	{
		*pphead = newnode;//头指针指向新的结点
	}
	//链表不为空
	else
	{
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

注意: 链表头插函数的参数我们应该传头指针的地址,而不是头指针本身。如果为传值调用,那么形参是实参的一份临时拷贝,对形参的改变不会影响实参。

单链表头插

单链表头插时,我们申请一个新的结点,然后让新结点的指针域指向之前的第一个结点,然后让头指针指向新结点。

注意:这两步操作的顺序不可以颠倒,如果先让头指针指向了新结点,那么将不可能找到第一个结点的位置了。也不可能在找到后面的数据了。

void SListPushFront(SListNode** pphead, SLTDataType x);
void SListPushFront(SListNode** pphead, SLTDataType x)
{
	assert(pphead);
	SListNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

单链表尾删

演示一种错误的写法:

对于单链表的尾删,我们需要考虑三种情况:

1.链表为空时,不做任何处理。

2.链表只有一个结点时,释放该结点,然后将头指针置为空。

3.链表有多个结点时,有两种处理方法,详见一下代码。

代码一: 找到最后一个结点的前一个结点,释放最后一个结点。将前一个结点的指针域置为空指针。

void SListPopBack(SListNode** pphead);
void SListPopBack(SListNode** pphead)
{
	assert(pphead);
	if (*pphead == NULL)
		return;
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* prev = NULL;
		SListNode* tail = *pphead;
		while (tail->next != NULL)
		{
			prev = tail;
			tail = tail->next;
		}
		free(tail);
		tail = NULL;
		prev->next = NULL;
	}
}

代码二: 找到最后一个结点的前一个结点,将后一个结点释放掉,然后在置为空就可以了。

void SListPopBack(SListNode** pphead);
void SListPopBack(SListNode** pphead)
{
	assert(pphead);
	if (*pphead == NULL)
		return;
	else if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

单链表头删

若链表为空,则不处理。若链表不为空,让头指针指向第二个结点,释放掉第一个结点。

void SListPopFront(SListNode** pphead);
void SListPopFront(SListNode** pphead)
{
	assert(pphead);
	if (*pphead == NULL)//链表为空
		return;
	else
	{
		SListNode* head = *pphead;
		*pphead = head->next;//让头指针指针域中的地址指向头指针
		free(head);//释放第一个结点
		head = NULL;
	}
}

在pos位置之前插入数据

若pos是第一个结点,我们直接调用之前写的头插。若pos不是第一个结点,我们找到pos位置的前一个结点,让新的结点指向地址为pos的结点,然后让前一个结点指向新结点。

void SListInsertBefore(SListNode** pphead, SListNode* pos, SLTDataType x);
void SListInsertBefore(SListNode** pphead, SListNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPushFront(pphead,x);//头插函数
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)//找到pos的前一个结点
		{
			prev = prev->next;
		}
		SListNode* newnode = BuySListNode(x);
		newnode->next = prev->next;//让新结点指向pos结点
		prev->next = newnode;//让前一个结点指向新结点
	}
}

在pos位置之后插入数据

让新结点指向该位置的下一个位置,然后让该位置的结点指向新结点。

void SListInsertAfter(SListNode** pphead, SListNode* pos, SLTDataType x);
void SListInsertAfter(SListNode** pphead, SListNode* pos, SLTDataType x)
{
	assert(pphead);
	SListNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

删除pos位置结点

void SListErase(SListNode** pphead, SListNode* pos);
void SListErase(SListNode** pphead, SListNode* pos)
{
	assert(pphead);
	assert(pos);
	if (*pphead == pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

删除pos位置之后的结点

void SListEraseAfter(SListNode* pos);
void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	SListNode* next = pos->next;
	if (next)//如果next不为空,则条件为真
	{
		pos->next = next->next;//让pos指向要删除位置的后一个结点
		free(next);//释放pos的下一个结点
		next = NULL;
	}
}

销毁链表

在销毁链表时。首先我们将头指针赋值给一个新的指针,用该指针依次遍历链表,先把该结点的下一个结点的地址保存,然后在释放该结点,最后将头指针置为空。

注意: 一定要在释放该指针之前保存该指针的下一个结点的地址,否则就找不到下一个结点了。

void SListDestroy(SListNode** pphead);
void SListDestroy(SListNode** pphead)
{
	assert(pphead);
	SListNode* cur = *pphead;
	while (cur)
	{
		SListNode* next = cur->next;//存放下一个结点地址
		free(cur);//释放当前结点
		cur = NULL;
	}
	*pphead = NULL;//将头指针置为空
}

链表查找

SListNode* SListFind(SListNode* phead, SLTDataType x);
SListNode* SListFind(SListNode* phead, SLTDataType x)
{
	SListNode* cur = phead;
	while (cur != NULL)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

提示:我们在写链表的时候,尽量画图分析,帮助我们理清思路。

到此这篇关于C语言详解无头单向非循环链表各种操作方法的文章就介绍到这了,更多相关C语言无头单向非循环链表内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C语言 超详细介绍与实现线性表中的无头单向非循环链表

    目录 一.本章重点 二.链表介绍 三.无头单向非循环链表常用接口实现 3.1动态申请一个节点 3.2单链表打印 3.3单链表尾插 3.4单链表的头插 3.5单链表的尾删 3.6单链表头删 3.7单链表查找 3.8单链表在pos位置之前插入x 3.9单链表删除pos位置的节点 四.在线oj训练 4.1移除链表元素(力扣) 4.2反转单链表(力扣) 一.本章重点 无头单向非循环链表介绍 无头单向非循环链表常用接口实现 在线oj训练 二.链表介绍 概念:链表是一种物理存储结构上非连续.非顺序的存储结构

  • C语言详解无头单向非循环链表各种操作方法

    目录 链表引入 链表介绍 创建链表 打印链表 创建结点 单链表尾插 单链表头插 单链表尾删 单链表头删 在pos位置之前插入数据 在pos位置之后插入数据 删除pos位置结点 删除pos位置之后的结点 销毁链表 链表查找 链表引入 问:上次我们看了顺序表,那么顺序表有些什么优缺点呢? 优点: 顺序表是连续的物理空间,方便下标的随机访问. 缺点: 1.增容需要申请新空间,拷贝数据,释放旧的空间.会有一定消耗. 2.头部或者中间位置插入或者删除数据,需要挪动数据,效率较低. 3.可能存在一定空间占用

  • C语言详解如何实现带头双向循环链表

    目录 创建链表存储结构 创建结点 链表的初始化 双向链表的打印 双向链表尾插 双向链表尾删 双向链表头插 双向链表头删 双向链表查找 双向链表pos前插入结点 双向链表删除pos位置的结点 双向链表的销毁 顺序表和链表的区别 2022042311415360.{C}{C}png" /> 创建链表存储结构 我们需要创建一个结构体来存储一个链表结点的相关信息. typedef int ListDataType;//将ListDataType先定义为int类型,根据需要可以改为不同的类型 //创

  • c语言详解动态内存分配及常见错误的解决

    目录 为什么会有动态内存分配 动态内存函数的介绍 malloc free calloc realloc 常见的错误 对NULL指针的解引用操作 越界访问 对非动态内存进行free 使用free释放动态开辟内存的一部分 对同一块动态内存多次释放 对动态内存内存忘记释放(内存泄漏) 为什么会有动态内存分配 内存使用方式有两种 1.创建一个变量 2.创建一个数组 int a = 1; int arr[10]; 但是这两种方式都有一些特点 1.空间是固定大小的,不会变 2.必须提前知道要开辟多少空间,必

  • C语言详解如何实现堆及堆的结构与接口

    目录 一.堆的结构及实现(重要) 1.1 二叉树的顺序结构 1.2 堆的概念及结构 1.3 堆的实现 1.3.1 堆的向下调整算法 1.3.2 向下调整算法的时间复杂度 1.3.3 堆的创建(向下调整) 1.3.4 堆排序 1.3.5 建堆的时间复杂度 二.堆的相关接口实现(以大堆为例) 2.1 堆的初始化 2.2 堆的销毁 2.3 堆的插入 2.4 堆的删除 2.5 获取堆顶元素 2.6 堆的判空 2.7 找出堆中前k个最大元素 2.8 堆的创建(向上调整) 一.堆的结构及实现(重要) 1.1

  • C语言详解实现链式二叉树的遍历与相关接口

    目录 前言 一.二叉树的链式结构 二.二叉树的遍历方式 1.1 遍历方式的规则 1.2 前序遍历 1.3 中序遍历 1.4 后序遍历 1.5 层序遍历 三.二叉树的相关接口实现 3.1 二叉树节点个数 3.2 二叉树叶子节点个数 3.3 二叉树第 k 层节点个数 3.4 二叉树的深度(高度) 3.5 二叉树查找值为 x 的节点 3.6 总结 & 注意 四.二叉树的创建和销毁 4.1 通过前序遍历的字符串来构建二叉树 4.2 二叉树销毁 4.3 判断二叉树是否是完全二叉树 前言 二叉树的顺序结构就

  • C语言详解分析进程控制中进程终止的实现

    目录 进程退出的形式 进程退出的几种方法 进程退出的形式 进程退出的几种情况 正常退出(自愿,代码运行完其结果正确) 错误退出(自愿,代码运行完其结果不正确) 异常退出(非自愿,代码异常直接终止) 被其他进程终止(非自愿) 自愿退出会返回一个退出码,由父进程接收. 在Linux上可以使用命令echo $?显示最近一次退出的进程返回的退出码 //现有如下代码,源文件名为mycode.c # include <stdio.h> int main(void) { printf("i am

  • Vuejs第一篇之入门教程详解(单向绑定、双向绑定、列表渲染、响应函数)

    什么是组件? 组件(Component)是 Vue.js 最强大的功能之一.组件可以扩展 HTML 元素,封装可重用的代码.在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能.在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展. 接下来给大家介绍vuejs单向绑定.双向绑定.列表渲染.响应函数基础知识,具体详情如下所示: (一)单向绑定 <div id="app"> {{ message }} </div> <sc

  • C语言详解如何应用模拟字符串和内存函数

    目录 1.strlen 求字符串长度 使用案例: 1.计数法 2.不创建临时变量计数器-递归 3.指针-指针的方式 2.长度不受限制的字符串函数 1.strcpy 使用案例: 模拟实现: 2.strcat 使用案例: 模拟实现: 3.strcmp-比较字符串首字母的大小 使用案例: 模拟实现: 3.长度受限制的字符串函数  1.strncpy 使用案例: 2.strncat  使用案例: 3.strncmp 使用案例: 4.strstr-找子串  使用案例: 模拟实现: 5.strtok 用法:

  • C语言 详解如何删除有序数组中的重复项

    目录 删除有序数组中的重复项Ⅰ a.思路 b.图解 c.代码 d.思考 删除有序数组中的重复项Ⅱ a.思路 b.图解 c.代码 d.思考 删除有序数组中的重复项Ⅰ a.思路 定义变量 int dest=0,cur=1,nums[cur]与nums[dest]逐一比较. nums[cur]!=nums[dest],将nums[cur]放入dest下一个位置,更新dest. nums[cur]!=nums[dest],cur移动. cur==numsSize,结束.返回dest+1. b.图解 c.

随机推荐