数据结构C语言版 —— 链表增删改查实现(单链表+循环双向链表)

news2024/11/26 14:46:06

文章目录

  • 链表
    • 1. 链表的基本概念
    • 2. 无头非循环单链表实现
      • 1) 动态申请节点
      • 2) 打印链表元素
      • 3) 插入节点
        • 头插法
        • 尾插法
        • 在指定位置之前插入
        • 在指定位置之后插入
      • 4) 删除节点
        • 删除头部节点
        • 删除末尾节点
        • 删除指定位置之前的节点
        • 删除指定位置之后的节点
        • 删除指定位置的节点
      • 5) 查找元素
      • 6) 销毁链表
    • 3. 带头循环双向链表实现
      • 1) 初始化链表
      • 2) 插入节点
        • 头插法
        • 尾插法
        • 指定位置插入
      • 3) 打印链表
      • 4) 节点删除
        • 删除首节点
        • 删除末尾节点
        • 删除指定位置的节点
      • 5) 双向链表的查找
      • 6) 销毁链表
    • 4. 顺序表对比链表


链表

1. 链表的基本概念

链表是用一组任意的额存储单元存储线性表的数据元素(这组存储单元可以是连续的,也可以是不连续的)。简单来说链表是一种物理结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。

在数据结构中,链表的结构非常多样,以下情况结合起来有8种结构的链表。

  1. 单链表,双链表
  2. 带头,不带头
  3. 循环,非循环

单链表和双链表结构

在这里插入图片描述

不带头单链表和带头单链表

带头的链表有一个哨兵节点,这个节点不存储数据。它始终是在链表的第一位,头插数据都往它后后面插。

在这里插入图片描述

单链表和无头循环单链表

循环单链表它的最后一个元素的指针域存储着头节点的地址

在这里插入图片描述

带头循环双链表

双向链表它有3个域,一个存放数据元素,一个存放前一个节点的地址,一个存放后一个节点的地址。这是一个带头且循环的双向链表,它的哨兵节点存prev存放着最后一个节点的低地址,而最后一个节点的next存放的是哨兵节点的地址。

在这里插入图片描述

我这里主要实现无头不循环单向链表带头循环双向链表

2. 无头非循环单链表实现

无头单项非循环链表,结构比较简单,一般不会用来单独存放数据。实际中单链表更多是作为其他高阶数据结构的子结构,比如哈希表、图的邻接表等。

单链表结构

#define SLTDateType int
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}ListNode;

我这里实现一些主要的接口

// 动态申请一个节点
ListNode* BuySListNode(SLTDateType data);
// 尾插法
void SListNodePushBack(ListNode** pList, SLTDateType data);
// 头插法
void SListNodePushFront(ListNode** pList, SLTDateType data);
// 打印链表
void SListNodePrint(ListNode* pList);
// 删除头部元素
void SListNodePopFront(ListNode** ppList);
// 删除末尾元素
void SListNodePopBack(ListNode** ppList);
// 查找元素
ListNode* SListFind(ListNode* pList, SLTDateType data);
// 在pos位置之前插入元素
void SListInsertBefore(ListNode** ppList, ListNode* pos, SLTDateType data);
// 在pos位置之后插入元素
void SListInsertAfter(ListNode** ppList, ListNode* pos, SLTDateType data);
// 删除pos位置的元素
void SListNodePopCurrent(ListNode** ppList, ListNode* pos);
// 删除pos位置之前的元素
void SListNodePopBefore(ListNode** ppList, ListNode* pos);
// 删除pos位置之后的元素
void SListNodePopAfter(ListNode** ppList, ListNode* pos);
// 销毁链表
void SListEraseAfter(ListNode* ppList);

1) 动态申请节点

链表的节点是用一个向堆区申请一个

//动态申请一个节点
ListNode* BuySListNode(SLTDateType data)
{
	ListNode* node = (ListNode*)(malloc(sizeof(ListNode)));
	if (node == NULL)
	{
		printf("申请失败\n");
	}
	else
	{
		node->data = data;
		node->next = NULL;
	}
	return node;
}

2) 打印链表元素

//打印链表
void SListNodePrint(ListNode* pList)
{
	ListNode* cur = pList;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

这个代码的时间复杂度为 O ( N ) O(N) O(N)

3) 插入节点

头插法

通过头插法向链表头部插入一个元素,分为以下步

  • 判断是否首次插入
  • 如果不是首次插入,把申请的节点下一个节点指向头节点,再把头节点指向node节点
//头插法
void SListNodePushFront(ListNode** ppList, SLTDateType data)
{
	assert(ppList);
	ListNode* node = BuySListNode(data);
	//首次插入
	if (*ppList == NULL)
	{
		*ppList = node;
	}
	else
	{
		node->next = *ppList;
		*ppList = node;
	}
}

这个代码的时间复杂度为 O ( 1 ) O(1) O(1)

尾插法

尾插法是向链表末尾插入一个元素

  • 同样要判断是否是第一次插入
  • 如果不是第一个插入,就遍历到最后一个节点,把最后一个节点的Next指向申请的节点。
//尾插法
void SListNodePushBack(ListNode** ppList, SLTDateType data)
{
    assert(ppList);
	ListNode* node = BuySListNode(data);
	//如果是第一次插入
	if (*ppList == NULL)
	{
		*ppList = node;
	}
	else
	{
		ListNode* cur = *ppList;
		while (cur->next != NULL)
		{
			cur = cur->next;
		}
		cur->next = node;
	}
}

这个代码涉及到遍历整个链表,所以时间复杂度为 O ( N ) O(N) O(N)

在指定位置之前插入

在指定位置之前插入元素比较复杂,要考虑两种情况

  1. 要在头节点之前插入
  2. 如果是其它位置就需要记录它的前驱节点
//在pos位置之前插入元素
void SListInsertBefore(ListNode** ppList, ListNode* pos, SLTDateType data)
{
	assert(ppList && pos);
	if (*ppList == pos)
	{
		//如果要插入的是头节点的位置
		//申请节点
		ListNode* node = BuySListNode(data);
		node->next = *ppList;
		*ppList = node;
	}
	else
	{
		//遍历到pos位置
		ListNode* cur = *ppList;
		ListNode* prev = *ppList;
		while (cur != NULL)
		{
			if (cur == pos)//注意这比较的是内存地址
			{
				//申请节点
				ListNode* node = BuySListNode(data);
				node->next = pos;
				prev->next = node;
				break;
			}
			prev = cur;
			cur = cur->next;
		}
	}
}

这个代码的时间复杂度为 O ( N ) O(N) O(N)

在指定位置之后插入

这个比较简单直接遍历到对应位置就好,注意修改节点指向的代码顺序!

//在pos位置之后插入元素
void SListInsertAfter(ListNode** ppList, ListNode* pos, SLTDateType data)
{
	assert(ppList && pos);
	ListNode* cur = *ppList;
	//遍历到pos位置
	while (cur != NULL)
	{
		if (cur == pos)
		{
			ListNode* node = BuySListNode(data);
			node->next = cur->next;//顺序不能错
			cur->next = node;
			break;
		}
		cur = cur->next;
	}
}

这个代码的时间复杂度为 O ( N ) O(N) O(N)

4) 删除节点

删除头部节点

拿一个临时遍历记录头节点的位置,再修改后节点的指向,最后free掉要删除的节点。

//删除头部元素
void SListNodePopFront(ListNode** ppList)
{
    assert(ppList);
	//为NULL情况
	if (*ppList == NULL)
	{
		return;
	}
	else
	{
		ListNode* cur = *ppList;
		*ppList = (*ppList)->next;
		free(cur);
		cur = NULL;
	}
}

这个代码的时间复杂度为 O ( 1 ) O(1) O(1)

删除末尾节点

删除尾节点需要考虑到三种情况

  1. 链表为NULL
  2. 只有一个节点情况
  3. 多个节点情况
//删除末尾元素
void SListNodePopBack(ListNode** ppList)
{
	assert(ppList);

	//为NULL情况
	if (*ppList == NULL)
	{
		return;
	}
	else if ((*ppList)->next == NULL)
	{
		//只有一个节点情况
		free(*ppList);
		*ppList = NULL;
	}
	else
	{
		//多个节点情况
		ListNode* cur = *ppList;
		ListNode* prev = *ppList;
		while ((cur->next) != NULL)
		{
			prev = cur;
			cur = cur->next;
		}
		free(cur);
		prev->next = NULL;
	}
}

这个代码的时间复杂度为 O ( N ) O(N) O(N)

删除指定位置之前的节点

这个操作也要考虑到3种情况

  1. 如果只有一个节点,或者传递的是头节点是无法删除的
  2. 有两个节点,要删除的是头节点
  3. 其他情况

在删除的时候都需要记录要删除的前一个节点的位置!

// 删除pos位置之前的元素
void SListNodePopBefore(ListNode** ppList, ListNode* pos)
{
	assert(ppList && pos);
	if (*ppList == pos)
	{
		//要删除的时头节点前面的元素
		return;
	}

	ListNode* cur = *ppList;
	ListNode* prev = *ppList;
	while (cur != NULL)
	{
		
		if (cur->next == pos)
		{
			if (cur == prev)
			{
				//要删除的是头节点
				*ppList = (*ppList)->next;
				free(prev);
				prev = NULL;
				cur = NULL;
				break;
			}
			else
			{
				//其他情况
				prev->next = cur->next;
				free(cur);
				cur = NULL;
				prev = NULL;
				break;
			}
		}
		else
		{
			prev = cur;
			cur = cur->next;
		}
		
	}
}

这个代码的时间复杂度为 O ( N ) O(N) O(N)

删除指定位置之后的节点

直接遍历到删除节点之前两个节点进行删除

// 删除pos位置之后的元素
void SListNodePopAfter(ListNode** ppList, ListNode* pos)
{
	assert(ppList && pos);
	
	
	ListNode* cur = *ppList;

	while (cur->next != NULL)
	{
		if (cur == pos)
		{
			cur->next = cur->next->next;
			break;
		}
		cur = cur->next;
	}
	
}

这个代码的时间复杂度为 O ( N ) O(N) O(N)

删除指定位置的节点

要考虑两种情况

  1. 要删除的的是头节点
  2. 其它情况(需要记录删除节点的前驱)
// 删除pos位置的元素
void SListNodePopCurrent(ListNode** ppList, ListNode* pos)
{
	assert(ppList && pos);
	//如果要删除的是头节点
	if (*ppList == pos)
	{
		*ppList = (*ppList)->next;
	}
	else
	{
		ListNode* cur = *ppList;
		ListNode* prev = *ppList;
		while (cur != NULL)
		{
			if (cur == pos)
			{
				prev->next = cur->next;
				break;
			}
			prev = cur;
			cur = cur->next;
		}
	}
	
}

这个代码的时间复杂度为 O ( N ) O(N) O(N)

5) 查找元素

查找指定节点通过遍历就好,这个代码也可以兼顾修改节点数据。

//查找元素
ListNode* SListFind(ListNode* pList, SLTDateType data)
{
	if (pList == NULL)
	{
		return NULL;
	}
	ListNode* cur = pList;
	while (cur != NULL)
	{
		if (cur->data == data)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

查找的时间复杂度为 O ( N ) O(N) O(N)

6) 销毁链表

通过双指针直接遍历链表,边遍历边free释放掉节点。

//销毁链表
void SListEraseAfter(ListNode* pList)
{
	assert(pList);

	ListNode* cur = pList->next;
	ListNode* curNext = NULL;
	while (cur != NULL)
	{
		curNext = cur->next;
		free(cur);
		cur = curNext;
	}
	free(pList);//释放头节点
}

这个代码的时间复杂为 O ( N ) O(N) O(N)

3. 带头循环双向链表实现

带头双向循环链表结构复杂,一般用于单独存储数据。在实际中使用链表,一般都是带头双向循环链表,虽然这个链表结构复杂,但是实现起来却是比较简单的。带头循环双向链表有以下几个特点:

  • 最后一个节点后的下一个节点指向哨兵节点
  • 哨兵节点的前一个节点指向链表的最后一个节点
  • 头插数据永远往哨兵节点后插

在这里插入图片描述

带头循环双向链表结构

typedef int LTDataType;

typedef struct ListNode
{
	LTDataType data;//数据
	struct ListNode* prev;//节点前驱
	struct ListNode* next;//节点后继
}ListNode;

// 动态申请一个节点
ListNode* BuyListNode(LTDataType data);
// 初始化双向链表
ListNode* ListNodeInit();
// 打印双向链表
void ListNodePrint(ListNode* pHead);
// 销毁双向链表
void ListDestory(ListNode* pHead);

// 双向链表头插
void ListNodePushFront(ListNode* pHead, LTDataType data);
// 双向链表尾插
void ListNodePushBack(ListNode* pHead, LTDataType data);
// 双向链表指定位置之前插入
void ListPosInsertBefore(ListNode* pHead, ListNode* pos, LTDataType data);
// 双向链表指定位置之后插入
void ListPosInsertAfter(ListNode* pHead, ListNode* pos, LTDataType data);

// 双向链表删除首节点
void ListNodePopFront(ListNode* pHead);
// 双向链表删除尾节点
void ListNodePopBack(ListNode* pHead);
// 双向链表删除指定位置节点
void ListNodePopCurrent(ListNode* pHead, ListNode* pos);
// 双向链表的查找
ListNode* ListNodeFind(ListNode* pHead, LTDataType data);

1) 初始化链表

要想初始化链表必须要有申请节点,所以封装一个函数来申请节点。

// 动态申请一个节点
ListNode* BuyListNode(LTDataType data)
{
	ListNode* newNode = (ListNode*)(malloc(sizeof(ListNode)));
	if (newNode == NULL)
	{
		printf("空间申请失败\n!");
		exit(-1);
	}
	newNode->data = data;
	newNode->prev = NULL;
	newNode->next = NULL;

	return newNode;
}

带头循环的双向链表初始化要先申请一个节点作为哨兵节点,这个节点不存放数据起一个标识作用,它永远位于首节点前面。初始化时先让哨兵节点的前驱和后继都指向自己。

在这里插入图片描述

// 初始化双向链表
ListNode* ListNodeInit(LTDataType data)
{
	// 申请一个头节点作为哨兵节点
	ListNode* head = BuyListNode(data);
	//让这个哨兵节点的前驱和后继都先指向自己
	head->prev = head;
	head->next = head;

	return head;
}

2) 插入节点

头插法

头插法只需要把新节点插入到哨兵节点后面就可以了,注意修改 节点指向顺序

在这里插入图片描述

// 双向链表头插
void ListNodePushFront(ListNode* pHead, LTDataType data)
{
	assert(pHead);
	
	ListNode* node = BuyListNode(data);
	//头插一律插到哨兵头节点后面
	node->next = pHead->next;
	node->prev = pHead;
	pHead->next->prev = node;
	pHead->next = node;
	
}

尾插法

尾插法和头插法类似,只不过它是把节点插到链表的末尾。

在这里插入图片描述

// 双向链表尾插
void ListNodePushBack(ListNode* pHead, LTDataType data)
{
	assert(pHead);
	ListNode* node = BuyListNode(data);
	node->next = pHead;
	node->prev = pHead->prev;
	pHead->prev->next = node;
	pHead->prev = node;
	
}

指定位置插入

指定位置前或者指定位置后插入,比较简单只需要修改指向即可。

指定位置之前插入

// 双向链表指定位置之前插入
void ListPosInsertBefore(ListNode* pHead, ListNode* pos, LTDataType data)
{
	assert(pHead && pos);
	ListNode* node = BuyListNode(data);
	
	node->prev = pos->prev;
	pos->prev->next = node;
	pos->prev = node;
	node->next = pos;
	
}

指定位置之后插入

// 双向链表指定位置之后插入
void ListPosInsertAfter(ListNode* pHead, ListNode* pos, LTDataType data)
{
	assert(pHead && pos);
	ListNode* node = BuyListNode(data);
	node->next = pos->next;
	pos->next->prev = node;
	pos->next = node;
	node->prev = pos;
}

3) 打印链表

因为这是带头循环链表,所以要从哨兵节点后一个节点开始遍历,知道遇到哨兵节点就结束遍历

// 打印双向链表
void ListNodePrint(ListNode* pHead)
{
	assert(pHead);
	ListNode* cur = pHead->next;

	while (cur != pHead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");

}

4) 节点删除

删除首节点

注意删除的不是哨兵节点,而是首节点,也就是哨兵节点后面那一个节点

在这里插入图片描述

// 双向链表删除首节点
void ListNodePopFront(ListNode* pHead)
{
	assert(pHead);
	if (pHead->next == pHead)
	{
		//没有节点
		return;
	}
	ListNode* cur = pHead->next;
	pHead->next = cur->next;
	cur->next->prev = pHead;
	free(cur);
}

删除末尾节点

在这里插入图片描述

// 双向链表删除尾节点
void ListNodePopBack(ListNode* pHead)
{
	assert(pHead);
	if (pHead->prev == pHead)
	{
		//没有节点
		return;
	}
	ListNode* cur = pHead->prev;
	pHead->prev = cur->prev;
	cur->prev->next = pHead;
	free(cur);

}

删除指定位置的节点

这个只要注意修改指向顺序即可

// 双向链表删除指定位置节点
void ListNodePopCurrent(ListNode* pHead, ListNode* pos)
{
	assert(pHead && pos);
	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);

}

5) 双向链表的查找

和打印类似都是遍历

// 双向链表的查找
ListNode* ListNodeFind(ListNode* pHead, LTDataType data)
{
	assert(pHead);
	ListNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->data == data)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

6) 销毁链表

// 销毁双向链表
void ListDestory(ListNode* pHead)
{
	assert(pHead);

	ListNode* cur = pHead->next;
	ListNode* curNext = NULL;
	while (cur != pHead)
	{
		curNext = cur->next;
		free(cur);
		cur = curNext;
	}
	//最后删除哨兵节点
	free(pHead);
}

4. 顺序表对比链表

顺序表的优点:

  1. 顺序表支持随机访问
  2. 顺序表的cpu高速缓存命中率高(物理空间是连续的)

顺序表的缺点:

  1. 空间不够需要扩容扩容存在着一定的内存消耗,可能存在着一定的空间浪费
  2. 在头部或者中间插入删除元素需要挪动元素,时间复杂度为 O ( N ) O(N) O(N),效率较低。

链表的优点:

  1. 按需申请,不存在空间浪费
  2. 任意位置插入的时间复杂为 O ( 1 ) O(1) O(1)(不包括遍历)

链表的缺点:

  1. 不支持下标的随机访问

如何理解,顺序表的cpu高速缓存命中率高,链表的高速缓存命中率低

我们知道CPU的访问速度是远远高于内存的,高速缓存就是为了平衡CPU和内存中间的性能差异,分为 L1、L2、L3 三种高速缓存。

在这里插入图片描述

CPU在访问内存的时候会进行预加载,把一部分数据加载到高速缓存中。CPU就会先看高速缓存中是否存在需要的数据,如果存在就是命中,不存在就是没有命中,没有命中的数据。

假设我们要打印顺序表和链表。

在这里插入图片描述

我们知道顺序表的物理空间是连续的,假设高速缓存行中一次性加载64个字节。内存到高速缓存中去看发现没有我们需要的数据(未命中),就会进行预加载(通过地址找到对于的数据)。一次性把0x001后面连续64个字节的数据加载进来,之后每次打印的数据都在高速缓缓存中存在,所以都是命中的。

而如果打印的是链表,那么内存去高速缓存中看没有数据,就会把头节点的0x921后连续的64个字节加载到高速缓存中,接着打印0x110,因为链表在物理上不是连续的,所以在高速缓存中不存在数据,就又会进行预加载,就这样不断预加载打印、预加载打印,都是没有命中的。低命中会照成缓存污染,效率也会更低。


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

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

相关文章

【图像评价】无参考图像质量评价NIQE【含Matlab源码 681期】

⛄一、无参考图像质量评价NIQE简介 理论知识参考:通用型无参考图像质量评价算法综述 ⛄二、部分源代码 function [mu_prisparam cov_prisparam] estimatemodelparam(folderpath,… blocksizerow,blocksizecol,blockrowoverlap,blockcoloverlap,sh_th) % Input …

013 单词速记

converse adj.相反的,颠倒的 v.交谈 con(加强语气)vers(转反转)e->反转 n.conversation 谈话,对话 adv.conversely 相反的 controversy n.争端 contro(counter) 相反 vers 转 lead to ~ 导致争端 contraversial 有争议…

MySQL-内置函数

文章目录内置函数日期函数字符串函数数学函数其他函数内置函数 日期函数 current_date();current_time();current_timestamp(); 应用: 创建生日表 插入数据: 创建评论区 采用datetime 时间戳自动填充时间 查询两分钟之内发的帖子 评论时间2min…

C语言期末集训1(大一,超基础,小猫猫大课堂配套练习)——顺序结构和分支结构的题

更新不易,麻烦多多点赞,欢迎你的提问,感谢你的转发, 最后的最后,关注我,关注我,关注我,你会看到更多有趣的博客哦!!! 喵喵喵,你对我…

安科瑞配电室环境监控系统解决方案-Susie 周

1、概述 配电室综合监控系统包括智能监控系统屏、通讯管理机、UPS电源、视频监控子系统(云台球机、枪机)、环境监测子系统(温度、湿度、水浸、烟感)、控制子系统(灯光、空调、除湿机、风机、水泵)、门禁监…

Redis分布式锁 - 基础实现及优化

应用场景 互联网秒杀抢优惠卷接口幂等性校验 代码示例 案例1 - StringRedisTemplate基础实现 package com.wangcp.redisson;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org…

以流量为王的时代,如何获得不错的流量,泰山众筹如何脱颖而出?

由于互联网、疫情等因素的影响,实体业务变得越来越困难。许多实体店已经开始转向在线电子商务,但运营一个好的电子商务平台并不容易。没有稳定的流量和忠实的用户,很难达到理想的效果。那到底如何才能获得不错的“流量”呢?泰山众…

第十四届蓝桥杯集训——JavaC组第十三篇——for循环

第十四届蓝桥杯集训——JavaC组第十三篇——for循环 目录 第十四届蓝桥杯集训——JavaC组第十三篇——for循环 for循环(重点) 倒序迭代器 for循环死循环 for循环示例 暴力循环 等差数列求和公式 基础循环展开 循环控制语句 break结束 continue继续 for循环(重点) f…

【图像融合】多尺度奇异值分解图像融合【含Matlab源码 2040期】

⛄一、多尺度奇异值分解的偏振图像融合去雾算法简介 立足于提高传统算法的适应性,提高去雾图像的质量,本文设计了如图 2 所示的去雾算法流程。首先,使用基于最小二乘方法计算出更加精确的偏振信息,改善了以往偏振信息计算不准确的…

基于Qt(C++)实现(PC)学生信息管理系统【100010043】

学生信息管理系统 一、系统指南 本系统为表格式的学生信息管理系统,提供了文件新建、打开及保存功能,还可在表格中对数据进行增加、删除、修改、搜索,下面将一一介绍这些功能 1、新建文件 新建文件将会产生一个全新的空表格,…

基于java+springmvc+mybatis+vue+mysql的少儿编程管理系统

项目介绍 在国家重视教育影响下,教育部门的密确配合下,对教育进行改革、多样性、质量等等的要求,使教育系统的管理和运营比过去十年前更加理性化。依照这一现实为基础,设计一个快捷而又方便的网上少儿编程教育网站系统是一项十分…

原来这就是BFC,遇到样式问题别瞎搞了

看到一篇前端面试题,第一个问题是 什么是BFC ?,一下子唤起了我的辛酸回忆,那是在七月,在沪漂找工作的路上,预约的一个电话面试,眼看着时间就要到了,人生第一次进星巴克,提…

leetcode 337. 打家劫舍 III-[python3图解]-递归+记忆化搜索

题目 小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为root。除了root之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连…

【Python百日进阶-数据分析】Day130 - plotly柱状图(条形图):go.bar()实例1

文章目录4.2 plotly.graph_objects条形图4.2.1 go的基本条形图4.2.2 分组条形图4.2.3 堆叠条形图4.2.4 带悬停文本的条形图4.2.5 带直接文本标签的条形图4.2.6 使用uniformtext控制文本大小4.2.7 旋转条形图标签4.2.8 自定义单个条颜色4.2.9 自定义单个条的宽度4.2.10 自定义单…

NetInside网络分析为企业IT工作保驾护航(二)

前言 某企业的DMS经销商在线系统,最近一段时间运维人员经常接到反馈,DMS使用出现大量访问慢的情况,针对此情况进行监测分析。 该企业已部署NetInside流量分析系统,使用流量分析系统提供实时和历史原始流量,重点针对DMS系统性能进…

MobileNetV3基于NNI剪枝操作

NNI剪枝入门可参考:nni模型剪枝_benben044的博客-CSDN博客_nni 模型剪枝 1、背景 本文的剪枝操作针对CenterNet算法的BackBone,即MobileNetV3算法。 该Backbone最后的输出格式如下: 假如out model(x),则x[-1][hm]可获得heatma…

Spring框架04(Spring框架中AOP)

一、spring中bean的生命周期 1.singleton 容器启动的时候创建对象,容器正常关闭时销毁对象 2.prototype 获取对象的时候创建对象,spring容器不负责对象的销毁 生命周期的过程: 1.调用无参创建对象 2.调用set方法初始化属性 3.调用初始化…

知识付费系统源码,可直接打包成app、H5、小程序

知识付费,在近几年来,越来越受到大家的关注。知识付费系统源码是将知识通过互联网渠道变现的方式。以知识为载体,通过付费获得在线知识以及在线学习所带来的收益。知识付费平台主要以分享知识内容,内容分为直播、录播、图文等形式…

【从零开始学爬虫】采集收视率排行数据

l 采集网站 ​【场景描述】采集收视率排行数据。 【源网站介绍】收视率排行网提供收视率排行,收视率查询,电视剧收视率,综艺节目收视率和电视台收视率信息。 【使用工具】前嗅ForeSpider数据采集系统 【入口网址】http://www.tvtv.hk/archives/category/tv 【采集内容】 …

产线工控安全

场景描述 互联网飞速发展,工业4.0的大力推行,让工控产线更加智能化,生产网已经发展成一个组网的计算机环境。这些工控产线组网中的所有工控设备现在统称为主机。 信息化虽然提高各大企业的生产效率,但也会遭遇各类安全问题&…