C++超详细讲解单链表的实现

目录
  • 单链表的实现(从入门到熟练)
    • 概念和结构
    • 链表的实现
      • 增删查改接口
      • 节点结构体创建
      • 节点开辟
      • 数据打印
      • 链表尾插数据
      • 头删
      • 链表数据查找
      • 链表pos位置前插数据
      • 链表pos位置后插数据
      • 链表pos位置数据删除
      • 链表节点释放
    • 链表与顺序表区别
      • 链表的优缺点
      • 顺序表的优缺点

单链表的实现(从入门到熟练)

概念和结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构

数据元素的逻辑顺序是通过链表中的指针链 接次序实现的

图示:

注意:

链表结构在逻辑上为连续的,但是物理上(内存中)不一定连续

链表节点都是在堆上申请出来的,申请空间按一定策略分配

结构种类

链表具有多种结构:单向\双向,带头\不带头,循环\非循环

实际上最常用的是:无头单向非循环链表,带头双向循环链表

链表的实现

注意:这里实现的是无头单向非循环链表

增删查改接口

// 动态申请一个节点
SListNode* BuySListNode(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);

节点结构体创建

typedef int SLTDateType;
typedef struct SListNode
{
 SLTDateType data;
 struct SListNode* next;
}SListNode;

节点开辟

对于链表来说,每需要空间就需要进行开辟,这里我们将之封装成一个函数,便于后续调用直接使用(开辟的同时进行赋值)

参考代码:

//链表节点开辟
SLTNode* BuySListNode(SLTDateType x)
{
	//动态分配一个节点
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		//分配失败则打印错误信息并结束进程
		perror("newnode fail:");
		exit(-1);
	}
	//成功则进行赋值
	newnode->data = x;
	newnode->next = NULL;
	//返回节点地址,以便后续操作
	return newnode;
}

数据打印

注意:

1.对于不带头的链表来说,打印数据不需要修改链表首节点地址(故只要传链表指针)

2.用循环遍历链表,每打印数据,需要指向下一个节点

3.依靠尾节点的址域为NULL来结束循环

代码:

//链表数据打印
void SListPrint(SLTNode* phead)//一级指针接收一级指针(打印不需改变指针本身内容)
{
	//创建一个寻址指针
	SLTNode* cur = phead;
	while (cur!=NULL)//后续还有节点
	{
		//打印数据并指向下一个节点
		printf("%d->", cur->data);
		cur = cur->next;
	}
	//最后打印空指针
	printf("NULL\n");
	return;
}

链表尾插数据

  • 要尾插数据则需要遍历找到链表的尾节点
  • 对于不带头链表,尾插数据也可能是插在链表首元素的地址(当链表为空),需要修改链表指针的内容(故需要传入链表指针的地址)
  • 插入数据要开辟节点

代码:

//链表尾插数据
void SListPushBack(SLTNode** pphead, SLTDateType x)//二级指针接收一级指针(尾插存在需改变链表指针本身内容)
{
	//避免传入错误(直接报错便于找到错误位置)
	assert(pphead);
	//接收新节点的地址
	SLTNode* newnode=BuySListNode(x);
	//头节点为空
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾节点
		SLTNode* tail = *pphead;//创建寻址节点
		//两个及以上节点的情况
		while (tail->next != NULL)
		{
			//指向下一个节点
			tail = tail->next;
		}
		//找到了
		tail->next = newnode;
	}
	return;
}

注意代码中的assert的作用:

  • 正确传入链表指针的地址是不会为空的
  • 但是对于非正常传入链表指针(不是链表指针的地址)且此时链表指针为空则会发生报错(assert报错会告诉错误位置),告诉程序员应该传入链表指针的地址

头删

注意:

  • 删除前需要保存当前节点的址域(即保存下个节点的空间地址,以防丢失)
  • 前删数据即删除当前链表首节点,即需修改链表指针的内容(故需传入链表指针的地址)
  • 删除后修改链表指针内容,指向新的首节点
  • 如果链表为空时无法删除(保存下个节点地址会造成非法访问)

代码:

//链表前删数据
void SListPopFront(SLTNode** pphead)
{
	//避免传入错误(直接报错便于找到错误位置)
	assert(pphead);
	//链表为空的情况
	if (*pphead == NULL)
	{
		return;
	}
	//创建指针保存第二个节点地址
	SLTNode* next = (*pphead)->next;
	//释放当前头结点空间
	free(*pphead);
	//更新头结点数据
	*pphead = next;
	return;
}

链表数据查找

注意:

  • 查找时用循环遍历链表
  • 对于查找数据不用修改链表指针的内容,故只需传入链表指针就行了
  • 查找到时则返回节点地址,否则返回NULL

代码:

//链表数据查找
SLTNode* SListFind(SLTNode* phead, SLTDateType x)
{
	//创建寻址指针
	SLTNode* cur = phead;
	while (cur!=NULL)
	{
		if (cur->data == x)//找到数据符合节点
		{
			return cur;//返回节点地址(好处:以便后续再寻找或修改)
		}
		else
		{
			//找下一个节点
			cur = cur->next;
		}
	}
	//未找到数据符合节点
	return NULL;
}

链表pos位置前插数据

注意:

  • 想要pos位置前插数据,不仅需要找到pos位置,还需要记录pos的前一个节点位置
  • 传入的pos为NULL则报错
  • 进行修改前节点的址域成新节点,并让新节点的址域修改成pos,这才是一个成功的pos位置前插数据
  • 循环遍历链表查找pos位置,没有找到pos位置则什么也不干

代码:

//链表pos位置往前插入(较难)(还有在pos位置之后插入,简单点)
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	//避免传入错误(直接报错便于找到错误位置)
	assert(pphead);
	assert(pos);
	SLTNode* newnode = BuySListNode(x);
	//首结点符合的情况
	if (*pphead == pos)
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{
		//创建寻址指针
		SLTNode* cur = *pphead;
		while (cur !=NULL)
		{
			if (cur->next!= pos)
			{
				cur = cur->next;//找到下一节点
			}
			else // 找到对应空间
			{
				cur->next = newnode;
				newnode->next = pos;
				return;//结束寻找(否则会一直插入,造成死循环)
			}
		}
	}
	//未找到则什么也不干
	return;
}

链表pos位置后插数据

注意:

  • 后插则不用关注是否为首节点
  • 也不用找到遍历找到前节点的位置
  • 后插则先将新节点址域改成pos后节点地址再将pos的址域改成新节点地址

ps:一定要注意修改链接节点址域的先后,避免地址的丢失

代码:

//链表pos位置往后插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDateType x)
{
	SLTNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
	return;
}

链表pos位置数据删除

注意:

  • 考虑删除首节点的情况,可能修改链表指针的内容,故需要传入链表指针的地址
  • 对于删除节点,需要先保存pos位置下一个节点地址,将pos位置释放,再将pos位置前节点的址域改成pos位置后节点的地址,这才是成功的删除pos位置节点
  • 循环找pos位置,没找到则什么也不干

参考代码:

//链表pos位置删除
void SListErase(SLTNode** pphead, SLTNode* pos)
{
	//避免传入错误(直接报错便于找到错误位置)
	assert(pphead);
	assert(pos);
	//头结点符合的情况
	if (*pphead == pos)
	{
		*pphead = (*pphead)->next;
		free(pos);
	}
	else
	{
		//创建寻址指针
		SLTNode* cur = *pphead;
		while (cur != NULL)
		{
			if (cur->next != pos)
			{
				cur = cur->next;//找到下一节点
			}
			else // 找到对应空间
			{
				SLTNode* next = cur->next->next;
				free(cur->next);
				cur->next = next;
				return;//结束寻找
			}
		}
	}
	//未找到则什么也不干
	return;
}

链表节点释放

注意:

  • 对于动态开辟的内存空间,在使用后一定要记得的进行释放(避免造成内存泄漏)
  • 因为链表节点是一个个开辟的,同样的释放也需要一个个进行释放
  • 循环遍历释放当前节点前需保存后一个节点的地址,避免地址丢失无法释放
  • 释放完后,还需将链表指针给置空(避免使用野指针)

参考代码:

//链表节点释放
void SListDestory(SLTNode** pphead)
{
	//避免传入错误(直接报错便于找到错误位置)
	assert(pphead);
	//遍历释放
	SLTNode* cur = *pphead;
	while (cur)
	{
		//保存下一个地址
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	//置空
	*pphead = NULL;
	return;
}

链表与顺序表区别

链表的优缺点

优点

  • 按需申请空间(空间使用合理)
  • 插入效率高(不用像顺序表样挪动数据)

缺点

  • 不支持随机访问(无法用下标直接访问)

顺序表的优缺点

优点

  • 支持随机访问 (有些算法需要结构支持随机访问:二分查找,快排等)

缺点

  • 扩容空间有消耗(空间碎片化以及空间浪费)
  • 插入数据需要挪动数据有消耗

到此这篇关于C++超详细讲解单链表的实现的文章就介绍到这了,更多相关C++ 单链表内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++中单链表的建立与基本操作

    准备数据 准备在链表操作中需要用到的变量及数据结构 示例代码如下: 复制代码 代码如下: struct Data   //数据结点类型 { string key;  //关键字  string name; int age;};struct CLType  //定义链表结构 { Data nodeData; Data *nextNode;}; 定义了链表数据元素的类型Data以及链表的数据结构CLType.结点的具体数据保存在一个结构Data中,而指针nextNode用来指向下一个结点. 我们可以

  • C++数据结构之单链表

    目录 单链表结构的定义 单链表打印 动态申请一个结点 单链表尾插 单链表尾删 单链表头插 单链表头删 求单链表长度 单链表查找 单链表在pos位置插入 单链表在pos后面位置插入 单链表删除pos位置 单链表删除pos的下一个结点 判断单链表是否为空 头文件 源文件 简介: 线性表的顺序存储结构有一个缺点就是插入和删除时需要移动大量元素,这会耗费许多时间.能不能想办法解决呢?干脆所有的元素都不要考虑相邻位置了,哪有空位就到哪里,让每一个元素都知道它下一个元素的位置在哪里.线性表链式存储结构: 用

  • C++单链表实现大数加法

    本文实例为大家分享了C++单链表实现大数加法,供大家参考,具体内容如下 Input Format 输入文件包括两行. 第一行包括一个正整数,保证位数不超过1000000. 第二行包括一个正整数,保证位数不超过1000000. Output Format 输出文件包括一行. 第一行包括一个正整数. Sample Input 10558 22 Sample Output 10580 #include <iostream> using namespace std; class BigData { f

  • C++ 数据结构超详细讲解单链表

    目录 前言 一.链表是什么 链表的分类 二.链表的实现 总结 (❁´◡`❁) 单链表 前言 上篇顺序表结尾了解了顺序表的诸多缺点,链表的特性很好的解决了这些问题,本期我们来认识单链表. 一.链表是什么 链表是一种物理存储结构上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接依次实现的. 由图,链式结构在逻辑上是连续的,但是物理上不一定连续 显示中结点一般是从堆上申请出来的 从堆上申请的空间,是按照一定的策略划分的,两次申请的空间,可能连续,可能不连续,见顺序表 链表的分类 链表

  • C++超详细分析单链表的实现与常见接口

    相信如果看完了上期顺序表的小伙伴应该发现了顺序表的诸多缺点:

  • C++编程语言实现单链表详情

    目录 一.单链表简单介绍 二.下面我们先实现单链表的初始化. 三.实现单链表的插入与删除数据 一.单链表简单介绍 首先,我们再回顾一下线性表的两种存储方式--顺序存储与链式存储 上图左边为顺序存储,右边为链式存储 之前我们利用数组来实现顺序表,对于顺序表的优点缺点,总结来说就是,查找方便,增删复杂. 线性表之顺序存储的优缺点 而链表特点可以说恰恰相反,增删方便,查找复杂. 今天实现的是链表中最简单的一种--单链表(每个结点中只含有一个指针域) 对于链表我们只知道它每个结点的存储的物理地址是不连续

  • C++ 单链表的基本操作(详解)

    链表一直是面试的高频题,今天先总结一下单链表的使用,下节再总结双向链表的.本文主要有单链表的创建.插入.删除节点等. 1.概念 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素. 链表中的数据是以结点来表示的,每个结点的构成:元素 + 指针,元素就是存储数据的存储单元,指针就是连接每个结点的地址数据.如下图: 2.链表的基本操作 SingleList.cpp: #include "stdafx.h" #include "SingleList.h&

  • C++使用模板实现单链表

    本文实例为大家分享了用模板实现单链表,供大家参考,具体内容如下 话不多说 直接上代码 #include <iostream> using namespace std; template<typename E> class CLink; template<typename T> class Node { friend class CLink<T>; public: /* 构造函数和析构函数一般不加类型参数 本类类中除了构造函数和析构函数以外 其它的地方都要加上

  • C++实现单链表的构造

    单链表的构造,包括最常用函数,setData(),Insert(),Remove(),getData(),Search(). 代码如下: #include <iostream> #include <stdlib.h> using namespace std; template<class T> struct LinkNode{ T data; LinkNode<T> *link; LinkNode(LinkNode<T> *ptr=NULL){l

  • C++超详细讲解单链表的实现

    目录 单链表的实现(从入门到熟练) 概念和结构 链表的实现 增删查改接口 节点结构体创建 节点开辟 数据打印 链表尾插数据 头删 链表数据查找 链表pos位置前插数据 链表pos位置后插数据 链表pos位置数据删除 链表节点释放 链表与顺序表区别 链表的优缺点 顺序表的优缺点 单链表的实现(从入门到熟练) 概念和结构 概念:链表是一种物理存储结构上非连续.非顺序的存储结构 数据元素的逻辑顺序是通过链表中的指针链 接次序实现的 图示: 注意: 链表结构在逻辑上为连续的,但是物理上(内存中)不一定连

  • C语言数据结构超详细讲解单向链表

    目录 1.链表概况 1.1 链表的概念及结构 1.2 链表的分类 2. 单向链表的实现 2.1 SList.h(头文件的汇总,函数的声明) 2.2 SList.c(函数的具体实现逻辑) 2.2.1 打印链表 2.2.2 搞出一个新节点(为其他函数服务) 2.2.3 链表尾插 2.2.4 链表头插 2.2.5 链表尾删 2.2.6 链表头删 2.2.7 查找节点 2.2.8 在pos位置之前插入 2.2.9 在pos位置之后插入 2.2.10 删除pos位置 2.2.11 删除pos之后位置 2.

  • C语言超详细讲解数据结构中双向带头循环链表

    目录 一.概念 二.必备工作 2.1.创建双向链表结构 2.2.初始化链表 2.3.动态申请节点 2.4.打印链表 2.5.销毁链表 三.主要功能 3.1.在pos节点前插入数据 尾插 头插 3.2.删除pos处节点数据 尾删 头删 3.3.查找数据 四.总代码 List.h 文件 List.c 文件 Test.c 文件 五.拓展 一.概念 前文我们已经学习了单向链表,并通过oj题目深入了解了带头节点的链表以及带环链表,来画张图总体回顾下: 在我们学习的链表中,其实总共有8种,都是单双向和带不带

  • C语言超详细讲解队列的实现及代码

    目录 前言 队列的概念 队列的结构 队列的应用场景 队列的实现 创建队列结构 队列初始化 队列销毁 入队列 出队列 队列判空 获取队列元素个数 获取队列头部元素 获取队列尾部元素 总代码 Queue.h 文件 Queue.c 文件 Test.c 文件 前言 队列的概念 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 入队列:进行插入操作的一端称为队尾 出队列:进行删除操作的一端称为队头 队列和前文所学的栈

  • C语言超详细讲解线性表

    目录 1. 顺序表 1.1 管理结点 1.2 顺序表的插入 1.3 顺序表的删除 1.4 顺序表的扩容 2. 链表 2.1 定义 2.2 头部插入 2.3 尾部插入 2.4 任意位置插入 2.5 任意位置删除 2.6 虚头结点 1. 顺序表 顺序表是指用一段连续的地址,依次存放数据元素的线性数据结构.此种存储方式使得顺序表的物理结构与逻辑结构都是连续的. 与数组的区别:函数中的数组被存放在栈段中,而栈段有系统限制的大小(可使用ulimit -s查看系统限制的大小,单位为KB),因此顺序表往往使用

  • Java数组队列及环形数组队列超详细讲解

    目录 一.队列 1.基本介绍 2.示意图 3.队列的特点 二.数组模拟队列 1.数组队列初始化 2.判断方法 3.增删改查的方法 4.注意 三.数组模拟环形队列 1.初始化 2.判断方法 3.增删改查的方法 一.队列 1.基本介绍 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表.进行插入操作的端称为队尾,进行删除操作的端称为队头. 2.示意图 3.队列的特点 先进先出: 在队列中插入一

  • C语言哈希表概念超详细讲解

    目录 1. 哈希概念 2. 哈希冲突 3. 哈希实现 3.1 闭散列(哈希表) 3.1.1 闭散列的细节 3.1.2 优化后的闭散列 3.2 扩散列(哈希桶) 3.2.1 扩散列的细节 4. 哈希表和哈希桶的比较 5. 结尾语 1. 哈希概念 哈希其实在学排序时已经用过了,就是计数排序.计数排序也是用的一种映射关系. 比如对此数组进行 计数排序 :1 1 9 9 9 3 3 8 8 我用的是绝对映射 ,所以开辟的数组空间 它的大小 必须 能映射到 最大的元素. 但是 对于哈希来讲,可以用决定映射

  • 超详细讲解Java线程池

    目录 池化技术 池化思想介绍 池化技术的应用 如何设计一个线程池 Java线程池解析 ThreadPoolExecutor使用介绍 内置线程池使用 ThreadPoolExecutor解析 整体设计 线程池生命周期 任务管理解析 woker对象 Java线程池实践建议 不建议使用Exectuors 线程池大小设置 线程池监控 带着问题阅读 1.什么是池化,池化能带来什么好处 2.如何设计一个资源池 3.Java的线程池如何使用,Java提供了哪些内置线程池 4.线程池使用有哪些注意事项 池化技术

随机推荐