数据结构之手撕链表(讲解➕源代码)

news2024/11/25 20:33:24

0.引言

我们在学习过顺序表之后,会发现两点不是很优秀的操作:
1.顺序表的头插和中间的插入:
        非常麻烦,需要不断的覆盖数据。
2.动态开辟空间:
        a.一般动态开辟的空间都是以2倍的形式开辟,当我们已经开辟了100个空间,并且存满了,此时我们还需要存放5个数据,那么就又需要开辟200个空间了,我们存放5个数据之后,还剩余了195个空间没有放数据,这也就导致了空间的浪费
        b. 而我们开辟新空间,拷贝数据,释放旧空间还会有一定的消耗

注意⚠️⚠️⚠️
        我们在申请空间的时候必须要主动给它释放掉,因为申请空间的时候,是在堆上申请的,这段空间不会随着程序的结束而自然释放掉,所以要在我们程序结束之前,主动释放掉这段空间。

那有没有一种结构会很简便呢?答案肯定是有的,就是我们本次要讲解的链表。


1.链表的概念

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的 。也就是说链表的物理结构不连续,但逻辑结构连续

我们通常定义一个结构体来表示链表的节点,结构体内部要包括节点的值和指向下一个节点的指针。

链表开始的节点称作:头节点,通常用head表示;

链表末尾的节点称作:尾结点,通常用tail表示;

其余节点称作:中间节点。

2.链表的逻辑模型

如图:在逻辑模型,可以看出它们是一个接着一个的,在逻辑上连续。

3.链表的物理模型

如图:在物理模型中,我们看到每个节点的地址都不是连续的,但是通过指针链接起来了。

 4.链表的分类

4.1 单向链表

        

4.2 双向链表

4.3 带头单向链表(带哨兵位)

4.4带头双向链表(带哨兵位)

4.5循环单链表

4.6循环双向链表

4.7带头循环单链表

4.8带头循环双向链表

5. 单链表的实现(接口实现代码)

5.1单链表的定义

typedef int SLTDataType;

typedef struct SListNode
{
    SLTDataType data;
    struct SListNode* next;
}SLTNode;

5.3单链表的销毁

void SLT_Destroy(SLTNode*phead)//销毁
{
    while (phead != NULL)
    {
        SLTNode *over = phead->next;
        free(phead);
        phead = over;
    }
}

5.4单链表的动态申请一个节点空间

SLTNode* BuySListNode(SLTDataType x) //创造新节点
{
    SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
    if (newnode == NULL)
    {
        perror("malloc fail");
        exit(-1);
    }

    newnode->data = x;
    newnode->next = NULL;

    return newnode;
}

5.5单链表的头插头删

void SLTPushFront(SLTNode** pphead, SLTDataType x) //头插
{
    SLTNode* newnode = BuySListNode(x);

    newnode->next = *pphead;
    *pphead = newnode;
}

void SLTPopFront(SLTNode** pphead) //头删
{
    // 空
    assert(*pphead);

    // 非空
    SLTNode* newhead = (*pphead)->next;
    free(*pphead);
    *pphead = newhead;
}

5.6单链表的尾插尾删

void SLTPushBack(SLTNode** pphead, SLTDataType x) //尾插
{
    SLTNode* newnode = BuySListNode(x);

    if (*pphead == NULL)
    {
        // 改变的结构体的指针,所以要用二级指针
        *pphead = newnode;
    }
    else
    {
        SLTNode* tail = *pphead;
        while (tail->next != NULL)
        {
            tail = tail->next;
        }

        // 改变的结构体,用结构体的指针即可
        tail->next = newnode;
    }
}

void SLTPopBack(SLTNode** pphead) //尾删
{
    // 1、空
    assert(*pphead);

    // 2、一个节点
    // 3、一个以上节点
    if ((*pphead)->next == NULL)
    {
        free(*pphead);
        *pphead = NULL;
    }
    else
    {
        SLTNode* tail = *pphead;
        while (tail->next->next)
        {
            tail = tail->next;
        }

        free(tail->next);
        tail->next = NULL;
    }
}

5.7单链表的在pos位置之前的插入

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)//pos之前插入
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		SLTNode* newnode = BuySListNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

5.8单链表的在pos位置之后的插入

void SLTInsertAfter(SLTNode* pos, SLTDataType x)//在pos之后插入
{
	assert(pos);

	SLTNode* newnode = BuySListNode(x);
	pos->next = newnode;
	newnode->next = pos->next;
}

5.9单链表的删除pos位置的节点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		prev->next = pos->next;
		free(pos);
		//pos = NULL;
	}
}

5.10单链表的删除pos位置之后的节点

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);

	// 检查pos是否是尾节点
	assert(pos->next);

	SLTNode* posNext = pos->next;

	pos->next = posNext->next;

	free(posNext);
	posNext = NULL;
}

5.11单链表的查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x) //查找
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}

		cur = cur->next;
	}

	return NULL;
}

6.带头双向循环链表的实现(接口实现代码)

6.1带头双向循环链表的定义

typedef int LTDataType;
typedef struct ListNode
{
    struct ListNode* prev; //前驱
    LTDataType data;
    struct ListNode* next; //后继
}ListNode;

6.2带头双向循环链表的初始化

ListNode* ListInit() //初始化链表
{
    ListNode *head = (ListNode*) malloc(sizeof (ListNode));
    if(head ==  NULL)
    {
        perror("初始化开辟空间失败");
        exit(-1);
    }
    head->data = -1;
    head->prev = head;
    head->next = head;
    return head;
}

6.3带头双向循环链表的销毁

void ListDestory(ListNode* pHead) //销毁
{
    int len = ListSize(pHead) + 1;
    while (len--)
        ListPopFront(pHead);
}

6.4带头双向循环链表的打印

void ListPrint(ListNode* phead) //打印
{
    assert(phead);
    ListNode *phead_next = phead->next;
    printf("phead <-> ");
    while(phead_next != phead)
    {
        printf("%d <-> ",phead_next->data);
        phead_next = phead_next->next;
    }
    printf("phead\n");
}

6.5带头双向链表的增加节点

ListNode* BuyListNode(LTDataType x) //增加节点
{
    ListNode *newnode = (ListNode*) malloc(sizeof (ListNode));
    if(newnode == NULL)
    {
        perror("增加节点开辟空间失败");
        exit(-1);
    }
    newnode->data = x;
    newnode->next = NULL;
    newnode->prev = NULL;
    return newnode;
}

6.6带头双向循环链表的在pos之前插入

void ListInsert(ListNode* pos,LTDataType x)//在pos位置之前插入
{
    assert(pos);
    ListNode *newnode = BuyListNode(x);
    ListNode *pos_prev = pos->prev;
    pos_prev->next = newnode;
    newnode->prev = pos_prev;
    newnode->next = pos;
    pos->prev = newnode;
}

6.7带头双向循环链表删除pos位置

void ListErase(ListNode* pos)//删除pos位置的节点
{
    assert(pos);
    ListNode *pos_next = pos->next;
    ListNode *pos_prev = pos->prev;
    pos_prev->next = pos_next;
    pos_next->prev = pos_prev;
    free(pos);
}

6.8带头双向循环链表的尾插尾删

void ListPushBack(ListNode* phead,LTDataType x)//尾插
{
    assert(phead);
    ListInsert(phead,x);
}

void ListPopBack(ListNode* phead)//尾删
{
    assert(phead);
    ListErase(phead->prev);
}

6.9带头双向循环链表的头插头删

void ListPushFront(ListNode* phead,LTDataType x)//头插
{
    assert(phead);
    ListInsert(phead->next,x);
}

void ListPopFront(ListNode* phead)//头删
{
    assert(phead);
    ListErase(phead->next);
}

6.10带头双向循环链表的长度求解

int ListSize(ListNode* phead)//求链表的长度
{
    assert(phead);
    int len = 0;
    ListNode *phead_next = phead->next;
    while(phead != phead_next)
    {
        len++;
        phead_next = phead_next->next;
    }
    return len;
}

6.11带头双向循环链表的寻找某一节点

ListNode*ListFind(ListNode* phead, LTDataType num)//寻找某一个节点
{
    assert(phead);
    ListNode *find = phead->next;
    while(find != phead)
    {
        if(find->data == num)
        {
            return find;
        }
        find = find->next;
    }
    return NULL;
}

7.顺序表和链表的区别

不同点顺序表链表

存储空间上 

物理上一定连续逻辑一定连续,物理不一定

任意位置插入或者删除

元素

需要覆盖数据通过指针就能找到

插入 

动态顺序表需要扩容没有容量概念,需要一个给一个

缓存利用率

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1097546.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

图片批处理工具 PhotoMill X直装 for mac

PhotoMill X是一款强大的图像处理软件&#xff0c;它可以帮助用户快速地对照片进行编辑、调整和转换。它支持在单个或批量模式下处理大量的图像文件&#xff0c;并具有直观的用户界面和易于使用的工具。 PhotoMill X具有的功能有&#xff1a; 裁剪、缩放、旋转、调整明暗度、…

32 数据分析(下)pandas介绍

文章目录 工具excelTableauPower Queryjupytermatplotlibnumpypandas数据类型Series基础的SeriesSeries的字典操作增加表的索引名字和表名字索引操作 DataFrameDataFrame 的基础使用DataFrame的列方法------理解DataFrame的行列方法------使用loc 与 iloc 对齐操作SeriesDataFr…

寻找小红书达人技巧有哪些,小红书行业黑话汇总!

媒介在工作的时候&#xff0c;需要对本行业的名词有一定的了解&#xff0c;比如说在媒介进行达人探寻的过程中&#xff0c;对行业名词&#xff0c;也就是俗称的互联网黑话具有一定的敏感性&#xff0c;是大有帮助的。今天就来分享一下&#xff0c;寻找小红书达人技巧有哪些&…

【广州华锐互动】VR模拟电力生产事故,切身感受危险发生

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经在各个领域中得到了广泛的应用。其中&#xff0c;VR技术在电力安全事故还原中的应用&#xff0c;不仅可以帮助我们更好地理解和预防事故的发生&#xff0c;还可以为事故调查提供更为准确和直观的证据…

mysql自定义函数

函数简介 mysql 5.0开始支持函数&#xff0c;函数是存在数据库中的一段sql集合&#xff0c;调用函数可以减少很多工作量&#xff0c; 减少数据在数据库和应用服务器上的传输&#xff0c;对于提高数据处理的效率。参数类型为in类型&#xff0c;函数必须有返回值&#xff0c; 与…

13年测试老鸟,性能测试内存泄露——案例分析(超细整理)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、环境配置 1&a…

epiiAdmin框架注意事项

1&#xff0c;epiiAdmin文档地址&#xff1a; 简介/安装 EpiiAdmin中文文档 看云 2&#xff0c;项目性想新建模块 composer.json文件——autoload选项——psr-4下增加模块名称&#xff0c;然后执行composer update命令。 "autoload": {"psr-4": {"…

springboot+html实现简单注册登录

前端&#xff1a; register.html <!DOCTYPE html> <html lang"en" > <head><meta charset"UTF-8"><title>register</title><link rel"stylesheet" type"text/css" href"/css/style.css&…

Find My产品|智能头盔与苹果Find My结合,智能防丢,全球定位

智能头盔作为一种新兴的智能穿戴设备&#xff0c;融合了先进的技术和先进的安全特性。当下&#xff0c;全球智能头盔市场规模预计将在未来几年内达到令人吃惊的规模。智能头盔在运动、娱乐、工业等领域具有广泛的应用。 智能头盔作为一种能够提供保护和监测功能的穿戴设备&…

scrollIntoView使用与属性详解

scrollIntoView 使用与属性详解 效果图如下图所示 如果要想让元素滚动到指定位置 window.onload function () {containerItems[6].scrollIntoView({ behavior: "smooth" }); };js 代码 const containerItems document.querySelectorAll(".container div&…

jmeter(二):jmeter组件总结,利用取样器中http发送请求

JMeter 的主要测试组件总结如下 1. 测试计划是使用 JMeter 进行测试的起点&#xff0c;它是其它 JMeter 测试元件的容器 2. 线程组代表一定数量的并发用户&#xff0c;它可以用来模拟并发用户发送请求。实际的 请求内容在Sampler中定义&#xff0c;它被线程组包含。 3. 监听…

docker ---rabbitmq 安装

第一步:安装rabbitmq sudo docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.9-management第二步&#xff1a;安装延迟队列插件&#xff08;因为使用了延迟队列&#xff09; 1、将插件 放入 /plugins/ 目录下&#xff08;需要先将插件文件放入宿主…

C++项目实战——基于多设计模式下的同步异步日志系统-⑩-异步缓冲区类与异步工作器类设计

文章目录 专栏导读异步缓冲区设计思想异步缓冲区类设计异步工作器类设计异步日志器设计 专栏导读 &#x1f338;作者简介&#xff1a;花想云 &#xff0c;在读本科生一枚&#xff0c;C/C领域新星创作者&#xff0c;新星计划导师&#xff0c;阿里云专家博主&#xff0c;CSDN内容…

数据结构----算法--排序算法

数据结构----算法–排序算法 一.冒泡排序&#xff08;BubbleSort&#xff09; 1.冒泡排序的核心思想 相邻两个元素进行大小比较&#xff0c;如果前一个比后一个大&#xff0c;就交换 注意&#xff1a; 在冒泡排序的过程中&#xff0c;促进了大的数往后去&#xff0c;小的数…

2023年中国功率半导体分立器件发展趋势分析:产品向高效率、低成本发展[图]

功率半导体分立器件是指被规定完成某种基本功能&#xff0c;并且本身在功能上不能再细分的半导体器件。功率半导体分立器件的应用几乎覆盖所有的电子制造业&#xff0c;传统应用领域包括消费电子、网络通讯等&#xff1b;近年来&#xff0c;汽车电子及充电系统、新能源发电等领…

log4j2同步日志引发的性能问题 | 京东物流技术团队

1 问题回顾 1.1 问题描述 在项目的性能测试中&#xff0c;相关的接口的随着并发数增加&#xff0c;接口的响应时间变长&#xff0c;接口吞吐不再增长&#xff0c;应用的CPU使用率较高。 1.2 分析思路 谁导致的CPU较高&#xff0c;阻塞接口TPS的增长&#xff1f;接口的响应时…

零基础实战部署云服务器项目-服务器部署(window server2012)

目录 什么是服务器呢&#xff1f; 项目服务器运行环境 一、申请服务器 1.找店铺 2.1选配置 型号 2.2配置&#xff08;具体配置&#xff09; 3.下单 4.收货 情况一&#xff1a;弹窗有进入控制台 情况二&#xff1a;手快关闭了窗口 所以下一步我们远程连接 方法二&#xff1a;win…

实验室安全巡检管理系统—全面安全检查

安全巡检管理系统可以代替传统的线下纸质巡查&#xff0c;微信扫码进行巡检记录、隐患上报、设备保养维护等工作&#xff0c;管理人员可远程实时查看巡查情况&#xff0c;同时在巡查结束后对现场问题进行闭环整改管理。大大提高现场巡查效率&#xff0c;辅助用户对巡查工作实时…

代码随想录二刷 Day 35

122.买卖股票的最佳时机 II 数组两两求差&#xff0c;然后把正数加起来 class Solution { public:int maxProfit(vector<int>& prices) {int sum0;int diff0;for(int i0;i<prices.size()-1;i){diff prices[i1] - prices[i];if(diff>0){sumdiff;}} return su…