数据结构-堆的实现及应用(堆排序和TOP-K问题)

数据结构-堆的实现及应用[堆排序和TOP-K问题]

  • 一.堆的基本知识点
    • 1.知识点
  • 二.堆的实现
    • 1.堆的结构
    • 2.向上调整算法与堆的插入
    • 2.向下调整算法与堆的删除
  • 三.整体代码
  • 四.利用回调函数避免对向上和向下调整算法的修改
    • 1.向上调整算法的修改
    • 2.向下调整算法的修改
    • 3.插入元素和删除元素函数的修改
  • 五.建堆
    • 1.自顶向下的建堆方式(利用向上调整算法)
    • 2.自底向上的建堆方式(利用向下调整算法)
    • 3.两种方法建堆的时间复杂度
      • 1.向下调整算法建堆的时间复杂度
      • 2.向上调整算法建堆的时间复杂度
  • 六.堆排序
    • 1.算法思想
    • 2.代码实现
    • 3.时间复杂度
    • 4.稳定性
  • 七.TOP-K问题
  • 八.一道与TOP-K相关的leetcode题目
  • 九.测试TOP-K对于海量数据的处理
  • 十.总结

一.堆的基本知识点

1.知识点

1.堆的知识点:

堆的知识点
堆的逻辑结构是一颗完全二叉树
堆的物理结构是一个数组
也就是说,给我们是一个数组,可是我们要把它想象成一个完全二叉树来做
通过下标父子结点关系
leftchild = parent * 2 + 1;
rightchild = parent * 2 + 2;
parent = (child - 1) / 2;(child可以是左孩子,也可以是右孩子)

下面我们通过一张图片来更加深刻地理解堆
在这里插入图片描述

堆的两个特性1.结构性:用数组表示的完全二叉树
2.有序性:任意节点的关键字是其子树所有结点的最大值
3.堆的两种分类
最大堆(MaxHeap):也称为大顶堆(最大值)
最小堆(MinHeap):也称为小顶堆(最小值)
3.大堆:要求树中所有的父亲都大于等于孩子
小堆:要求所有的父亲都小于等于孩子
堆只有两种:大堆,小堆,其余的都不是堆,注意有些选择题常考堆的判别
大堆:堆顶数据是最大的
小堆:堆顶数据是最小的

二.堆的实现

1.堆的结构

上面我们说过,堆的物理结构是一个数组,逻辑结构是一个完全二叉树,所以堆的实际结构类似于顺序表,只不过我们的处理方式类似于二叉树

那么我们就可以用顺序表那样的结构来表示堆了
于是我们可以写出这样的代码

typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;
//堆的初始化
void HeapInit(HP* php);
//堆的销毁
void HeapDestroy(HP* php);
//堆的打印
void HeapPrint(HP* php);
//取堆顶数据
HPDataType HeapTop(HP* php);
//判断是否为空
bool HeapEmpty(HP* php);
//返回堆的元素大小
int HeapSize(HP* php);
void HeapInit(HP* php)
{assert(php);//这里我们将容量初始化为0,当然,初始化为别的一些数值也可以,这里没有强制要求php->a = NULL;php->size = php->capacity = 0;
}void HeapDestroy(HP* php)
{assert(php);free(php->a);php->capacity = php->size = 0;php->a = NULL;
}void HeapPrint(HP* php)
{int i = 0;for (i = 0; i < php->size; i++){printf("%d ", php->a[i]);}printf("\n");
}HPDataType HeapTop(HP* php)
{assert(php);assert(php->size > 0);return php->a[0];
}bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}int HeapSize(HP* php)
{assert(php);return php->size;
}

剩下的一些常见的接口:

//堆的插入
void HeapPush(HP* php, HPDataType x);
//堆的删除
void HeapPop(HP* php);

在这里,我们要先说明一下:

1.在堆中插入一个数据后,我们不能改变堆的特性,
即:

原来是小堆,插入数据后还是小堆
原来是大堆,插入数据后还是大堆

2.我们的删除操作所要删除的值是堆顶元素.
即数组的第一个元素,
删除操作依然不能改变堆的特性

要想实现堆的插入
首先我们需要先学习一个算法:向上调整算法

2.向上调整算法与堆的插入

假设我们现在有一个小堆(所有的父亲都小于他们所对应的孩子)
我们要插入一个元素
在这里插入图片描述
向上调整算法整体思路:
child不断向上跟父亲比,如果比父亲小,跟父亲交换,向上迭代
当该节点调整到父亲小于该节点时,或者该节点调整到数组的首元素位置时调整结束

所以我们可以写出这样的代码

//向上调整算法
//[0,child]区间内向上调整
void AdjustUp(HPDataType* a,int child)
{int parent = (child - 1) / 2;//终止条件:孩子等于0,大于0就继续调整//不要拿父亲作为条件,父亲和孩子都等于0的时候,parent = (0-1)/2还是0,死循环了//while(parent>=0)while (child > 0){//建小堆if(a[child]<a[parent])//建大堆if(a[child]>a[parent])if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

其中,向上调整算法的时间复杂度为O(log2(N)),
最多调整高度次,因为是完全二叉树,所以高度约等于O(log2(N))
实现了向上调整算法之后,我们就可以完成插入了

void HeapPush(HP* php, HPDataType x)
{assert(php);//扩容if (php->size == php->capacity){int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;//进行扩容HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);if (NULL == tmp){perror("malloc fail");exit(-1);}php->a = tmp;php->capacity = newCapacity;}//插入php->a[php->size] = x;php->size++;//向上调整AdjustUp(php->a, php->size - 1);
}

2.向下调整算法与堆的删除

假设我们现在有一个小堆
在这里插入图片描述
向下调整算法整体思路:
前提:根节点的左子树和右子树都是小堆
child为左右孩子中较小者
如果父亲大于child,交换父亲和child,父亲和孩子向上迭代

于是我们可以写出这样的代码

//向下调整算法(小堆)
//[root,n)区间内向下调整
void AdjustDown(HPDataType* a, int root,int n)
{int parent = root;int child = 2 * parent + 1;while (child < n){//建小堆:if(...&& a[child]>a[child+1])//建大堆:if(...&& a[child]<a[child+1])if (child + 1 < n && a[child] > a[child + 1]){child++;}//建小堆:if(a[parent]>a[child])//建大堆:if(a[parent]<a[child])if (a[parent] > a[child]){Swap(&a[child], &a[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}

实现了向下调整算法的代码后,我们可以写出删除的代码
其中,向下调整算法的时间复杂度也为O(log2(N)),
最多调整高度次,因为是完全二叉树,所以高度约等于O(log2(N))

//删除堆顶元素
void HeapPop(HP* php)
{assert(php);assert(php->size > 0);Swap(&(php->a[0]), &(php->a[php->size - 1]));php->size--;AdjustDown(php->a, 0, php->size);
}

三.整体代码

Heap.h

#pragma once
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <stdbool.h>typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size;int capacity;
}HP;
//初始化堆
void HeapInit(HP* php);
//销毁堆
void HeapDestory(HP* php);
//打印堆
void HeapPrint(HP* php);
//插入X继续保持堆形态
void HeapPush(HP* php, HPDataType x);
//删除堆顶元素
void HeapPop(HP* php);
//判断是否为空
bool HeapEmpty(HP* php);
//返回堆顶元素
HPDataType HeapTop(HP* php);
//返回堆的元素大小
int HeapSize(HP* php);

Heap.c

#include "Heap.h"
//初始化堆
void HeapInit(HP* php)
{php->a = NULL;php->capacity = php->size = 0;
}
//销毁堆
void HeapDestory(HP* php)
{free(php);php->capacity = php->size = 0;
}
//打印堆
void HeapPrint(HP* php)
{int i = 0;for (i = 0; i < php->size; i++){printf("%d ", php->a[i]);}printf("\n");
}void Swap(HPDataType* h1, HPDataType* h2)
{HPDataType tmp = *h1;*h1 = *h2;*h2 = tmp;
}//向上调整算法
//区间范围:[0,child]
void AdjustUp(HPDataType* a,int child)
{int parent = (child - 1) / 2;//终止条件:孩子等于0,大于0就继续调整//不要拿父亲作为条件,父亲和孩子都等于0的时候,parent = (0-1)/2还是0,死循环了//while(parent>=0)while (child > 0){if (a[child] < a[parent]){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}
//插入X继续保持堆形态(小堆:父亲小于孩子)void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->size == php->capacity){int newCapacity = php->capacity == 0 ? 4 : php->capacity * 2;//进行扩容HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newCapacity);if (NULL == tmp){perror("malloc fail");exit(-1);}php->a = tmp;php->capacity = newCapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1);
}//向下调整算法(小堆)
//区间范围:[root,n)
void AdjustDown(HPDataType* a, int root,int n)
{int parent = root;int child = 2 * parent + 1;while (child < n){if (child + 1 < n && a[child] > a[child + 1]){child++;}if (a[parent] > a[child]){Swap(&a[child], &a[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}//删除堆顶元素
void HeapPop(HP* php)
{assert(php);assert(php->size > 0);Swap(&(php->a[0]), &(php->a[php->size - 1]));php->size--;AdjustDown(php->a, 0, php->size);
}
//判断是否为空
bool HeapEmpty(HP* php)
{return php->size == 0;
}
//返回堆顶元素HPDataType HeapTop(HP* php)
{assert(php);assert(!HeapEmpty(php));return php->a[0];
}//返回堆的元素大小
int HeapSize(HP* php)
{assert(php);return php->size;
}

四.利用回调函数避免对向上和向下调整算法的修改

从上面的讲解中,对于向上调整算法和向下调整算法来说,如果想要实现从小堆到大堆的转换,还需要改一下代码,(尽管只需要改一下比较符号即可),

但是那样的话我们就需要再去实现针对于建大堆的向上调整算法和向下调整算法,
而且还需要根据不同需要去调用不同函数,过于麻烦

所以有什么方法可以避免这种修改吗?
在C语言中,我们可以利用回调函数来完成,对于回调函数来说,大家可以看我的这篇博客
征服C语言指针系列(3)
里面有详细的讲解

1.向上调整算法的修改

void AdjustUp(HPDataType* a, int child, int(*cmp_up)(HPDataType* p1, HPDataType* p2))
{int parent = (child - 1) / 2;while (child > 0){//小堆:if(a[parent]>a[child])//也就是说cmp_up(...,...)这个函数返回值为正数,则建的是小堆//否则,建的是大堆if (cmp_up(&a[parent], &a[child]) > 0){Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}else{break;}}
}

2.向下调整算法的修改

void AdjustDown(HPDataType* a, int root,int n, int(*cmp_down)(HPDataType* p1, HPDataType* p2))
{int parent = root;int child = 2 * parent + 1;while (child < n){//小堆:if(a[child]>a[child+1])//也就是说cmp_down(...,...)这个函数返回值为正数,则建的是小堆//否则,建的是大堆if (child + 1 < n && cmp_down(&a[child], &a[child + 1]) > 0){child++;}//小堆:if(a[parent]>a[child])//也就是说cmp_down(...,...)这个函数返回值为正数,则建的是小堆//否则,建的是大堆if (cmp_down(&a[parent], &a[child]) > 0){Swap(&a[child], &a[parent]);parent = child;child = 2 * parent + 1;}else{break;}}
}
函数调用者自行实现两个函数
int cmp_down(HPDataType* p1, HPDataType* p2);int cmp_up(const HPDataType* p1, const HPDataType* p2);
//使用函数指针来做一个回调函数
cmp_up:向上调整
cmp_down:向下调整
//建小堆(父亲小于孩子)
int cmp_up(const HPDataType* p1, const HPDataType* p2)
{return *p1 - *p2;
}//建大堆
int cmp_up(const HPDataType* p1, const HPDataType* p2)
{return *p2 - *p1;
}//建小堆:父亲小于孩子int cmp_down(HPDataType* p1, HPDataType* p2)
{return *p1 - *p2;
}//建大堆
int cmp_down(HPDataType* p1, HPDataType* p2)
{return *p2 - *p1;
}

3.插入元素和删除元素函数的修改

void HeapPush(HP* php, HPDataType x)
{assert(php);if (php->capacity == php->size){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");exit(-1);}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;php->size++;AdjustUp(php->a, php->size - 1, cmp_up);
}
void HeapPop(HP* php)
{assert(php);assert(php->size > 0);Swap(&(php->a[0]), &(php->a[php->size - 1]));php->size--;AdjustDown(php->a, 0, php->size, cmp_down);
}

经过了上面的修改,我们就可以在不改变代码的情况下,实现大堆或者小堆了

五.建堆

上面的代码可以让我们从无到有建立堆
但是如果我们要把一个数组改造成堆,而且不能浪费其他空间,只能在原数组上改造,那该怎么办呢?
这里我们需要建堆
这里以建小堆为例

1.自顶向下的建堆方式(利用向上调整算法)

根据上文可知进行向上调整算法后,数组中[0,child]区间就变为小堆了,所以我们可以用一个for循环来扩展这个区间让这个区间从[0,1]一直扩到[0,n-1]
于是我们可以写出如下代码

	for (int i = 1; i < n; i++){AdjustUp(arr, i, cmp_up);}

下面给大家画张图看一看:
在这里插入图片描述

2.自底向上的建堆方式(利用向下调整算法)

既然向上调整算法可以建堆,那么向下调整算法呢?
答案是:也可以建堆
我们可以从最后一个非叶子节点往上建堆,先让下面变成小堆,再让上面变成小堆
那么我们该如何求出最后一个非叶子节点呢?

根据堆的特性,我们得出过如下结论:
parent=(child-1)/2;
parent自然是非叶子节点,那么我们只需要求出最后一个parent不就行了吗?
我们又知道最后一个child的下标一定是n-1
所以得出最后一个parent的下标是(n-1-1)/2;

根据上文我们得出向下调整算法可以使得
数组中区间[root,n)范围内变为小堆
那么我们就可以逐步扩大这个范围,让这个范围从[(n-1-1)/2,n)一直扩大到[0,n)

所以我们可以写出如下代码

	for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, i, n, cmp_down);}

下面给大家画张图看一看:
在这里插入图片描述

3.两种方法建堆的时间复杂度

//向下调整算法for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, i, n, cmp_down);}
//向上调整算法for (int i = 1; i < n; i++){AdjustUp(arr, i, cmp_up);}

可能大家看到这两个代码后的反应是:
因为向上和向下调整算法的时间复杂度都是log(2)N
所以这两种建堆的时间复杂度不都是O(Nlog2(N))吗?
答案是:并不是这样,
向下调整算法建堆的时间复杂度:O(N)
向上调整算法建堆的时间复杂度:O(N
log(2)N)
下面我们来用数学公式证明一下(这里需要用到错位相减法)

1.向下调整算法建堆的时间复杂度

在这里插入图片描述

2.向上调整算法建堆的时间复杂度

在这里插入图片描述
前面已经介绍了如何把一个普通的数组改造成堆
那么接下来我们来看堆的两大重要应用:
堆排序和TOP-K问题

六.堆排序

1.算法思想

1.那么排升序我们要建什么堆呢?
答案是:大堆,这个答案确实挺出人意料的,但是为什么呢?
下面我们来详细解释一下这个原因
1.建小堆为什么不可以(不建议):
2.建大堆为什么可以(建议):

因为:
堆排序是属于选择排序的一种.
如果是建小堆,最小数在堆顶,已经被选出来了,
那么在剩下的数中再去选数,但是剩下的树结构都乱了,需要重新建堆才能选出下一个数,
建堆的时间复杂度是O(N),我们在讲解堆排序的最后会给大家证明这个建堆的时间复杂度.
那么这样不是不可以,但是堆排序就没有效率优势了并且建堆选数还不如直接遍历选数

其次,如何选次小的数呢?
第二个数去做根了,剩下的树关系全乱了,再重新建堆,
建堆的时间复杂度:O(N),而建堆选数排序,时间复杂度:O(N^2)
并且建堆选数还不如直接遍历选数

下面我给大家画图来演示一下:
在这里插入图片描述

2.建大堆:
步骤:
1.第一个和最后一个交换,然后把交换后的那个较大的数(即位于数组末尾的那个数)不看做堆里面
2.前n-1和数进行向下调整算法,选出次大的数放到根节点,再跟倒数第二个位置交换

2.代码实现

代码如下:

void HeapSort(int* a,int n)
{int i = 0;//这里用向下调整算法来建堆,因为时间复杂度只有O(N)for (i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(a, n, i);}int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustUp(a, end, 0);--end;}
}

下面给大家画图演示一下,能帮助大家有更好的理解

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.时间复杂度

void HeapSort(int* a,int n)
{int i = 0;for (i = (n - 1 - 1) / 2; i >= 0; i--){AdjustUp(a, n, i);}int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustUp(a, end, 0);--end;}
}

向下调整算法最坏的情况:进行高度次,即log(2)N次,所以while循环最坏进行了(n-1)log(2)N次,而for循环的建堆的时间复杂度为O(N)
所以整体的时间复杂度为O(N
log(2)N)
整个效率相对来说是很高的

4.稳定性

堆排序是不稳定的
因为建堆的时候有可能发生以下的类似情况
在这里插入图片描述

七.TOP-K问题

可能很多小伙伴有这么一个疑问:什么是TOP-K问题啊?
TOP-K问题:
就是求数据集合中前K个最大或者最小的元素,一般情况下数据量都比较大
比如:
全国企业500强,专业前10名,全国高考前100名等等…
那么我们如何利用堆来解决TOP-K问题呢?

假设有10亿个数据,内存存不下,数据在文件中
找出最大的前K个,  K==1001.读取文件的前100个数据,在内存数组中建立一个小堆
2.再依次读取剩下的数据,跟堆顶数据比较,大于堆顶,就替换它进堆,向下调整
3.所有数据读取完毕,堆里面的数据就是最大的前100

那么我们要建小堆还是大堆呢?
答案是:找最大的前K个就建小堆
反之,建大堆

为什么不建议建大堆:大堆只能找出最大的一个数,
而且最大的数会挡在根节点的位置,后面的数完全无法进堆,那就完蛋了小堆的优势
而小堆的话,只有第K个大的数才会挡在根节点的位置,而更大的数会往堆的下面跑
时间复杂度O(N*logK),而且K相对于N来说可以忽略不计,所以可以认为是O(N)
时间复杂度:O(K),K:堆的大小,而K相对于N来说可以忽略不计,所以可以认为是O(1)

下面我们结合一道leetcode题目来实现一下这个代码

八.一道与TOP-K相关的leetcode题目

最小K个数

void Swap(int*p1,int*p2)
{int tmp =*p1;*p1 = *p2;*p2 = tmp;
}
//前k个数建大堆
//向下调整算法void AdjustDown(int*a,int n,int parent){int minchild = parent*2+1;while(minchild<n){if(minchild+1<n&&a[minchild+1]>a[minchild]){minchild++;}if(a[minchild]>a[parent]){Swap(&a[minchild],&a[parent]);parent = minchild;minchild = parent*2+1;}else{break;}}}
int* smallestK(int* arr, int arrSize, int k, int* returnSize){if(k==0){*returnSize=0;return NULL;}int* ret=(int*)malloc(sizeof(int)*k);for(int i=0;i<k;i++){ret[i]=arr[i];}//前k个数建大堆for(int i=(k-1-1)/2;i>=0;i--){AdjustDown(ret,k,i);}for(int i=k;i<arrSize;i++){if(ret[0]>arr[i]){ret[0]=arr[i];AdjustDown(ret,k,0);}}*returnSize=k;return ret;
}

在这里插入图片描述

九.测试TOP-K对于海量数据的处理

下面我们来测试一下TOP-K对于大数据的处理功能

//创建一个文件,并且随机生成一些数字
void CreateDataFile(const char* filename, int N)
{FILE* Fin = fopen(filename, "w");if (Fin == NULL){perror("fopen fail");exit(-1);}srand(time(0));for (int i = 0; i < N; i++){fprintf(Fin, "%d ", rand() % 10000);}
}
void PrintTopK(const char* filename, int k)
{assert(filename);FILE* fout = fopen(filename, "r");if (fout == NULL){perror("fopen fail");return;}int* minheap = (int*)malloc(sizeof(int) * k);if (minheap == NULL){perror("malloc fail");return;}//读前k个数for (int i = 0; i < k; i++){//空格和换行默认是多个值之间的间隔fscanf(fout, "%d", &minheap[i]);}//建k个数的堆for (int j = (k - 1 - 1) / 2; j >= 0; j--){AdjustDown(minheap, j, k, cmp_down);}//读取后N-K个int x = 0;while(fscanf(fout,"%d",&x)!=EOF){if (x > minheap[0]){minheap[0] = x;AdjustDown(minheap, 0, k, cmp_down);}}for (int i = 0; i < k; i++){printf("%d ", minheap[i]);}printf("\n");free(minheap);fclose(fout);
}
int main()
{//CreateDataFile("data.txt", 1000000);//找前10个最大的数PrintTopK("data.txt", 10);return 0;
}

在这里插入图片描述
我们已经提前修改了文件当中的值,
现在文件当中的最大的前10个值是1000000到1000009
运行后的结果:
在这里插入图片描述
可见堆针对于TOP-K的强大之处

十.总结

本文介绍了
1.堆的知识点
2.用C语言实现了堆
3.并且用函数指针实现了回调函数对向上和向下调整算法的代码进行了优化
4.实现了建堆
5.用数学公式推导出了两种建堆方法的时间复杂度
6.实现了堆排序
7.分析了堆排序的时间复杂度和稳定性
8.介绍了TOP-K的代码实现并解决了一道leetcode题目
9.测试了TOP-K对于大数据的处理功能

以上就是<<数据结构-堆的实现及应用>>的讲解,希望能对大家有所帮助,谢谢大家!

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

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

相关文章

操作系统——复习笔记

主要是按照王道考研408的ppt进行复习。 概述 操作系统概览 操作系统&#xff08;Operating System&#xff0c; OS&#xff09;是指控制和管理整个计算机系统的硬件和软件资源&#xff0c;并合理地组织调度计算机的工作和资源的分配&#xff1b;以提供给用户和其他软件方便的…

Unity Shader 溶解效果

一、效果图 二、原理分析 实现原理就是在片元着色器中&#xff0c;对像素点进行丢弃不显示。借助美术做的噪点图(利用噪点图中rgb中r值来做计算)。比如噪点图r值从0-1。我们从小到大让r值逐渐丢弃&#xff0c;比如刚开始r < 0.1丢弃&#xff0c;然后t < 0.2丢弃...知道t…

OpenCV(二十二):均值滤波、方框滤波和高斯滤波

目录 1.均值滤波 2.方框滤波 3.高斯滤波 1.均值滤波 OpenCV中的均值滤波&#xff08;Mean Filter&#xff09;是一种简单的滤波技术&#xff0c;用于平滑图像并减少噪声。它的原理非常简单&#xff1a;对于每个像素&#xff0c;将其与其周围邻域内像素的平均值作为新的像素值…

数据库基础——数据库、数据表和SQL语句

数据库、数据表和SQL语句 数据库、数据表和SQL语句是什么&#xff1f;数据库安装数据库登录及退出创建、查看数据库及修改名字查看及修改数据库编码删除数据库使用或查看当前正在使用的数据库创建、查看数据表及修改名字查看及修改数据表编码查看及修改数据表结构增加约束删除约…

Postman接口测试流程

一、工具安装 ● 安装Postman有中文版和英文版&#xff0c;可以选择自己喜欢的版本即可。安装时重新选择一下安装路径&#xff08;也可以默认路径&#xff09;&#xff0c;一直下一步安装完成即可。&#xff08;本文档采用英文版本&#xff09;安装文件网盘路径链接&#xff1…

【分享】golang windows 运行报错 undefined: syscall.SIGUSR1

在跟着煎鱼大佬学习 Golang-gin的时候&#xff0c;"在优雅的重启服务篇" ,为了gin服务的热更新&#xff0c;采用了 endlessfresh的方案&#xff0c;安装endless后无法在windows本地调试,然后报错。 (优雅的重启服务-地鼠文档优雅的重启服务-我不怎么喜欢左写写&#…

蓝桥杯官网填空题(振兴中华)

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 小明参加了学校的趣味运动会&#xff0c;其中的一个项目是&#xff1a;跳格子。 地上画着一些格子&#xff0c;每个格子里写一个字&#xff0c;如下所示&#xff1…

dnmp运行时404报错

dnmp运行时404报错 问题截图&#xff1a; dnmp简介 M1芯片&#xff08;Arm CPU&#xff09; 环境中搭建PHPNGINXMYSQL的利器&#xff0c;docker容器管理当前使用的软件&#xff0c;可以简单安装软件和扩展。 localhost.conf 原始文件如下&#xff1a; server {listen 8…

海康NVR(Network Video Recorder)启用SSH过程摸索

文章目录 海康NVR具备的特点启用SSH模式优劣比较启用SSH模式的优势启用SSH模式的坏处 Hik NVR启用SSH功能1&#xff0c;Web登录NVR2&#xff0c;SSH登录NVR SSH shell模式特点SSH shell模式指令作用1&#xff0c;简要帮助“help”可以列出常用的shell指令部分可用shell指令输出…

修复 ChatGPT 发生错误的问题

目录 ChatGPT 发生错误&#xff1f;请参阅如何修复连接错误&#xff01; 修复 ChatGPT 发生错误的问题 基本故障排除技巧 检查 ChatGPT 的服务器状态 检查 API 限制 检查输入格式 清除浏览数据 香港DSE是什么&#xff1f; 台湾指考是什么&#xff1f; 王湘浩 生平 …

【漏洞复现】EnjoySCM存在文件上传漏洞

漏洞描述 EnjoySCM是一款适应于零售企业的供应链管理软件,主要为零售企业的供应商提供服务。EnjoySCM的目的是通过信息技术,实现供应商和零售企业的快速、高效、准确的信息沟通、管理信息交流。。 该系统存在任意文件上传漏洞,攻击者通过漏洞可以获取服务器的敏感信息。 …

【C#项目实战】控制台游戏勇士斗恶龙(1)——游戏初始设置以及开始界面

君兮_的个人主页 即使走的再远&#xff0c;也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;最近开始正式的步入学习游戏开发的正轨&#xff0c;想要通过写博客的方式来分享自己学到的知识和经验&#xff0c;这就是开设本专栏的目的。希望…

老太太阿姨收割机秀才被封

除了他自己和平台官方&#xff0c;恐怕没有人知道详细数字&#xff0c;不过坊间流传着一句话&#xff0c;叫“秀才和一笑倾城一场直播&#xff0c;就可以榨光一个省的老人低保 可见吸金是有多么恐怖 一笑倾城是秀才的“姊妹篇”&#xff0c;秀才专供老太太&#xff0c;一笑倾城…

uni-app:自带的消息提示被遮挡的解决办法(自定义消息提示框)

效果&#xff1a; 代码&#xff1a; 1、在最外层或者根组件的模板中添加一个容器元素&#xff0c;用于显示提示消息。例如&#xff1a; <div class"toast-container" v-if"toastMessage"><div class"toast-content">{{ toastMessa…

EMERSON A6500-CC 机架接口模块 AMS参数

EMERSON A6500-CC 机架接口模块 AMS参数 ModBus和机架接口模块设计用于工厂的高可靠性 最关键的旋转机械。它从所有AMS A6500 ATG模块读取参数 并通过ModBus TCP/IP和/或ModBus RTU&#xff08;串行&#xff09;输出这些参数。 此外&#xff0c;OPC UA可用于向第三方系统传输数…

华为Mate 60和iPhone 15选哪个?

最近也有很多朋友问我这个问题来着&#xff0c;首先两款手机定位都是高端机&#xff0c;性能和体验各有千秋&#xff0c;各自有自己的铁杆粉。 但是让人意想不到的是华为mate60近日在海外越来越受欢迎和追捧&#xff0c;甚至是引起了不少人的抢购&#xff0c;外观设计和…

C/C++操作加密与不加密的zip文件

为了后续的方便操作zip文件&#xff0c; 将所有的操作封装成了一个动态库了。 /*** \description 从压缩包文件中解压出指定的文件到指定的目录.* \author sunsz* \date 2023/09/09**/ LIBZIP_API int UnpackFile(const char* password, char zipfilename[], char filename_…

如何使用ArcGIS去除卫星影像上的云

虽然目前发布的地图都是对云量进行过筛选&#xff08;一般低于20%&#xff09;&#xff0c;但是还是有可能会遇到有云的情况&#xff08;特别是下载历史影像的时候&#xff09;&#xff0c;那么这些云应该怎么去除呢&#xff0c;我们可以尝试使用ArcGIS进行处理。 识别像素 将…

Python一行命令搭建HTTP服务器并外网访问 - 内网穿透

文章目录 1.前言2.本地http服务器搭建2.1.Python的安装和设置2.2.Python服务器设置和测试 3.cpolar的安装和注册3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 Python作为热度比较高的编程语言&#xff0c;其语法简单且语句清晰&#xff0c;而且python有…

TCP三次握手和四次挥手

目录 TCP连接建立 问题思考 1.为什么要三次握手&#xff1f; 2.三次握手一定要保证成功吗&#xff1f; TCP连接释放 问题思考 ​ 1.理解TIME-WAIT状态 2.理解CLOSE-WAIT状态 TCP连接建立 TCP建立连接的过程叫作握手&#xff0c;握手需要在客户和服务器之间交换三个TCP…