双向链表实现简单的增删查改

2023-10-28

前言:上次分享了单向链表的增删查改,这次要介绍双向链表的增删查改,其实双向链表也有多种,这次主要介绍结构最复杂但是实现起功能反而最简单的带头双向循环链表,希望我的分享对各位有些许帮助。学习这篇文章的内容最好有这篇文章的基础


目录

一:双向链表的简单介绍

(1) 概述

(2) 图示 (带头双向循环链表)

二:用带头双向循环链表实现简单的增删查改

(1) 大致框架

(2) 四大基本功能函数的实现

1. 创建哨兵位头节点

2. 创建新节点

3. 打印链表

4. 销毁链表

(3) 尾插尾删的实现

1. 尾插

2. 尾删

(3) 头插头删的实现

1. 头插

2. 头删

(4) 特定位置的插入与删除

1. 特定位置的寻找

2. 特定位置的插入

3. 特定位置的删除

三:完整代码及测试样例的展示

(1) Test.c

(2) List.h

(3) List.c

(4) 测试样例的展示


一:双向链表的简单介绍

(1) 概述

双向链表的种类有多种——不带头,带头,循环,非循环等等,这里我们主要介绍最复杂的带头双向循环链表:结构最复杂,有一个存储无效数据的哨兵位头节点,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向 循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们函数功能的实现可以很好地体现。

而双向链表与单向链表最本质的区别就是: 单向链表只有一个指针域,而双向链表有两个指针域,所以双向链表可以灵活的找到一个节点的左右节点,使得相对于单向链表而言有很大的优势。

(2) 图示 (带头双向循环链表)


二:用带头双向循环链表实现简单的增删查改

(1) 大致框架

 Test.c——用于测试接口函数的功能

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"
void Test1()//测试尾插尾删
{

}
void Test2()//测试头插头删
{

}
void Test3()//测试特定位置的插入与删除
{

}
int main()
{
	Test1();//测试尾插尾删

	//Test2();//测试头插头删

	//Test3();//测试特定位置的插入与删除
	return 0;
}

List.h——用于各种定义

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//将int重定义为LTDataType,方便链表数据类型的更改(eg:int->double)
typedef int LTDataType;

//定义一个带头双向循环链表
typedef struct ListNode
{
	LTDataType data;//数据域
	struct ListNode* prev;//可以链接前一个节点的指针域
	struct ListNode* next;//可以链接后一个节点的指针域
} LTNode;

LTNode* ListInit();//创建哨兵位头节点
void ListPrint(LTNode* phead);//打印
LTNode* BuyListNode(LTDataType x);//创建新节点
void ListDestroy(LTNode* phead);//销毁链表

void ListPushBack(LTNode* phead, LTDataType x);//尾插
void ListPopBack(LTNode* phead);//尾删
void ListPushFront(LTNode* phead, LTDataType x);//头插
void ListPopFront(LTNode* phead);//头删

LTNode* ListNodeFind(LTNode* phead, LTDataType x);//找到链表中数据为x的第一个节点
void ListInsertNode(LTNode* pos, LTDataType x);//在pos节点前插入节点
void ListEraseNode(LTNode* pos);//删除pos节点

List.c——用于接口函数功能的实现

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"

LTNode* ListInit()//创建哨兵位头节点
{

}

void ListPrint(LTNode* phead)//打印
{

}

LTNode* BuyListNode(LTDataType x)//创建新节点
{

}

void ListPushBack(LTNode* phead, LTDataType x)//尾插
{

}

void ListPopBack(LTNode* phead)//尾删
{

}

void ListPushFront(LTNode* phead, LTDataType x)//头插
{

}

void ListPopFront(LTNode* phead)//头删
{

}

LTNode* ListNodeFind(LTNode* phead, LTDataType x)//找到链表中数据为x的第一个节点
{

}

void ListInsertNode(LTNode* pos, LTDataType x)//在pos节点前插入节点
{

}

void ListEraseNode(LTNode* pos)//删除pos节点
{

}

void ListDestroy(LTNode* phead)//销毁链表
{

}

(2) 四大基本功能函数的实现

1. 创建哨兵位头节点

对于带头双向循环链表而言,哨兵位头节点起到了至关重要的作用,所以实现带头双向循环链表的第一步就是创建一个哨兵位头节点:

LTNode* ListInit()//创建哨兵位头节点
{
	LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	LTNode* phead = tmp;
	phead->next = phead;
	phead->prev = phead;
	return phead;//返回开辟的头节点
}

带头双向循环链表中头节点的实际结构

2. 创建新节点

从之前分享的单链表增删查改的知识也可以很清楚的知道,在插入节点之前要先创建一个新节点,所以为了方便创建节点,直接分装在一个函数中实现即可:

LTNode* BuyListNode(LTDataType x)//创建新节点
{
	LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	LTNode* newnode = tmp;
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;//数据域为新节点的有效数据x
	return newnode;
}

3. 打印链表

跟单链表思路相似,遍历链表即可,不同的是二者的终止条件不同,由于这是循环链表,所以链表的终点不是NULL:

void ListPrint(LTNode* phead)//打印链表
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)//带头双向循环链表遍历一遍后会回到哨兵位头节点
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

4. 销毁链表

与打印的思路相同,遍历链表的过程中不断释放节点,回到头节点即可,这里需要特别注意的是:哨兵位头节点也是动态开辟出来的,所以也要进行释放并置空,而为了维持函数接口传递参数的一致性,该函数接口传递的是表示哨兵位头节点的一级指针,此时形参只是实参的一份临时拷贝,在函数内部改变无法引起其变化,所以哨兵位头节点的置空要在哨兵位头节点所在的作用域函数内实现。

void ListDestroy(LTNode* phead)//销毁链表————遍历即可
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->next != phead)
	{
		LTNode* curNext = cur->next;
		free(cur);
		cur = curNext;
		phead->next = cur;
		cur->prev = phead;
	}
	free(phead);
}

(3) 尾插尾删的实现

1. 尾插

void ListPushBack(LTNode* phead, LTDataType x)//尾插
{
	assert(phead);
	//1.常规思路———利用结构的特殊,直接形成链接关系即可
	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;

	//2.调用函数ListInsertNode,简化尾插

}

2. 尾删

void ListPopBack(LTNode* phead)//尾删
{
	assert(phead);
	assert(phead->next != phead);
	//1.常规思路————记录尾节点的前一个节点,把前一个结点当作新的尾节点,释放旧的尾节点
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	phead->prev = tailPrev;
	tailPrev->next = phead;
	free(tail);
	tail = NULL;

	//2.调用函数ListEraseNode,简化尾删

}

(3) 头插头删的实现

1. 头插

void ListPushFront(LTNode* phead,LTDataType x)//头插
{
	assert(phead);
	//1.常规思路————要知道链表真正的起点是哨兵位头节点的后一个节点
	LTNode* newnode = BuyListNode(x);
	LTNode* next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;

	//2.调用函数ListInsertNode,简化尾删

}

2. 头删

void ListPopFront(LTNode* phead)//头删
{
	assert(phead);
	assert(phead->next != phead);//删除前一定要判空,即不能删除哨兵位头节点
	//1.常规思路
	LTNode* cur = phead->next;
	LTNode* next = phead->next->next;
	free(cur);
	phead->next = next;
	next->prev = phead;

	//2.调用函数ListEraseNode,简化头删	
}

(4) 特定位置的插入与删除

1. 特定位置的寻找

LTNode* ListNodeFind(LTNode* phead, LTDataType x)//找到链表中数据为x的第一个节点并返回
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->next != phead)//遍历寻找
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

2. 特定位置的插入

//在pos节点前插入节点,pos节点是调用ListNodeFind函数得到的
void ListInsertNode(LTNode* pos, LTDataType x)
{
	LTNode* newnode = BuyListNode(x);//得到一个新节点
    //简单的链接过程
	LTNode* posPrev = pos->prev;
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

3. 特定位置的删除

void ListEraseNode(LTNode* pos)//删除pos节点
{
//删除pos节点需要知道pos节点的前一个节点与后一个节点,这样才能在删除后重新形成链接关系
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

注意:对于带头双向循环链表而言,这三个接口函数的实现很重要,其实只要掌握了这三个解控函数,就可以很快的构建起一个带头双向循环链表,因为插入与删除的相关接口函数直接调用这三个函数就能够实现:

//复用两个接口函数实现这些相关函数最重要的一点是真正理解带头双向循环链表的结构(上面那张图很重要)
void ListPushBack(LTNode* phead, LTDataType x)//尾插
{
	assert(phead);
	//1.常规思路
	
	//2.调用函数ListInsertNode,简化尾插
	ListInsertNode(phead, x);//在哨兵位头节点前面插入就可以实现尾插
}

void ListPopBack(LTNode* phead)//尾删
{
	assert(phead);
	assert(phead->next != phead);
	//1.常规思路
	
	//2.调用函数ListEraseNode,简化尾删
	ListEraseNode(phead->prev);//删除哨兵位头节点的前一个节点可以实现尾删
}

void ListPushFront(LTNode* phead, LTDataType x)//头插
{
	assert(phead);
	//1.常规思路
	
	//2.调用函数ListInsertNode,简化尾删
	ListInsertNode(phead->next, x);//在哨兵位头节点后面插入就可以实现头插
}

void ListPopFront(LTNode* phead)//头删
{
	assert(phead);
	assert(phead->next != phead);
	//1.常规思路
	
	//2.调用函数ListEraseNode,简化头删
	ListEraseNode(phead->next);//删除哨兵位头节点的后一个节点可以实现尾删
}

三:完整代码及测试样例的展示

(1) Test.c

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"

void Test1()//测试尾插尾删
{
	LTNode* phead = ListInit();//哨兵位头节点

	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushBack(phead, 3);
	ListPushBack(phead, 4);
	ListPushBack(phead, 5);
	ListPrint(phead);

	ListPopBack(phead);
	ListPopBack(phead);
	ListPopBack(phead);
	ListPrint(phead);

	ListDestroy(phead);
	phead = NULL;
}

void Test2()//测试头插头删
{
	LTNode* phead = ListInit();//哨兵位头节点

	ListPushFront(phead, 1);
	ListPushFront(phead, 2);
	ListPushFront(phead, 3);
	ListPushFront(phead, 4);
	ListPushFront(phead, 5);
	ListPrint(phead);

	ListPopFront(phead);
	ListPopFront(phead);
	ListPopFront(phead);
	ListPrint(phead);

	ListDestroy(phead);
	phead = NULL;
}

void Test3()//测试特定位置的插入与删除
{
	LTNode* phead = ListInit();//哨兵位头节点
	ListPushBack(phead, 1);
	ListPushBack(phead, 2);
	ListPushFront(phead, 3);
	ListPushFront(phead, 4);
	ListPushFront(phead, 5);
	ListPrint(phead);

	LTNode* pos = ListNodeFind(phead, 1);
	if (pos)
	{
		ListInsertNode(pos, 6);
	}
	ListPrint(phead);
	pos = ListNodeFind(phead, 3);
	if (pos)
	{
		ListInsertNode(pos, 7);
	}
	ListPrint(phead);

	pos = ListNodeFind(phead, 5);
	if (pos)
	{
		ListEraseNode(pos);
	}
	ListPrint(phead);
	pos = ListNodeFind(phead, 4);
	if (pos)
	{
		ListEraseNode(pos);
	}
	ListPrint(phead);

	ListDestroy(phead);
	phead = NULL;
}

int main()
{
	//Test1();//测试尾插尾删

	//Test2();//测试头插头删

	Test3();//测试特定位置的插入与删除
	return 0;
}

(2) List.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

//将int重定义为LTDataType,方便链表数据类型的更改(eg:int->double)
typedef int LTDataType;

//定义一个带头双向循环链表
typedef struct ListNode
{
	LTDataType data;//数据域
	struct ListNode* prev;//可以链接前一个节点的指针域
	struct ListNode* next;//可以链接后一个节点的指针域
} LTNode;

LTNode* ListInit();//创建哨兵位头节点
void ListPrint(LTNode* phead);//打印
LTNode* BuyListNode(LTDataType x);//创建新节点
void ListDestroy(LTNode* phead);//销毁链表

void ListPushBack(LTNode* phead, LTDataType x);//尾插
void ListPopBack(LTNode* phead);//尾删
void ListPushFront(LTNode* phead, LTDataType x);//头插
void ListPopFront(LTNode* phead);//头删

LTNode* ListNodeFind(LTNode* phead, LTDataType x);//找到链表中数据为x的第一个节点
void ListInsertNode(LTNode* pos, LTDataType x);//在pos节点前插入节点
void ListEraseNode(LTNode* pos);//删除pos节点

(3) List.c

#define _CRT_SECURE_NO_WARNINGS
#include "List.h"

LTNode* ListInit()//1.创建哨兵位头节点
{
	LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	LTNode* phead = tmp;
	phead->next = phead;
	phead->prev = phead;
	return phead;//返回开辟的头节点
}

LTNode* BuyListNode(LTDataType x)//2.创建新节点
{
	LTNode* tmp = (LTNode*)malloc(sizeof(LTNode));
	if (tmp == NULL)
	{
		printf("malloc fail\n");
		exit(-1);
	}
	LTNode* newnode = tmp;
	newnode->next = NULL;
	newnode->prev = NULL;
	newnode->data = x;//数据域为新节点的有效数据x
	return newnode;
}

void ListPrint(LTNode* phead)//3.打印链表
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)//带头双向循环链表遍历一遍后会回到哨兵位头节点
	{
		printf("%d ", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

void ListPushBack(LTNode* phead, LTDataType x)//4.尾插
{
	assert(phead);
	//1.常规思路
	LTNode* newnode = BuyListNode(x);
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;

	//2.调用函数ListInsertNode,简化尾插
	//ListInsertNode(phead, x);//在哨兵位头节点前面插入就可以实现尾插
}

void ListPopBack(LTNode* phead)//5.尾删
{
	assert(phead);
	assert(phead->next != phead);
	//1.常规思路
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	phead->prev = tailPrev;
	tailPrev->next = phead;
	free(tail);
	tail = NULL;

	//2.调用函数ListEraseNode,简化尾删
	//ListEraseNode(phead->prev);
}

void ListPushFront(LTNode* phead,LTDataType x)//6.头插
{
	assert(phead);
	//1.常规思路
	LTNode* newnode = BuyListNode(x);
	LTNode* next = phead->next;
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = next;
	next->prev = newnode;
	//2.调用函数ListInsertNode,简化尾删
	//ListInsertNode(phead->next, x);
}

void ListPopFront(LTNode* phead)//7.头删
{
	assert(phead);
	assert(phead->next != phead);
	//1.常规思路
	LTNode* cur = phead->next;
	LTNode* next = phead->next->next;
	free(cur);
	phead->next = next;
	next->prev = phead;
	//2.调用函数ListEraseNode,简化头删
	//ListEraseNode(phead->next);
}

LTNode* ListNodeFind(LTNode* phead, LTDataType x)//8.找到链表中数据为x的第一个节点并返回
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->next != phead)//遍历寻找
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

void ListInsertNode(LTNode* pos, LTDataType x)//9.在pos节点前插入节点,pos节点是调用ListNodeFind函数得到的
{
	LTNode* newnode = BuyListNode(x);
	LTNode* posPrev = pos->prev;
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

void ListEraseNode(LTNode* pos)//10.删除pos节点
{
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

void ListDestroy(LTNode* phead)//11.销毁链表————遍历即可
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur->next != phead)
	{
		LTNode* curNext = cur->next;
		free(cur);
		cur = curNext;
		phead->next = cur;
		cur->prev = phead;
	}
	free(phead);
}

(4) 测试样例的展示


总结:

虽然带头双向循环链表的结构比单向链表复杂很多,但是它对功能实现的难易程度以及自身的价值都优于单链表,所以大家在掌握单链表的同时也要掌握好双向链表,再次提醒各位,结构十分重要,结构又可以在图示中很好的体现出来,所以各位在数据结构的学习过程中一定要重视画图以及理解所化的图。就这样,我又分享了一次自己在学习道路上的理解,希望对各位有所帮助,再见。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

双向链表实现简单的增删查改 的相关文章

  • 【第一章】专栏介绍

    版本 修改时间 初稿 2023 03 26 补充 考研和就业的选择 2023 04 04 自我介绍 你好 我曾经是一名普通一本学生 专业是电子信息工程专业 从大二就开始独自一人自学后端开发 大三后面三年大部分时间都在图书馆或者实验室学习 在
  • 如何上传大文件(4GB)到虚拟机

    使用xhell上传大文件会报文件过大的异常 解决方案 可以使用 Everything 工具 实现快速便捷传送大文件到虚拟机 1 百度搜索Everything 进入官网下载 这个程序体量非常小 可以放心下载 2 下载完成后打开 点击工具一栏
  • linux下MySql服务器的安装(yum安装OK)

    root test219 mysql mysql V mysql Ver 14 14 Distrib 5 5 11 for Linux x86 64 using readline 5 1 mysql5 5在linux服务器上的安装 mysq
  • 2021.11.12总结

    把入门3循环结构的题大致写完了

随机推荐

  • 树莓派4B之Windows XP系统安装游戏(一)

    上一篇博文 树莓派4B安装windows xp windows 95 windows xp windows 95 for raspberry pi 4B 下一篇博文 树莓派4B之Windows XP系统安装游戏 二 目录 一 模拟器 游戏下
  • AI新手必看:如何区分参数和超参数

    相信所有人刚开始应用机器学习时 都会被两个术语混淆 计算机学科里有太多的术语 而且许多术语的使用并不一致 哪怕是相同的术语 不同学科的人理解一定有所不同 比如说 模型参数 model parameter 和 模型超参数 model Hype
  • 华为nova6se怎么升级鸿蒙,华为EMUI11支持哪些手机

    华为EMUI11适配机型有什么 首批支持EMUI11 更新的机型有 P40 系列 Mate30 系列 MatePad Pro系列等 10 款机型 先了解更多EMUI11适配机型相关内容的小伙伴下面和小编一起来看看吧 华为EMUI11适配机型
  • VC++ 图像颜色调节

    1 BMP图片在GDI方式下贴图 32位位图 半透明像素会显示黑色或白底 像素处理代码 void CrossImage CImage img if img IsNull return 确认该图像包含Alpha通道 if img GetBPP
  • JAVA的图形用户界面布局GUI入门(上)

    java的GUI企业里面用的比较少 现在主流的UI都使用HTML5 开发 Java提供了三个主要包 做GUI开发 java awt 包 主要提供字体 布局管理器 javax swing 包 商业开发常用 主要提供各种组件 窗口 按钮 文本框
  • 神经网络学习之一——M-P模型

    神经网络学习之一 M P模型 M P模型是什么 M P模型是于1943年美国神经生理学家沃伦 麦卡洛克 Warren McCuloch 和数学家沃尔特 皮茨 Walter Pitts 提出 是首个通过模仿神经元而形成的模型 结构图如下所示
  • 主机地址变更后,dubbo请求时依旧会寻址旧IP的问题

    机房迁移 导致测试服务器IP变更 比原于IP为192 168 1 105变更为10 1 9 120 服务源码未做任何变更 启动服务时依旧是旧地址请求 此问题由dubbo本地注册中心的缓存所致 清理掉即可 位置一般在于 用户目录 dubbo目
  • Redis(一)常见命令使用

    常见文件名 Redis cli使用命令 1 启动Redis 2 连接Redis 3 停止Redis 4 发送命令 1 redis cli带参数运行 如 2 redis cli不带参数运行 如 5 测试连通性 key操作命令 获取所有键 查询
  • PostgreSQL系列3:PostgreSQL导入导出SQL

    启动数据库 pg ctl D data db pgsql data l data db pgsql logs pgsql log start 关闭数据库 pg ctl D data db pgsql data stop 使用pgsql客户端
  • R语言实战学习--回归

    文章目录 普通最小二乘回归 OLS 简单线性回归 多项式回归 多元线性回归 回归诊断 标准方法 QQ图正态性检验 残差图 误差的独立性 成分残差图 偏残差图 线性 同方差性 线性模型假设综合验证 异常观测值 高杠杆值 强影响点 变量添加图
  • 爬虫基础————ip地址和url详解

    学习慕课网bobby老师的课程从零起步 系统入门Python爬虫工程师时做的笔记 有兴趣的同学可以去慕课网观看视频 1 ip地址 整个网络传输可以比作快递 数据就是快递包裹 会经过一系列中转站 分包捡包等操作 最后才送到客户手中 Ip地址就
  • Python程序:输出杨辉三角的几种办法

    文章目录 一 问题描述 二 问题分析 三 第一种方法 1 具体代码 2 运行结果 3 程序的改进 四 第二种方法 1 具体代码 2 运行结果 五 总结分析 一 问题描述 给定一个非负整数 n 生成 杨辉三角 的前 n行 在 杨辉三角 中 每
  • 【文献调研】多任务学习-Part1

    基于数据增强和多任务学习的突发公共卫生时间谣言识别研究 摘要 Motivation 通过引入多任务学习模型和数据增强方法 解决突发公共卫生事件情景下谣言识别任务数据不平衡且带标签数据量少的问题 Methods 首先提取突发公共卫生事件谣言文
  • 《Learning Spark》第八章:调优及调试spark应用

    2020 07 05 引言 我记得当时我就是因为使用hadoop太过费劲了 才上手的spark 然后因为自己的机器性能不行 又一点一点调优 当时调优的过程 主要是从底层的结构上来进行调优 主要就是那些worker数量以及内存大小等等 但是对
  • PyTorch和TensorFlow生成对抗网络学习MNIST数据集

    介绍 生成对抗网络 简称GAN 是最近开发的最受欢迎的机器学习算法之一 对于人工智能 AI 领域的新手 我们可以简单地将机器学习 ML 描述为AI的子领域 它使用数据来 教 机器 程序如何执行新任务 一个简单的例子就是使用一个人的脸部图像作
  • png四通道透明背景图成功加入到视频帧中 使用了mask原理

    import cv2 import ffmpeg import cv2 import numpy as np import glob video f D CCTV CCTV mp4 视频文件名 output f D CCTV logoaft
  • 共享内存---结构体使用

    共享内存主要是通过映射机制实现的 Windows 下进程的地址空间在逻辑上是相互隔离的 但在物理上却是重叠的 所谓的重叠是指同一块内存区域可能被多个进程同时使用 当调用 CreateFileMapping 创建命名的内存映射文件对象时 Wi
  • 网络基础-应用层:E-mail应用:SMTP协议,POP协议,IMAP协议

    Email应用的构成 邮件客户端 邮件服务器 SMTP协议 只支持文本 邮件服务器 邮箱 存储发给该用户的Email 消息队列 存储等待发送的Email SMTP协议 邮件服务器之间传递消息所使用的协议 客户端 发送消息的服务器 服务器 接
  • js实用方法记录-js动态加载css、js脚本文件

    js实用方法记录 动态加载css js 附送一个加载iframe h5打开app代码 1 动态加载js文件到head标签并执行回调 方法调用 dynamicLoadJs http www yimo link static js main m
  • 双向链表实现简单的增删查改

    前言 上次分享了单向链表的增删查改 这次要介绍双向链表的增删查改 其实双向链表也有多种 这次主要介绍结构最复杂但是实现起功能反而最简单的带头双向循环链表 希望我的分享对各位有些许帮助 学习这篇文章的内容最好有这篇文章的基础 目录 一 双向链