堆的概念实现

前言

本文将详细讲解堆。堆是一种二叉树(一般是完全二叉树)使用顺序结构的数组来存储。
tip:这里我们需要注意区分堆在不同地方的含义,这里的堆是一个数据结构,操作系统虚拟进程地址空间的堆是操作系统中管理内存的一块区域分段。

一、堆的概念及结构

1、堆的概念

如果有一个关键码的集合K,把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足堆中某个节点的值总是不大于(或不小于)其父节点的值,则称为大堆(小堆)。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

tip:

  • 堆总是一颗完全二叉树。
  • 大堆:树中所有父亲都大于或等于孩子。
  • 小堆:树中所有父亲都小于或等于孩子。
  • 注意:堆不一定有序的,因为左右孩子谁大谁小并没有限制。

2、堆的结构

在这里插入图片描述
tip:学习堆我们一定要画图,因为堆在内存中的存储结构是一个数组,但元素之间的逻辑是一颗二叉树,我们很难可以将其想象出来,所以学堆画图很重要

二、堆的实现

虽然堆分为两类,大根堆和小根堆,但是他们的结构与功能都是类似的,所以这里我们实现一个大堆为例就可以了。

我们先预览堆的结构与所需的接口函数:

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//堆——完全二叉树
//虽然堆在内存上是用一段物理地址连续的存储单元依次存储的,但是逻辑关系上是一颗完全二叉树
//堆又分为大根堆和小根堆
//大根堆:树中所有父亲都大于或等于孩子
//小根堆:树中所有父亲都小于或等于孩子
//大根堆和小根堆的实现基本相似,所以这里我们以实现一个大根堆为例typedef int HPDataType;//重命名堆中数据的类型——》优点:①一改全改;②见名知意
typedef struct Heap
{HPDataType* arr;//指向堆区申请的数组int size;//存储有效数据个数int capacity;//堆的容量空间
}HP;//堆的初始化——初始化堆的成员变量
void HeapInit(HP* php);//堆的销毁——动态申请的空间,自己要记得销毁,否则可能造成内存泄漏
void HeapDestroy(HP* php);//交换
void Swap(HPDataType* e1, HPDataType* e2);//堆的向上调整
void AdjustUp(HPDataType* arr, int child);//堆的插入
void HeapPush(HP* php, HPDataType x);//打印堆数组
void HeapPrint(HP* php);//判断堆是否为空
bool HeapIsEmpty(HP* php);//堆的向下调整
void AdjustDown(HPDataType* arr, int n, int parent);//堆的删除
void HeapPop(HP* php);//获取堆顶元素
HPDataType HeapTop(HP* php);//获取堆的有效元素个数
int HeapSize(HP* php);

1、堆的结构

堆由三个成员变量组成,分别是arr指针指向动态申请的数组、size存储堆的有效数据个数、capacity存储堆的容量,所以堆是一个复杂结构,我们将其定义为结构体。

typedef int HPDataType;//重命名堆中数据的类型——》优点:①一改全改;②见名知意
typedef struct Heap
{HPDataType* arr;//指向堆区申请的数组int size;//存储有效数据个数int capacity;//堆的容量空间
}HP;

tip:

  • 建议将堆中存储的数据的类型typedef,typedef 一可以方便我们更改堆的类型,二见名知意。

2、堆的初始化

堆的初始化模块功能:初始化堆对象的三个成员变量

//堆的初始化——初始化堆的成员变量
void HeapInit(HP* php)
{//断言指针的有效性,php不可能为空assert(php);//初始化堆的成员php->arr = (HPDataType*)malloc(sizeof(HPDataType) * 4);if (NULL == php->arr){//扩容失败perror("HeapInit::malloc");return;}php->size = 0;php->capacity = 4;
}

3、堆的销毁

堆的销毁模块功能:清理堆对象使用的资源,并销毁堆对象中动态申请的资源。

//堆的销毁——动态申请的空间,自己要记得销毁,否则可能造成内存泄漏
void HeapDestroy(HP* php)
{assert(php);//释放堆对象申请的动态空间free(php->arr);//free之后php->arr仍指向原来的空间,可能造成非法访问或多次释放,建议置为空php->arr = NULL;php->size = 0;php->capacity = 0;
}

4、堆的插入(重点)

堆的插入模块功能:先在数组尾插入数据,数据再向上调整找到自己在大堆中的位置。

问题1: 堆插入为什么只有一种?

因为堆要满足大根堆或小根堆的性质,头插和任意位置插入会改变堆原本的结构,而尾插不会改变堆原本的结构。

问题2: 已知有一个数组,它是按照大根堆的性质存储的。现在我们在数组尾插入一个数据80,仍想让数组按照大根堆存储,该怎么实现?

  1. 使用向上调整算法实现。
  2. 向上调整算法的思想在这里插入图片描述
    • 将目标节点(孩子结点)与其父节点比较。
    • 如果目标节点大于其父节点,交换两个节点的值,并继续向上比较。
    • 结束向上比较的条件:①如果目标节点小于其父节点,则停止向上比较;②最坏的情况是向上比较到根才结束。
  3. 因为其过程是不断的向上比较,所以将其叫做向上调整算法。

    tip:向上调整的前提是除了目标位置,前面数据构成大堆/小堆。

(1)交换两个数据

因为在堆中许多地方都会交换两个数据,所以我们将其模块化。

//交换——有多个模块需要交换,所以将其模块化
void Swap(HPDataType* e1, HPDataType* e2)
{HPDataType temp = *e1;*e1 = *e2;*e2 = temp;
}

tip:

  • 注意:值传递不会改变形参的改变不会改变实参。

(2)向上调整

注意:向上调整的前提是除了目标位置,前面数据构成大堆/小堆。

//堆的向上调整
void AdjustUp(HPDataType* arr, int child)
{//存储父亲的下标int parent = (child - 1) / 2;//大堆向上调整:当孩子大于父亲时,孩子与父亲交换//向上调整的结束条件://①最坏情况:向上到根才结束,即child = 0(parent<0)。但注意不能用parent < 0作为结束,因为parent = (child - 1) / 2不可能小于0.//②特殊情况:当孩子不大于父亲时结束。while (child > 0){if (arr[child] > arr[parent]){//交换Swap(&arr[child], &arr[parent]);//继续向上调整child = parent;parent = (child - 1) / 2;}else{//跳出循环break;}}
}

tip:

  • 最坏情况是到根结束,注意不能用parent < 0来判断,因为parent = (child - 1)/ 2不可能小于0。
  • 小根堆的向上调整与大根堆类似,只需将if中的arr[child] > arr[parent]改成arr[child] < arr[parent]即可。

(3)堆的插入

//堆的插入
void HeapPush(HP* php, HPDataType x)
{assert(php);//1、向堆插入数据,要先判断是否扩容if (php->size == php->capacity){//扩容//realloc可能扩容失败,所以先使用一个临时变量保存realloc的返回值HPDataType* temp = (HPDataType*)realloc(php->arr, sizeof(HPDataType) * php->capacity * 2);//判断是否扩容成功if (temp != NULL){//扩容成功,仍然使用php->arr指向申请的空间,并将temp置为空php->arr = temp;temp = NULL;//注意别忘记更新容量php->capacity *= 2;}else{perror("realloc");return;}}//2、堆的插入——先在数组尾插入数据,再向上调整//①数组尾插——不需要挪动,时间复杂度O(1)php->arr[php->size] = x;php->size++;//②向上调整AdjustUp(php->arr, php->size - 1);
}

tip:

  • 注意我们插入数据之前,要检查是否需要扩容。

5、堆的判空

堆的判空模块功能:如果堆为空,则返回真;反之,返回假。

//判断堆是否为空
bool HeapIsEmpty(HP* php)
{assert(php);//当堆中有效个数为0时,堆为空return 0 == php->size;
}

tip:

  • 判断一个变量与一个常量是否相等时,建议变量做右操作数,提高代码的健壮性。

6、堆的删除(重点)

堆的删除模块功能:删除堆顶的元素。

问题1: 堆的删除为什么只能删除堆顶元素?

因为堆的性质,堆顶元素的值最大或最小。

所以删除堆顶元素才有意义,删除掉最大的之后我们可以继续选出第二大的。

问题2: 我们直接删除堆顶?

我们不会直接删除堆顶元素,因为直接删除堆顶元素有两大问题:

  • 数组头删,需要挪动数据,时间复杂度为O(N),效率低。
  • 父子兄弟关系全乱了。在这里插入图片描述

问题3: 怎样删除堆顶?

思路

  1. 交换:把堆顶元素与最后一个元素交换
  2. 删除堆顶:数组尾删,直接size–即可
  3. 向下调整:运用向下调整算法,确保堆的结构。

问题4: 怎样向下调整?

在这里插入图片描述
思路

  1. 通过假设法选出左右孩子较大的那个孩子结点
  2. 将较大的那个孩子结点与父节点比较,如果比父节点大,则交换
  3. 继续向下比较,直到比父节点小才停止,最坏的情况是,向下比较到叶子才停止。

    tip:向下调整的前提是左右子树都是大堆/小堆。

(1)向下调整

注意:向下调整的前提是左右子树都是大堆/小堆。

//堆的向下调整
void AdjustDown(HPDataType* arr, int n, int parent)
{//假设法:定义一个变量存储较大孩子的下标,先假设左孩子大,再通过if确定假设是否成立。int child = parent * 2 + 1;//大堆向下调整:当父亲小于较大的孩子,孩子与父亲交换//向下结束的条件://①最坏情况: 向下到叶子结束,即child(parent * 2 + 1) > n。//②特殊情况:当孩子不小于父亲时结束。while (child < n){//if确定假设是否成立//注意:要先判断child + 1是否为堆有效数据if (child + 1 < n && arr[child] < arr[child + 1]){++child;}if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);//继续向下调整parent = child;child = parent * 2 + 1;}else{break;}}
}

tip:

  • 比较左右孩子的时候,需要先判断右孩子是否为堆的有效数据,当child(parent * 2 + 1) > n,没有右孩子。
  • 小根堆的向下调整与大根堆类似,只需将if中的arr[child] > arr[parent]改成arr[child] < arr[parent]即可。

(2)堆的删除

//堆的删除
void HeapPop(HP* php)
{assert(php);//删除堆不能为空assert(!HeapIsEmpty(php));//堆只有删除堆顶元素才有意义//问题:是直接删吗?//答案是:不是,直接删,有两个问题——①效率低,挪动数据时间复杂度O(N);②堆的父子兄弟关系全乱了//①交换堆顶与堆尾Swap(&php->arr[0], &php->arr[php->size - 1]);//②数组尾删php->size--;//③向下调整AdjustDown(php->arr, php->size, 0);
}

tip:

  • 在删除数据之前,需要判断堆是否为空。
  • 堆删除的思路口诀:一交换二删除三向下调整。

7、获取堆顶元素

//获取堆顶元素
HPDataType HeapTop(HP* php)
{assert(php);//断言堆不为空assert(!HeapIsEmpty(php));return php->arr[0];
}

tip:

  • 为了程序的健壮性,在获取之前断言堆不为空。
  • 获取堆顶元素,直接返回数组下标为0的元素即可

8、获取堆的有效数据个数

//获取堆的有效元素个数
int HeapSize(HP* php)
{assert(php);return php->size;
}

tip:

  • 直接返回size即可。

9、堆的打印

//打印堆数组
void HeapPrint(HP* php)
{assert(php);int i = 0;for (i = 0; i < php->size; ++i){printf("%d ", php->arr[i]);}printf("\n");
}

tip:

  • 堆在内存中是连续存储的其本质就是数组,所以使用for循环就打印了。

三、总代码

1、接口声明模块

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>//堆——完全二叉树
//虽然堆在内存上是用一段物理地址连续的存储单元依次存储的,但是逻辑关系上是一颗完全二叉树
//堆又分为大根堆和小根堆
//大根堆:树中所有父亲都大于或等于孩子
//小根堆:树中所有父亲都小于或等于孩子
//大根堆和小根堆的实现基本相似,所以这里我们以实现一个大根堆为例typedef int HPDataType;//重命名堆中数据的类型——》优点:①一改全改;②见名知意
typedef struct Heap
{HPDataType* arr;//指向堆区申请的数组int size;//存储有效数据个数int capacity;//堆的容量空间
}HP;//堆的初始化——初始化堆的成员变量
void HeapInit(HP* php);//堆的销毁——动态申请的空间,自己要记得销毁,否则可能造成内存泄漏
void HeapDestroy(HP* php);//交换
void Swap(HPDataType* e1, HPDataType* e2);//堆的向上调整
void AdjustUp(HPDataType* arr, int child);//堆的插入
void HeapPush(HP* php, HPDataType x);//打印堆数组
void HeapPrint(HP* php);//判断堆是否为空
bool HeapIsEmpty(HP* php);//堆的向下调整
void AdjustDown(HPDataType* arr, int n, int parent);//堆的删除
void HeapPop(HP* php);//获取堆顶元素
HPDataType HeapTop(HP* php);//获取堆的有效元素个数
int HeapSize(HP* php);

2、接口实现模块

#include"Heap.h"//堆的初始化——初始化堆的成员变量
void HeapInit(HP* php)
{//断言指针的有效性,php不可能为空assert(php);//初始化堆的成员php->arr = (HPDataType*)malloc(sizeof(HPDataType) * 4);if (NULL == php->arr){//扩容失败perror("HeapInit::malloc");return;}php->size = 0;php->capacity = 4;
}//堆的销毁——动态申请的空间,自己要记得销毁,否则可能造成内存泄漏
void HeapDestroy(HP* php)
{assert(php);//释放堆对象申请的动态空间free(php->arr);//free之后php->arr仍指向原来的空间,可能造成非法访问或多次释放,建议置为空php->arr = NULL;php->size = 0;php->capacity = 0;
}//交换——有多个模块需要交换,所以将其模块化
void Swap(HPDataType* e1, HPDataType* e2)
{HPDataType temp = *e1;*e1 = *e2;*e2 = temp;
}//堆的向上调整
void AdjustUp(HPDataType* arr, int child)
{//存储父亲的下标int parent = (child - 1) / 2;//大堆向上调整:当孩子大于父亲时,孩子与父亲交换//向上调整的结束条件://①最坏情况:向上到根才结束,即child = 0(parent<0)。但注意不能用parent < 0作为结束,因为parent = (child - 1) / 2不可能小于0.//②特殊情况:当孩子不大于父亲时结束。while (child > 0){if (arr[child] > arr[parent]){//交换Swap(&arr[child], &arr[parent]);//继续向上调整child = parent;parent = (child - 1) / 2;}else{//跳出循环break;}}
}//堆的插入
void HeapPush(HP* php, HPDataType x)
{assert(php);//1、向堆插入数据,要先判断是否扩容if (php->size == php->capacity){//扩容//realloc可能扩容失败,所以先使用一个临时变量保存realloc的返回值HPDataType* temp = (HPDataType*)realloc(php->arr, sizeof(HPDataType) * php->capacity * 2);//判断是否扩容成功if (temp != NULL){//扩容成功,仍然使用php->arr指向申请的空间,并将temp置为空php->arr = temp;temp = NULL;//注意别忘记更新容量php->capacity *= 2;}else{perror("realloc");return;}}//2、堆的插入——先在数组尾插入数据,再向上调整//①数组尾插——不需要挪动,时间复杂度O(1)php->arr[php->size] = x;php->size++;//②向上调整AdjustUp(php->arr, php->size - 1);
}//打印堆数组
void HeapPrint(HP* php)
{assert(php);int i = 0;for (i = 0; i < php->size; ++i){printf("%d ", php->arr[i]);}printf("\n");
}//判断堆是否为空
bool HeapIsEmpty(HP* php)
{assert(php);//当堆中有效个数为0时,堆为空return 0 == php->size;
}//堆的向下调整
void AdjustDown(HPDataType* arr, int n, int parent)
{//假设法:定义一个变量存储较大孩子的下标,先假设左孩子大,再通过if确定假设是否成立。int child = parent * 2 + 1;//大堆向下调整:当父亲小于较大的孩子,孩子与父亲交换//向下结束的条件://①最坏情况: 向下到叶子结束,即child(parent * 2 + 1) > n。//②特殊情况:当孩子不小于父亲时结束。while (child < n){//if确定假设是否成立//注意:要先判断child + 1是否为堆有效数据if (child + 1 < n && arr[child] < arr[child + 1]){++child;}if (arr[child] > arr[parent]){Swap(&arr[child], &arr[parent]);//继续向下调整parent = child;child = parent * 2 + 1;}else{break;}}
}//堆的删除
void HeapPop(HP* php)
{assert(php);//删除堆不能为空assert(!HeapIsEmpty(php));//堆只有删除堆顶元素才有意义//问题:是直接删吗?//答案是:不是,直接删,有两个问题——①效率低,挪动数据时间复杂度O(N);②堆的父子兄弟关系全乱了//①交换堆顶与堆尾Swap(&php->arr[0], &php->arr[php->size - 1]);//②数组尾删php->size--;//③向下调整AdjustDown(php->arr, php->size, 0);
}//获取堆顶元素
HPDataType HeapTop(HP* php)
{assert(php);//断言堆不为空assert(!HeapIsEmpty(php));return php->arr[0];
}//获取堆的有效元素个数
int HeapSize(HP* php)
{assert(php);return php->size;
}

3、功能测试模块

#include"Heap.h"int main()
{//定义堆变量HP hp;//初始化HeapInit(&hp);//插入数据HeapPush(&hp, 6);HeapPush(&hp, 16);HeapPush(&hp, 36);HeapPush(&hp, 56);HeapPush(&hp, -1);HeapPush(&hp, 5);HeapPush(&hp, -16);HeapPush(&hp, 35);HeapPush(&hp, 19);HeapPush(&hp, 9);HeapPush(&hp, 6);HeapPush(&hp, 18);HeapPrint(&hp);//找出前k个大的数int k = 0;scanf("%d", &k);while (!HeapIsEmpty(&hp) && k--){printf("%d ", HeapTop(&hp));//删除HeapPop(&hp);}printf("\n");HeapDestroy(&hp);return 0;
}

运行结果:

在这里插入图片描述

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

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

相关文章

公众号天气推送源码,附带教学,自动版本推送带各种模板

公众号天气推送系统介绍 主要功能特点&#xff1a; 实时天气查询&#xff1a;用户可以通过公众号随时查询当前位置的实时天气状况&#xff0c;包括温度、湿度、风速、天气状况等详细信息。定时推送服务&#xff1a;系统支持自定义时间段的天气推送&#xff0c;确保用户在出门…

【项目问题解决】java. net.SocketException: Connection reset

目录 【项目问题解决】java. net.SocketException: Connection reset 1.问题描述2.问题原因3.解决思路4.解决方案5.总结6.参考 文章所属专区 项目问题解决 1.问题描述 通过JMeter 压测接口&#xff0c;无并发&#xff0c;无间歇时间跑接口10000次报错&#xff0c;后续改成建个…

JavaScript指针事件

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ​ ​ ✨ 前言 随着移动设备的普及,触屏交互正在快速增长。指针事件提供了支持触控和…

问题:胚珠裸露于心皮上,无真正的果实的植物为() #经验分享#媒体

问题&#xff1a;胚珠裸露于心皮上&#xff0c;无真正的果实的植物为&#xff08;&#xff09; A.双子叶植物 B.被子植物 C.单子叶植物 D.裸子植物 参考答案如图所示

探索设计模式的魅力:代理模式揭秘-软件世界的“幕后黑手”

设计模式专栏&#xff1a;http://t.csdnimg.cn/U54zu 目录 引言 一、魔法世界 1.1 定义与核心思想 1.2 静态代理 1.3 动态代理 1.4 虚拟代理 1.5 代理模式结构图 1.6 实例展示如何工作&#xff08;场景案例&#xff09; 不使用模式实现 有何问题 使用模式重构示例 二、…

基于 Python 的漏洞扫描系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

vue-内置组件-Suspense

Suspense (实验性功能) <Suspense> 是一项实验性功能。它不一定会最终成为稳定功能&#xff0c;并且在稳定之前相关 API 也可能会发生变化。 <Suspense> 是一个内置组件&#xff0c;用来在组件树中协调对异步依赖的处理。它让我们可以在组件树上层等待下层的多个嵌…

【十三】【C++】vector简单实现

代码实现 /*vector类简单实现*/ #if 1 #define _CRT_SECURE_NO_WARNINGS#include <iostream> using namespace std; #include <vector> #include <algorithm> #include <crtdbg.h> #include <assert.h> #include <string.h>namespace MyVe…

Python中HTTP隧道的基本原理与实现

HTTP隧道是一种允许客户端和服务器之间通过中间代理进行通信的技术。这种隧道技术允许代理服务器转发客户端和服务器之间的所有HTTP请求和响应&#xff0c;而不需要对请求或响应内容进行任何处理或解析。Python提供了强大的网络编程能力&#xff0c;可以使用标准库中的socket和…

【大厂AI课学习笔记】1.5 AI技术领域(6)目标检测

目标检测是CV中的重要场景。 在图像中定位感兴趣的目标&#xff0c;准确判断每个目标的类别&#xff0c;并给出每个目标的边界框。 上图是目标检测的典型应用案例。 目标检测的难点是小目标的高精度检测。 目前主要的应用领域是机器人导航、自动驾驶、智能视频监督、工业检测…

如何在Sprint中管理UI测试?

作为iOS团队&#xff0c;我们编写3种类型的UI测试。如果你问这些是什么&#xff1b;快照、冒烟和回归测试。那么这些测试到底是什么&#xff1f;让我们稍微谈谈这些。 快照测试快照测试是检查UI中的某些内容是否损坏的测试。 首先&#xff0c;它将所需的视图图像保存在某处&am…

MyBatis 实现动态 SQL

MyBatis 中的动态 SQL 就是SQL语句可以根据不同的情况情况来拼接不同的sql。 本文会介绍 xml 和 注解 两种方式的动态SQL实现方式。 XML的实现方式 先创建一个数据表&#xff0c;SQL代码如下&#xff1a; DROP TABLE IF EXISTS userinfo; CREATE TABLE userinfo (id int(1…

Dynamo批量处理多个Revit文件?

Hello大家好&#xff01;我是九哥~ 最近很多小伙伴都在咨询Dynamo如何批量处理多个Revit文件&#xff0c;之前写过一篇《Dynamo批量修改多文件项目基点参数》&#xff0c;利用的是后台打开Revit的方式&#xff0c;可以实现一些批量操作的功能。 但是这个方法&#xff0c;对于一…

横扫Spark之 - 9个常见的行动算子

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 文章目录 1. collect()2. count()3. first()4. take()5. takeOrdered()6. countByKey()7. saveAS...()8. foreach()9. foreachPartition() *** 1. collect() 收集RDD每个分区的数据以数组封装之后发…

Bert下载和使用(以bert-base-uncased为例)

Bert官方github地址&#xff1a;https://github.com/google-research/bert?tabreadme-ov-file 【hugging face无法加载预训练模型】OSError&#xff1a;Can‘t load config for ‘./bert-base-uncased‘. If you‘re trying 如何下载和在本地使用Bert预训练模型 以bert-base-u…

“金龙送礼,昂首贺春”—— Anzo Capital给您送五粮液、茅台啦!

“迎龙年&#xff0c;贺新春”—— 值此龙年将至之际&#xff0c;为答谢新老客户一直以来对Anzo Capital昂首资本的信赖和支持&#xff0c;Anzo Capital昂首资本2月入金送礼活动重磅升级&#xff0c;除了京东卡、天猫超市卡、奔富红酒、SKG健康产品、白酒礼盒以外&#xff0c…

免费软件推荐-开源免费批量离线图文识别(OCR)

近期要批量处理图片转电子化&#xff0c;为了解决这个世纪难题&#xff0c;试了很多软件&#xff08;华为手机自带OCR识别、 PandaOCR、天若OCR、Free OCR&#xff09;等软件&#xff0c;还是选择了这一款&#xff0c;方便简单 一、什么是OCR? 光学字符识别&#xff08;Opt…

部署一个自己的P站

效果 安装 1.拉取代码 cd /opt git clone https://gitee.com/WangZhe168_admin/logoly.git 2.安装依赖 cd logoly npm install 3.启动 npm run serve 愉快地使用吧

ElasticSearch之倒排索引

写在前面 本文看下es的倒排索引相关内容。 1&#xff1a;正排索引和倒排索引 正排索引就是通过文档id找文档内容&#xff0c;而倒排索引就是通过文档内容找文档id&#xff0c;如下图&#xff1a; 2&#xff1a;倒排索引原理 假定我们有如下的数据&#xff1a; 为了建立倒…

使用 devc++ 开发 easyx 实现 Direct2D 交互

代码为 codebus 另一先生的 文案 EasyX 的三种绘图抗锯齿方法 - CodeBus 这里移植到 devc 移植操作如下&#xff1a; 调用dev 的链接库方式&#xff1a; project -> project option -> 如图所示 稍作修改的代码。 #include <graphics.h> #include <d2d1.…