堆和堆排序【数据结构】

news2024/11/15 19:42:48

目录

  • 一、堆
    • 1. 堆的存储定义
    • 2. 初始化堆
    • 3. 销毁堆
    • 4. 堆的插入
      • 向上调整算法
    • 5. 堆的删除
      • 向下调整算法
    • 6. 获取堆顶数据
    • 7. 获取堆的数据个数
    • 8. 堆的判空
  • 二、Gif演示
  • 三、 堆排序
    • 1. 堆排序
      • (1) 建大堆
      • (2) 排序
    • 2.Topk问题
  • 四、完整代码
    • 1.堆的代码
      • Heap.c
      • Heap.h
      • test.c
    • 2. 堆排序的代码

前言:
什么是堆呢?
堆(Heap)是一种数据结构,它是 一种特殊的二叉树 ,其中父节点的键值总是大于或等于(或小于或等于)其任何一个子节点的键值。这意味着在堆中,根节点具有最大(或最小)键值。
堆:一般是数组数据看做一棵完全二叉树
完全二叉树的逻辑结构:
大堆01

  • 大堆: 任意一个父结点 大于等于 子结点
    大堆02

  • 小堆: 任意一个父结点 小于等于 子结点
    小堆
    数组存储完全二叉树
    在这里插入图片描述

一、堆

1. 堆的存储定义

因为存储结构,这里使用动态数组的形式来存放数据。但是也要注意其中的逻辑结构是完全二叉树。定义一个指针指向动态数组,定义存储堆的容量capacity,记录堆中的数据的个数size

代码

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;	//指向动态数组
	int capacity;	//堆的容量
	int size;		//堆中数据个数
}Heap;

2. 初始化堆

类似顺序表的初始化

代码

//初始化堆
void InitHeap(Heap* hp) 
{
	assert(hp);
	hp->a = NULL;
	hp->capacity = 0;
	hp->size = 0;
}

3. 销毁堆

避免内存泄漏

代码

//销毁堆
void DestroyHeap(Heap* hp) 
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->capacity = hp->size = 0;	
}

4. 堆的插入

重点:
在堆的插入前,我们需要注意的就是,首先判断其容量,然后使用realloc给数组分配空间
分配空间后,把数据插入堆。但是数据在插入堆时,由于堆一般分为大根堆和小根堆,所以这里使用的大根堆。 大堆:父结点的值大于等于其孩子结点的值
但是数据的值不能确定,这个时候就需要我们使用 堆的向上调整算法

向上调整算法

在数组的末端插入元素,进行与其父结点进行比较,大堆的情况下,如果其孩子结点的值大于父亲结点的值时,把插入的数据向上调整,向上调整的方法是:把插入的数据与其父结点进行交换,交换后继续判断是否还需要向上调整。(使用向上调整算法的条件是前面结点的树是构成堆的)

这里是使用的是数组,所以当插入元素时,在数组的末端进行插入数据

物理存储:
在这里插入图片描述
逻辑存储情况:
在这里插入图片描述

在插入的数据时,我们就需要考虑一下,
1. 当插入的孩子结点的值大于其父亲结点的值时,就向上调整
思路:
首先是根据孩子结的下标找父结点的下标,(孩子结点下标-1)/2 == 父结点下标,因为可能调整所以将判断条件放到循环里面(当然也可以用递归),在循环里面切记一定要及时更新当前孩子结点的下标和父结点的下标,孩子结点的值大于父结点的值就向上调整,否则就跳出循环。当孩子结点的下标到0时,向上调整完成,循环结束。
在这里插入图片描述
2. 当小于等于时,不需要调整
在这里插入图片描述
代码

//向上调整
void AdjustUp(HPDataType * a,int child) 
{
	//先找到父结点的下标
	int parent = (child - 1) / 2;
	while (child > 0)	//child等于0时,说明已经调整ok了
	{
		if (a[child] > a[parent])
		{
			swap(&a[child], &a[parent]);
			//可能会向上调整多次
			child = parent;
			parent = (parent - 1) / 2;
		}
		else 
		{
			break;
		}
	}
}

//堆的插入
void PushHeap(Heap* hp, HPDataType x)
{
	assert(hp);
	//堆满判断
	if (hp->capacity == hp->size) 
	{
		int newcapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;
		HPDataType* tmp = (HPDataType*)realloc(hp->a,sizeof(HPDataType)*newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		hp->a = tmp;
		hp->capacity = newcapacity;
	}
	//堆元素的插入
	hp->a[hp->size] = x;
	hp->size++;
	//堆的向上调整
	AdjustUp(hp->a,hp->size-1);
}

调试,查看一下数据存储情况
在这里插入图片描述

5. 堆的删除

堆中元素的删除,发现直接删除尾结点是简单的(size减一即可),但是,一般堆,删除元素都是删除的头结点。
直接删除头结点时:发现逻辑结构上变成了两棵树,这样直接删除头结点的方法不推荐。
在这里插入图片描述
交换结点再删除
头尾结点交换后,再删除尾结点,然后头结点使用堆的向下调整算法,调堆。
使用前提就是,当进行交换的时候,保证左右仍是堆。第一个结点与最后一个结点的值交换后,向下调整。

向下调整算法

这里调的堆是大堆(根结点的值大于左右孩子结点的值)

  • 第一步,找到第一个根结点的孩子结点,这里使用假设法,先让左孩子的值最大,再进行判断左孩子还是右孩子的值是最大的,找出大的。
  • 第二步与根结点进行比较,大于根结点就交换。
  • 及时更新父结点和孩子结点的下标
  • 注意当孩子结点值都小于父亲结点值就跳出循环;循环结束条件:孩子结点的下标大于数组最大的下标(就是孩子下标<数组的个数,child<size,大于等于时说明循环就结束了)。

过程:
在这里插入图片描述
调整后
在这里插入图片描述
这样就完成堆头结点的删除。
还需要注意的就是:
在这里插入图片描述

代码

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
	//先去找根结点的较大的孩子结点
	int child = 2 * parent + 1;
	//可能会向下调整多次
	while (child<size) 
	{
		//这里使用假设法,先假设左孩子的值最大
		//如果不对就进行更新
		if ((child+1 < size)&&a[child] < a[child+1]) 
		{
			child++;
		}
		//根结点与其孩子结点中的较大的一个进行交换
		if(a[child] > a[parent]) 
		{
			swap(&a[child],&a[parent]);
			//更新下标
			parent = child;
			child = 2 * parent + 1;
		}
		else 
		{
			break; //调完堆
		}
	}
}
//堆的删除
void PopHeap(Heap* hp)
{
	assert(hp);
	assert(hp->size>0);
	//头尾交换
	swap(&hp->a[0],&hp->a[hp->size-1]);
	hp->size--;
	//向下调整
	AdjustDown(hp->a,hp->size,0);
}

调试一下:
在这里插入图片描述
上图中指向下标6其实有数据65的,但是数组的下标有效范围在0-5

6. 获取堆顶数据

前提:堆得有数据
代码

//获取堆顶数据
HPDataType TopHeap(Heap* hp) 
{
	assert(hp);
	assert(hp->size>0);
	return hp->a[0];
}

7. 获取堆的数据个数

代码

//获取堆的数据个数
int SizeHeap(Heap* hp)
{
	assert(hp);
	return hp->size;
}

8. 堆的判空

代码

//堆的判空
bool EmptyHeap(Heap* hp) 
{
	assert(hp);
	return hp->size == 0;
}

二、Gif演示

调堆演示
堆动图gif

三、 堆排序

堆排序是一种选择排序。
堆排序:可以从小到大进行排序(使用大堆)。Top k 问题:取出最大的前k个值。

1. 堆排序

堆排序(Heap Sort)是一种基于完全二叉树的排序算法,它通过将待排序的元素建成一个二叉堆。堆排序的时间复杂度为O(nlogn),它是不稳定排序算法。

堆排序的思路如下:

  1. 升序排序为例,先建立一个大堆(父节点的值大于子节点的值),将待排序的元素都插入堆中。
  2. 将堆顶元素(最大值)与堆末尾元素交换,然后将堆的大小减1。
  3. 对堆顶元素向下调整操作,使得堆重新满足最大堆的性质。
  4. 重复2-3步,直到堆的大小为1。排序完成。

(1) 建大堆

使用 向下 调整算法来向上建堆:使用向下调整算法,把数组调成大堆
因为堆本身是一个完全二叉树,假设一共有h层,我们从第h-1层(即不是叶子结点的那一层开始)
因为是大堆,根结点的值大于孩子结点的值,从最下方使用向下调整来不断把较大的值来调到根节点。
注意:虽然使用的是向下调整算法,其实还是不断往上调整(把大的值调到上面)。
如图:
在这里插入图片描述
直到调整到第一层为止
建堆时间复杂度:O(N)

//堆排序
void HeapSort(int* arr, int n) 
{
	int i = 0;
	//使用向下调整算法向上调整,把大的值调到上方。
	for (i = (n - 1 - 1) / 2; i >= 0;i--)
	{
		//先找到数组最后端的父结点的下标
		//父结点的下标减一就是另一个
		//使用向下调整算法进行调整
		AdjustDown(arr,n,i);
	}
}

当然也可以用向上算法进行向上建堆。

思路:先让一个独自成堆,然后尾插一个结点,再进行与根结点进行比较,大于根结点的值就交换。
但是这个使用向上调整算法向上建堆的时间复杂度为:O(Nlog(N))

//向上调整算法进行堆排序
void HeapSort(int* arr, int n)
{
	int i = 0;
	//先让第一个结点独自成堆
	//再一次尾增结点进行向上调整
	for (i = 1; i < n; i++) 
	{
		AdjustUp(arr,i);
	}
}

(2) 排序

因为建成大堆后,将堆顶元素(最大值)与堆末尾元素交换

	//注意end 是从n-1开始的(数组最后一个元素的下标)
	int end = n-1;
	while (end > 0) 
	{
		//swap end = n-1 这表示下标
		swap(&arr[0],&arr[end]);
		//adjustdown 函数里面的end是元素的个数,所以不是先--end
		//所以
		AdjustDown(arr,end,0);
		end--;
	}

注意这里的end–,上述是从数组最后一个元素下标n-1 开始。堆的首元素与尾元素交换完后,接着就是堆的个数减1,然后下进行向下调整。这里的end–放在了最后。因为AdjustDown中的第二个参数是传的是堆的大小,正好数组下标n-1 , 堆由n减一也是 n -1。

下方给出了 end 从n 开始的优化,但是可读性就会下降

void HeapSort(int* arr, int n)
{
	int i = 0;
	//先建成一个大堆
	for (i = (n - 1 - 1) / 2; i >= 0;--i) 
	{
		AdjustDown(arr,n,i);
	}

	//堆顶元素与堆尾元素进行交换,进而把大的元素放到后面
	int end = n;
	while (end > 0) 
	{
		swap(&arr[--end],&arr[0]);
		AdjustDown(arr,end,0);
	}
}

2.Topk问题

topk问题,例如:在10000个数据排名中找出前10;或者在10000个数中找出最大的前10个

这里我们就以在10000个数中找出最大的前10(k = 10)个为例

首先应先准备数据,随机生成10000个数(注意srand函数只能生成30000多个随机数)
核心思想: 建一个可以存储k个数据的小堆。先把文件数据前10个数据读取到小堆中(进行向下调成小堆),然后再把文件中的其他数据一个一个读出与小堆的根结点的值进行比较,如果大于小堆的根结点,就进放入堆中,然后进行向下调堆。

//创建数据
void Createdata() 
{
	int n = 10000;
	srand((unsigned)time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file,"w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < n;i++)
	{
		int x = (rand() + i) % 100000;
		//把随机生成的数据写到fin文件中去
		fprintf(fin,"%d\n",x);
	}
	fclose(fin);
}
void PrintTopK(int k) 
{
	//从文件中读出数据
	const char* file = "data.txt";
	FILE* fout = fopen(file,"r");
	if (fout == NULL)
	{
		perror("fout error");
		return;
	}
	//将数据读出到容量为k的动态数组中
	int* arr = (int*)malloc(sizeof(int)*k);
	if (arr == NULL)
	{
		perror("malloc error");
		exit(-1);
	}
	//先把前k个数据放入数组中
	for (int i = 0; i < k; i++)
	{
		//将数据读到数组中
		fscanf(fout,"%d",&arr[i]);

		//放数据的同时进行建堆
		AdjustUp(arr,i);
	}

	int x = 0;
	//当文件里面的数据读完后会返回EOF
	while (fscanf(fout, "%d", &x) != EOF) 
	{
		//当从文件拿出的数据大于小堆中的数据时
		//将数据放到小堆中
		//并使用向下调整
		//这样每次来的比较大的数据就可以放到小堆中
		if (x > arr[0]) 
		{
			arr[0] = x;
			AdjustDown(arr,k,0);
		}
	}

	//打印数据
	for (int i = 0; i < k;i++) 
	{
		printf("%d ",arr[i]);
	}
	fclose(fout);

}

在这里插入图片描述

四、完整代码

1.堆的代码

Heap.c

#include "Heap.h"

//初始化堆
void InitHeap(Heap* hp) 
{
	assert(hp);
	hp->a = NULL;
	hp->capacity = 0;
	hp->size = 0;
}

//销毁堆
void DestroyHeap(Heap* hp) 
{
	assert(hp);
	free(hp->a);
	hp->a = NULL;
	hp->capacity = hp->size = 0;	
}

//交换两个数
void swap(HPDataType* s1,HPDataType* s2) 
{
	HPDataType tmp = *s1;
	*s1 = *s2;
	*s2 = tmp;
}
//向上调整
void AdjustUp(HPDataType * a,int child) 
{
	//先找到父结点的下标
	int parent = (child - 1) / 2;
	while (child > 0)	//child等于0时,说明已经调整ok了
	{
		if (a[child] > a[parent])
		{
			swap(&a[child], &a[parent]);
			//可能会向上调整多次
			child = parent;
			parent = (parent - 1) / 2;
		}
		else 
		{
			break;
		}
	}
}

//堆的插入
void PushHeap(Heap* hp, HPDataType x)
{
	assert(hp);
	//堆满判断
	if (hp->capacity == hp->size) 
	{
		int newcapacity = hp->capacity == 0 ? 4 : 2 * hp->capacity;
		HPDataType* tmp = (HPDataType*)realloc(hp->a,sizeof(HPDataType)*newcapacity);
		if (tmp == NULL)
		{
			perror("realloc fail");
			exit(-1);
		}
		hp->a = tmp;
		hp->capacity = newcapacity;
	}
	//堆元素的插入
	hp->a[hp->size] = x;
	hp->size++;
	//堆的向上调整
	AdjustUp(hp->a,hp->size-1);
}

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
	//先去找根结点的较大的孩子结点
	int child = 2 * parent + 1;
	//可能会向下调整多次
	while (child<size) 
	{
		//这里使用假设法,先假设左孩子的值最大
		//如果不对就进行更新
		if ((child+1 < size)&&a[child] < a[child+1]) 
		{
			child++;
		}
		//根结点与其孩子结点中的较大的一个进行交换
		if(a[child] > a[parent]) 
		{
			swap(&a[child],&a[parent]);
			//更新下标
			parent = child;
			child = 2 * parent + 1;
		}
		else 
		{
			break; //调完堆
		}
	}
}
//堆的删除
void PopHeap(Heap* hp)
{
	assert(hp);
	assert(hp->size>0);
	//头尾交换
	swap(&hp->a[0],&hp->a[hp->size-1]);
	hp->size--;
	//向下调整
	AdjustDown(hp->a,hp->size,0);
}

//获取堆顶数据
HPDataType TopHeap(Heap* hp) 
{
	assert(hp);
	assert(hp->size>0);
	return hp->a[0];
}

//获取堆的数据个数
int SizeHeap(Heap* hp)
{
	assert(hp);
	return hp->size;
}

//堆的判空
bool EmptyHeap(Heap* hp) 
{
	assert(hp);
	return hp->size == 0;
}

Heap.h

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;	//指向动态数组
	int capacity;	//堆的容量
	int size;		//堆中数据个数
}Heap;

//初始化堆
void InitHeap(Heap* hp);

//销毁堆
void DestroyHeap(Heap* hp);

//堆的插入
void PushHeap(Heap* hp, HPDataType x);

//堆的删除
void PopHeap(Heap*hp);

//获取堆顶数据
HPDataType TopHeap(Heap* hp);

//获取堆的数据个数
int SizeHeap(Heap* hp);

//堆的判空
bool EmptyHeap(Heap* hp);

test.c

#include "Heap.h"

void Test1() 
{
	Heap hp;
	InitHeap(&hp);
	PushHeap(&hp,49);
	PushHeap(&hp,65);
	PushHeap(&hp,34);
	PushHeap(&hp,25);
	PushHeap(&hp,37);
	PushHeap(&hp,27);
	PushHeap(&hp,19);
	//删除65
	PopHeap(&hp);
	//printf("堆的个数:%d\n",SizeHeap(&hp));
	//while (!EmptyHeap(&hp)) 
	//{
	//	printf("%d-", TopHeap(&hp));
	//	PopHeap(&hp);
	//}

	DestroyHeap(&hp);
	//27,19,34,65,49,25,37
}
int main() 
{
	Test1();
	return 0;
}

2. 堆排序的代码

//堆排序
void HeapSort(int* arr, int n) 
{
	int i = 0;
	//使用向下调整算法向上调整,把大的值调到上方。
	for (i = (n - 1 - 1) / 2; i >= 0;i--)
	{
		//先找到数组最后端的父结点的下标
		//父结点的下标减一就是另一个
		//使用向下调整算法进行调整
		AdjustDown(arr,n,i);
	}

	//进行排序
	//因为是大堆,所以根结点的值是最值
	//把最值与堆的最后一个结点进行交换
	//再把交换后的根节点进行向下调整
	//然后堆的大小减一
	

	//注意end 是从n-1开始的(数组最后一个元素的下标)
	int end = n-1;
	while (end > 0) 
	{
		//swap end = n-1 这表示下标
		swap(&arr[0],&arr[end]);
		//adjustdown 函数里面的end是元素的个数,所以不是先--end
		//所以
		AdjustDown(arr,end,0);
		end--;
	}
}

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

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

相关文章

RT-Thread: STM32 SPI使用流程

1.添加驱动 ①点开设置界面 ②勾选看门 SPI 驱动 ③点击保存 ④查看添加的驱动文件 drv_spi.c 2.打开驱动头文件定义 ①打开配置文件 ②打开定义 3.打开需要开启的SPI总线 打开 drivers 目录下的 board.h 用SPI搜索&#xff0c;找到如下文字&#xff0c;打开对应的宏。 /*-…

Windows 和 Anolis 通过 Docker 安装 Milvus 2.3.4

Windows 10 通过 Docker 安装 Milvus 2.3.4 一.Windows 安装 Docker二.Milvus 下载1.下载2.安装1.Windows 下安装&#xff08;指定好Docker文件目录&#xff09;2.Anolis下安装 三.数据库访问1.ATTU 客户端下载 一.Windows 安装 Docker Docker 下载 双击安装即可&#xff0c;安…

麒麟系统—— openKylin 安装 redis

麒麟系统—— openKylin 安装 redis 一、准备工作1. 确保麒麟系统 openKylin 已经安装完毕。 二、下载安装文件三、解压安装解压安装 四、配置与运行修改配置文件以配置文件运行 五、加入到服务中最终&#xff1a;介绍配置的其他参数使用 Redis是一种开源的、基于内存的高速缓存…

NodeJs环境安装与配置

最近电脑重装了系统&#xff0c;开发环境啥的都得重装&#xff0c;顺便记录下 nodeJs 的安装与配置&#xff0c;方便需要的同学查看&#xff0c;也方便自己以后查找。 安装 下载地址&#xff1a;https://nodejs.cn/download/ 根据需要选择自己环境需要的下载即可&#xff0c;…

Mysql-存储引擎-InnoDB

数据文件 下面这条SQL语句执行的时候指定了ENGINE InnoDB存储引擎为InnoDB: CREATE TABLE tb_album (id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 编号,title varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 相册名称,image varc…

81.网游逆向分析与插件开发-背包的获取-装备栏数据结构的逆向分析

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;自动化助手显示物品数据-CSDN博客 然后游戏中有弓箭&#xff0c;弓箭有数量&#xff0c;可以作为突破口&#xff0c;也可以使用物品id 获取弓的方式 获取弓箭的方式 然后搜索250 然后搜索出一个 然后…

LMC:通过大模型合作与互相评估来进行无需训练的开放集识别

论文名称&#xff1a; LMC: Large Model Collaboration with Cross-assessment for Training-Free Open-Set Object Recognition 论文链接&#xff1a; https://readpaper.com/paper/4803888389338169345 非常高兴跟大家分享我们接收于NeurIPS 2023的工作LMC&#xff1a;通…

小项目:使用MQTT上传温湿度到Onenet服务器

前言 我们之前分别编写了 DHT11、ESP8266 和 MQTT 的代码&#xff0c;现在我们将它们仨整合在一起&#xff0c;来做一个温湿度检测小项目。这个项目可以实时地将 DHT11 传感器获取到的温湿度数据上传到 OneNET 平台。通过登录 OneNET&#xff0c;我们随时随地可以查看温湿度数…

windows消息循环之手撸一个Win32窗口程序

Windows消息循环&#xff08;Windows Message Loop&#xff09; 在Windows操作系统中&#xff0c;一个程序通过不断地接收和处理消息来保持活动状态的一种机制。在Windows编程中&#xff0c;消息循环是处理用户输入、操作系统事件和其他消息的关键部分。 在Windows应用程序中…

python 基础知识点(蓝桥杯python科目个人复习计划26)

今日复习内容&#xff1a;基础算法中的前缀和 1.定义&#xff1a; 前缀和&#xff1a;对于一个长度为n的列表a&#xff0c;前缀和为&#xff1a; sum[i] a[1] ...a[i];例如&#xff1a;a [1,2,3,4,5],则它的前缀和数组sum为&#xff1a;[1,3,6,10,15]。 2.前缀和的性质 …

c语言实战之贪吃蛇

文章目录 前言效果展示游戏用到的图片游戏思路一览游戏前准备一、贪吃蛇、食物、障碍物节点坐标的结构体二、枚举游戏状态、和贪吃蛇的方向三、维护运行的结构体 游戏开始前的初始化一、学习图形库相关知识二、设置背景三、欢迎界面四、初始化贪吃蛇五、生成障碍物六、生成食物…

董仲舒是中国两千多年主流思想的总建造师

仕途不得志&#xff0c;董仲舒在家教书。董仲舒写了《春秋繁露》&#xff0c;用来解释《春秋》。 董仲舒把诸子百家的思想吸收之后&#xff0c;融入儒家&#xff0c;把儒家发扬光大。 儒家思想体系&#xff0c;孔子是总设计师&#xff0c;董仲舒是总建造师&#xff0c;朱熹是…

linux centos 查看端口是否打开与打开端口

查看端口是否打开 talnet talnet ip 端口linux查看防火墙开放情况 firewall-cmd --list-all打开端口 其中permanent表示永久生效&#xff0c;public表示作用域&#xff0c;443/tcp表示端口和类型&#xff0c;执行规则的重载 firewall-cmd --zonepublic --add-port443/tcp …

D6287F——正反转马达驱动电路,采 用 SOP8的 封 装 形 式 封 装 。驱动电流最高可达1.0A

D6287F 是 一 块 正 反 转 马 达 驱 动 电 路 &#xff0c;两 种 逻 辑 输 入 方 式 可 控 制 马 达 的 正 转 、 反 转 、 停 止 、 中 断 等 。 内 置 马 达 停 止 时 省 电 电 路 及 热 保 护 电 路 。 最 大 驱 动 电 流 达 1.0A 。 广 泛 用 于 VCRs及 音 频 设 备 等 电…

福州一酒店发生火灾 富维图像烟火识别来揭秘

最近&#xff0c;福州一家知名酒店发生了火灾事件&#xff0c;所幸及时控制&#xff0c;未造成重大伤亡。这一事件再次提醒我们&#xff0c;商业场所的火灾安全不可忽视。在这种情况下&#xff0c;北京富维图像公司推出的FIS智能图像识别系统就显得尤为重要。 FIS系统能够通过已…

开始学习第二十五天(番外)

今天分享一下写的小游戏啦 头文件game.h #include<stdio.h> #include<time.h> #include<stdlib.h> #define H 3 #define L 3 void InitBoard(char Board[H][L], int h, int l); void DisplayBoard(char Board[H][L], int h, int l); void playermove(cha…

游戏引擎在进化,下一代3D创建引擎#a16z

这是一篇发表在a16z的文章&#xff0c;文中的观点非常值得我们学习。我花了点时间对原文进行梳理&#xff0c;挑选/补充了一些信息&#xff0c;分享给大家。 a16z.com/unbundling-the-game-engine 解开游戏引擎&#xff1a;下一代3D创作引擎的崛起 作者&#xff1a;Troy Kirwin…

Windows11搭建GPU版本PyTorch环境详细过程

Anaconda安装 https://www.anaconda.com/ Anaconda: 中文大蟒蛇&#xff0c;是一个开源的Python发行版本&#xff0c;其包含了conda、Python等180多个科学包及其依赖项。从官网下载Setup&#xff1a;点击安装&#xff0c;之后勾选上可以方便在普通命令行cmd和PowerShell中使用…

计网Lesson12 - UDP客户服务器模型和UDP协议

文章目录 丢个图在这&#xff0c;实在不是很明白在讲啥&#xff0c;等学完网编的我归来狠狠拿下它

【IM】如何保证消息可用性(二)

请先阅读第一篇&#xff1a;【IM】如何保证消息可用性&#xff08;一&#xff09; 在第一篇文章中我们了解了保证消息可用性的挑战与目标&#xff0c;现在我们来对于具体的技术方案进行探讨。 1. 上行消息 消息上行过程指的是客户端发送消息给服务端 我们需要先辨析几个概念…