数据结构初阶--二叉树的顺序结构之堆
目录
一.堆的概念及结构
1.1.堆的概念
1.2.堆的存储结构
二.堆的功能实现
2.1.堆的定义
2.2.堆的初始化
2.3.堆的销毁
2.4.堆的打印
2.5.堆的插入
向上调整算法
堆的插入
2.6.堆的删除
向下调整算法
堆的删除
2.7.堆的取堆顶元素
2.8.堆的判空
2.9.堆的求堆的大小
三.堆的创建
3.1.向上调整建堆
时间复杂度
3.2.向下调整建堆
时间复杂度
四.堆的应用
4.1.堆排序
步骤一:建堆
步骤二:排序
4.2.TOP-K问题
一.堆的概念及结构
1.1.堆的概念
若n个关键字序列L[1...n]满足下面某一条性质,则称为堆(Heap)
- 若满足:L(i)>=L(2i)且L(i)>=L(2i+1)(1<=i<=n/2)--大根堆(大顶堆)
- 若满足:L(i)<=L(2i)且L(i)<=L(2i+1)(1<=i<=n/2)--小根堆(小顶堆)
大根堆在逻辑视角上可以看成所有子树根>=左,右的完全二叉树。相应的小根堆也可以看成根<=左,右的完全二叉树。
堆的性质:
- 堆中某个结点的值总是不大于或不小于其父结点的值;
- 堆总是一棵完全二叉树。
1.2.堆的存储结构
普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储。
二.堆的功能实现
2.1.堆的定义
typedef int HPDataType;typedef struct Heap
{HPDataType* a;//开辟一个动态数组aint size;//当前元素个数int capacity;//数组的最大容量
}HP;
定义一个struct来保存堆的信息,主要包含数组首元素的地址a,数组中当前元素个数size以及数组的最大容量capacity。堆的定义同顺序表的定义类似。
2.2.堆的初始化
void HeapInit(HP* php)
{//判空assert(php);//将数组首元素的地址位置空php->a = NULL;php->size = php->capacity = 0;
}
在初始化堆之前,首先需要对传入的参数php进行断言判断其是否为空,然后将数组首元素的地址置为空NULL,最后再将size和capacity都初始化为0。
调试分析:
2.3.堆的销毁
void HeapDestory(HP* php)
{//判空assert(php);//释放free(php->a);php->a = NULL;php->size = php->capacity = 0;
}
在销毁堆之前,首先需要对传入的参数php进行断言判断其是否为空,然后调用free函数释放数组首元素的地址,并将数组首元素的地址置为空NULL,最后再将size和capacity都置为0。
调试分析:
2.4.堆的打印
void HeapPrint(HP* php)
{//判空assert(php);//打印for (int i = 0; i < php->size; ++i){printf("%d ", php->a[i]);}printf("\n");
}
首先判断传入的参数php是否为空,然后进行for循环依次打印数组中的各个元素。
2.5.堆的插入
当在堆中插入新元素时,对于小根堆,新元素放到表尾,与父结点对比,若新元素比父结点更小,则将二者互换。新元素就这样一路向上调整,直到无法继续上升为止。
向上调整算法
当在堆的末尾插入一个新元素,而新插入的元素可能会破坏堆的性质,这时就要进行调整。以小堆为例,当新元素大于其对应的父结点,则满足堆的性质,无需调整;当新元素小于其对应的父结点,则不满足堆的性质,要进行调整。
调整规则:
若新插入的元素child小于其对应的父结点parent,则调用Swap函数,将二者进行交换,此时child来到父结点parent的位置,其对应的新的父结点的下标为(child-1)/2,然后将child继续与parent进行比较,依次往上执行,直到child大于其对应的父结点parent,则跳出循环。
循环判断条件为:child>0,这是考虑到最坏的情况,也就是当child一直小于其对应的父结点时,child经过最后一次交换来到根结点的位置时,此时堆的调整已经结束。
实现:
//交换数据
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}//向上调整(小堆)
void AdjustUp(HPDataType* a, int child)
{//查找父结点下标int parent = (child - 1) / 2;//最坏情况是一路调整到根while (child > 0){if (a[child] < a[parent])//大堆:a[child]>a[parent]{//将父子结点进行交换Swap(&a[child], &a[parent]);//把父结点的下标赋值给孩子child = parent;//再去查找父结点的下标parent = (child - 1) / 2;}else{//若已构成堆,则直接跳出循环break;}}
}
堆的插入
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 (tmp == NULL){printf("realloc fail\n");exit(-1);}//将新开辟的内存空间的首地址tmp赋值给aphp->a = tmp;//更新capacityphp->capacity = newCapacity;}//插入//先将元素插入到堆的末尾,即最后一个孩子后面//插入之后如果堆的性质遭到了破坏,则将新插入结点顺着其双亲往上调整到合适的位置php->a[php->size] = x;//注意:size指向数组最后一个元素的下一个位置php->size++;//向上调整AdjustUp(php->a, php->size - 1);
}
在堆中插入元素之前,首先需要检查当前容量是否为空或者已满。若容量为空,则调用realloc函数开辟四个元素的内存空间,若容量已满,则调用realloc函数将内存空间开辟到原来的二倍,并将新开辟的内存空间的首地址tmp赋值给a,同时更新capacity。接着便可以插入元素,因为size是指向数组最后一个元素的下一个位置,所以先将新元素x插到下标为size的位置,然后再将size+1。最后再调用AdjustUp函数,进行向上调整。
调试分析:
运行结果:
2.6.堆的删除
堆的删除是删除堆顶的元素,将堆顶的元素与最后一个元素交换,然后删除数组最后一个元素,再进行向下调整。
向下调整算法
当对堆进行删除时,删除的往往是堆顶元素,删除之后可能会破坏堆的性质,这时就要进行调整。以小堆为例,在删除之前,首先将堆顶元素与最后一个元素交换,交换完之后再将最后一个元素删除。然后从根结点开始依次向下调整,直到把它调整为一个小堆。
向下调整算法的前提:左右子树必须是一个堆,才能调整。
调整规则:
首先选出根结点的左右孩子中较小的那一个,这里先假设左孩子最小,然后将左孩子与右孩子进行比较,若左孩子小于右孩子,则不变;若左孩子大于右孩子,则将右孩子设为最小。然后将最小的孩子与父结点进行比较,如果比父结点小,则交换,交换完之后,把孩子结点child所在的下标赋值给父结点parent,并让child指向新的父结点的左孩子,然后依次向下比较,直到调整到叶子结点的位置;如果比父结点大,则调整结束。
实现:
//交换数据
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}void AdjustDown(HPDataType* a, int size, int parent)
{//1.选出左右孩子中小的那一个int child = parent * 2 + 1;//假设左孩子最小while (child < size){//当右孩子存在且右孩子小于左孩子if (child + 1 < size && a[child + 1] < a[child])//大堆:a[child+1]>a[child]{++child;//则将右孩子置为child}//2.小的孩子跟父亲比较,如果比父亲小,则交换,然后继续往下调整;如果比父亲大,则调整结束if (a[child] < a[parent])//大堆:a[child]>a[parent]{//交换数据Swap(&a[child], &a[parent]);//3.继续往下调,最多调整到叶子结点就结束//把孩子的下标赋值给父亲parent = child;//假设左孩子最小child = parent * 2 + 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, php->size, 0);
}
在删除之前,首先需要判断数组是否为空,若为空则无法进行删除,若不为空则可以进行删除。然后调用Swap函数将数组的第一个待删除元素与数组的最后一个元素进行交换,并让size-1,删除最后一个元素。最后再调用函数AdjustDown,进行向下调整。
调试分析:
运行结果:
2.7.堆的取堆顶元素
HPDataType HeapTop(HP* php)
{//判空assert(php);//判断数组是否为空assert(php->size > 0);//根结点即为堆顶元素return php->a[0];
}
在取堆顶元素之前,首先要对数组进行判空操作,若数组为空则无法进行读取操作,若数组不为空则直接读取数组的首元素,数组的首元素也就是根结点,即为堆顶元素。
调试分析:
运行结果:
2.8.堆的判空
bool HeapEmpty(HP* php)
{//判空assert(php);//看size的大小是否为0return php->size == 0;
}
判断堆是否为空,只需判断size是否等于0,若size为0,则数组为空,即堆为空;若size不为0,则数组不为空,即堆不为空。
调试分析:
2.9.堆的求堆的大小
int HeapSize(HP* php)
{//判空assert(php);//size的大小即为数组的大小,也就是堆的大小return php->size;
}
求堆的大小,只需求数组中当前元素个数,也就是求size的大小。
调试分析:
三.堆的创建
3.1.向上调整建堆
向上调整建堆,实际上是模拟堆的插入过程。首先,将数组中的第一个元素看做是堆的根结点,然后将数组中的元素依次插入堆中,每插入一个元素,就调用函数AdjustUp向上调整一次,直到将所有的元素均插入堆中。
实现:
for (int i = 1; i < n; ++i)//从第一个位置插入
{AdjustUp(a, i);
}
时间复杂度
因为堆是一棵完全二叉树,而满二叉树又是一种特殊的完全二叉树,为了简化计算,我们不妨假设此处的堆是一棵满二叉树。
由上图得,对于高度为h的满二叉树构成的堆,最多进行向上调整的次数设为,有:
综上,证得向上调整建堆的时间复杂度为O(N*logN)。
3.2.向下调整建堆
首先将数组中的元素以完全二叉树的形式排列好,然后从倒数第一个非叶子结点开始,调用函数AdjustDown依次向下调整。每调整一次,则将i的值减1,让其来到倒数第二个非叶子结点的位置,重复上述操作,直到i来到根结点的位置。
实现:
for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1为最后一个结点的下标,求最后一个结点的父结点的下标(n-1-1)/2
{AdjustDown(a, n, i);
}
注意:
我们可以直接通过向上调整算法来建堆,但是我们不可以直接通过向下调整算法来建堆。因为向下调整算法的前提:左右子树必须是堆,才能调整。
时间复杂度
因为堆是一棵完全二叉树,而满二叉树又是一种特殊的完全二叉树,为了简化计算,我们不妨假设此处的堆是一棵满二叉树。
由上图得,对于高度为h的满二叉树构成的堆,最多进行向上调整的次数设为,有:
综上,证得向上调整建堆的时间复杂度为O(N)。
四.堆的应用
4.1.堆排序
堆排序也就是利用堆的思想来进行排序,总共分为两个步骤:
- 建堆(升序建大堆,降序建小堆);
- 利用堆删除思想来进行排序 。
注意:
升序也可以建小堆,只是每次都要通过建堆的方式选出最小的元素。当进行第一次建堆选出最小的元素并放在数组起始位置时,剩余的元素关系就会发生错乱:原本的左孩子结点会变成新的根结点,右孩子结点会变成新的左孩子结点。然后将剩下的元素继续进行建堆,选出剩余元素中最小的元素并放入数组起始位置的下一个位置,重复上述操作,直到整个数组有序。整体时间复杂度为O(N^2),可见效率太低,没有使用到堆的优势。因此,升序要建大堆。
我们以升序建大堆为例:
步骤一:建堆
这里采用时间复杂度较低的向下建堆法来进行大根堆的建立。
实现:
//交换数据
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{//1.选出左右孩子中小的那一个int child = parent * 2 + 1;//假设左孩子最小while (child < size){//当右孩子存在且右孩子小于左孩子if (child + 1 < size && a[child + 1] > a[child])//大堆:a[child+1]>a[child]{++child;//则将右孩子置为child}//2.小的孩子跟父亲比较,如果比父亲小,则交换,然后继续往下调整;如果比父亲大,则调整结束if (a[child] > a[parent])//大堆:a[child]>a[parent]{//交换数据Swap(&a[child], &a[parent]);//3.继续往下调,最多调整到叶子结点就结束//把孩子的下标赋值给父亲parent = child;//假设左孩子最小child = parent * 2 + 1;}else{break;}}
}void HeapSort(int* a, int n)
{//建堆//建堆方式二:向下调整//向下调整算法的左右子树必须是堆,因此不能使用该方法直接建堆//时间复杂度:O(N)//首先将数组中的元素以完全二叉树的形式排列好,然后从倒数第一个非叶子结点开始,依次向下调整for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1为最后一个结点的下标,求最后一个结点的父结点的下标(n-1-1)/2{AdjustDown(a, n, i);}
}int main()
{int a[] = { 27,15,19,18,28,34,65,49,25,37 };HeapSort(a, sizeof(a) / sizeof(a[0]));return 0;
}
调试分析:
建堆前:
建堆后:
步骤二:排序
这里用到堆的删除思想。先交换数组的首尾元素,此时尾结点中的元素为堆中的最大值。然后将堆的最后一个元素排除在外,并继续从根结点开始,对堆进行向下调整。重复上述操作,直到堆中仅剩一个元素为止。
实现:
//交换数据
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{//1.选出左右孩子中小的那一个int child = parent * 2 + 1;//假设左孩子最小while (child < size){//当右孩子存在且右孩子小于左孩子if (child + 1 < size && a[child + 1] > a[child])//大堆:a[child+1]>a[child]{++child;//则将右孩子置为child}//2.小的孩子跟父亲比较,如果比父亲小,则交换,然后继续往下调整;如果比父亲大,则调整结束if (a[child] > a[parent])//大堆:a[child]>a[parent]{//交换数据Swap(&a[child], &a[parent]);//3.继续往下调,最多调整到叶子结点就结束//把孩子的下标赋值给父亲parent = child;//假设左孩子最小child = parent * 2 + 1;}else{break;}}
}void HeapSort(int* a, int n)
{//建堆//建堆方式二:向下调整//向下调整算法的左右子树必须是堆,因此不能使用该方法直接建堆//时间复杂度:O(N)//首先将数组中的元素以完全二叉树的形式排列好,然后从倒数第一个非叶子结点开始,依次向下调整for (int i = (n - 1 - 1) / 2; i >= 0; i--)//n-1为最后一个结点的下标,求最后一个结点的父结点的下标(n-1-1)/2{AdjustDown(a, n, i);}//排序//时间复杂度:O(N*logN),其中N为元素个数,logN为向上调整的次数,也即树的高度int end = n - 1;while (end > 0){//将第一个结点与最后一个结点交换Swap(&a[0], &a[end]);//向下调整选出次大的数AdjustDown(a, end, 0);--end;}
}int main()
{int a[] = { 27,15,19,18,28,34,65,49,25,37 };HeapSort(a, sizeof(a) / sizeof(a[0]));return 0;
}
调试分析:
排序前:
排序后:
小结:
建堆和堆的删除都用到了向下调整,因此掌握了向下调整,就可以完成排序。
建堆的时间复杂度为:O(N),排序的时间复杂度为:O(N*logN)。取影响结果较大的一个,也就是O(N*logN)。所以堆排序的时间复杂度为O(N*logN)。
4.2.TOP-K问题
TOP-K问题:即求数据集合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
对于TOP-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。
法一:
堆排序,采用时间复杂度最低的堆排序,时间复杂度为O(N*logN);
法二:
首先建立N个数的大根堆,然后Top/Pop k次,时间复杂度为O(N+k*logN);
注意:上述两种方法在数据量非常大时,是不太可取的。
法三:
假设N非常大,比如N是100亿,而K比较小,假如k是100,如何求解?
首先,将数据集合中前k个数建立小根堆,时间复杂度:O(k);
然后,将剩下的N-k个元素依次和堆顶元素进行比较,如果比堆顶元素大,就替换堆顶元素,并进行向下调整;
待N-k个元素依次和堆顶元素比较完之后,堆中剩余的k个元素就是所求的最大的前k个元素,时间复杂度:O((N-k)*logk)。
注意:法三较于前两种方法有很高的空间效率。
实现:
//交换数据
void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{//1.选出左右孩子中小的那一个int child = parent * 2 + 1;//假设左孩子最小while (child < size){//当右孩子存在且右孩子小于左孩子if (child + 1 < size && a[child + 1] < a[child])//大堆:a[child+1]>a[child]{++child;//则将右孩子置为child}//2.小的孩子跟父亲比较,如果比父亲小,则交换,然后继续往下调整;如果比父亲大,则调整结束if (a[child] < a[parent])//大堆:a[child]>a[parent]{//交换数据Swap(&a[child], &a[parent]);//3.继续往下调,最多调整到叶子结点就结束//把孩子的下标赋值给父亲parent = child;//假设左孩子最小child = parent * 2 + 1;}else{break;}}
}void PrintTopK(int* a, int n, int k)
{//1.建堆:用a中前k个元素建堆int* kMinHeap = (int*)malloc(sizeof(int) * k);assert(kMinHeap);for (int i = 0; i < k; i++){//将a中前k的元素放进kMinHeap中kMinHeap[i] = a[i];}//建立小根堆for (int i = (k - 1 - 1) / 2; i >= 0; --i){AdjustDown(kMinHeap, k, i);}//2.将剩余的n-k个元素依次与堆顶元素比较,不满则替换for (int j = k; j < n; j++){//若后面的元素大于堆顶元素,则进行替换,并向下调整if (a[j] > kMinHeap[0]){kMinHeap[0] = a[j];AdjustDown(kMinHeap, k, 0);}}//3.打印最大的前k个元素for (int i = 0; i < k; i++){printf("%d ", kMinHeap[i]);}printf("\n");//销毁free(kMinHeap);
}void TestTopk()
{int n = 10000;int* a = (int*)malloc(sizeof(int) * n);assert(a);srand((size_t)time(0));for (int i = 0; i < n; i++){//产生一万个不大于100万的随机数a[i] = rand() % 1000000;}a[5] = 1000000 + 1;a[1231] = 1000000 + 2;a[531] = 1000000 + 3;a[5121] = 1000000 + 4;a[115] = 1000000 + 5;a[2335] = 1000000 + 6;a[9999] = 1000000 + 7;a[76] = 1000000 + 8;a[423] = 1000000 + 9;a[3144] = 1000000 + 10;PrintTopK(a, n, 10);
}int main()
{TestTopk();return 0;
}
运行结果:
相关文章:

数据结构初阶--二叉树的顺序结构之堆
目录 一.堆的概念及结构 1.1.堆的概念 1.2.堆的存储结构 二.堆的功能实现 2.1.堆的定义 2.2.堆的初始化 2.3.堆的销毁 2.4.堆的打印 2.5.堆的插入 向上调整算法 堆的插入 2.6.堆的删除 向下调整算法 堆的删除 2.7.堆的取堆顶元素 2.8.堆的判空 2.9.堆的求堆的…...
NVM Command学习
ubuntu系统安装nvme-cli,可以在应用层发起命令。 sudo apt install nvme-cli$ sudo nvme --help nvme-1.9 usage: nvme <command> [<device>] [<args>]The <device> may be either an NVMe character device (ex: /dev/nvme0) or an nvme …...

TCP Socket 基础知识点(实例是以Java进行演示)
本篇根据TCP & Socket 相关知识点和学习所得进行整理所得。 文章目录 前言1. TCP相关知识点1.1 双工/单工1.2 TCP协议的主要特点1.3 TCP的可靠性原理1.4 报文段1.4.1 端口1.4.2 seq序号1.4.3 ack确认号1.4.4 数据偏移1.4.5 保留1.4.6 控制位1.4.7 窗口1.4.8 校验和1.4.9 紧…...

openCV图像读取和显示
文章目录 一、imread二、namedWindow三、imshow #include <opencv2/opencv.hpp> #include <iostream>using namespace std; using namespace cv;int main(int argc,char** argv) {cv::Mat img imread("./sun.png"); //3通道 24位if (img.empty()) {std:…...
requests 方法总结
当使用 requests 库进行接口自动化测试时,以下是一些详细的步骤和方法总结: 1. **安装 requests 库**:首先,确保你已经安装了 requests 库。可以使用 pip 命令进行安装:pip install requests。 2. **导入库**&#x…...
Go语言删除文本文件中的指定行
GO语言删除文本文件中的指定行 1. 思路2. 处理文件3. 处理后的文本文件 1. 思路 假设现在有一个文本文件,我们需要删除文件中乱码的行。我们可以使用go的os库来处理文件,遍历整个文件然后将除过乱码的行写入一个新文件,以此来实现我们的需求…...

Arthas GC日志-JVM(十八)
上篇文章说jvm的实际运行情况。 Jvm实际运行情况-JVM(十七) Arthas介绍 因为arthas完全是java代码写的,我们直接用命令启动: Java -jar arthas-boot.jar 启动成功后,选择我们项目的进程。 进入我们可用dashboard…...

ISC 2023︱诚邀您参与赛宁“安全验证评估”论坛
8月9日-10日,第十一届互联网安全大会(简称ISC 2023)将在北京国家会议中心举办。本次大会以“安全即服务,开启人工智能时代数字安全新范式”为主题,打造全球首场AI数字安全峰会,赋予安全即服务新时代内涵…...

分享一个计算器
先看效果: 再看代码(查看更多): <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>计算器</title><style>* {box-sizing: border-box;}body…...
Android 13 Launcher——长按图标弹窗背景变暗
目录 一.背景 二.修改代码 一.背景 客户定制需要长按图标弹窗让其背景变暗,所以需要进行定制,如下是定制流程,本篇是接上篇https://gonglipeng.blog.csdn.net/article/details/132171100 的内容 二.修改代码 主要代码逻辑在ArrowPopup中的reorderAndShow方法和closeCom…...
Elasticsearch概述和DSL查询总结
目录 Elasticsearch概述 1. 什么是Elasticsearch 2. 作用 3. 特点 DSL(Domain Specifit Language)特定领域语言: 概念和作用 查询代码总结 最后附项目准备 1.创建搜索工程(maven工程) 2.配置文件 application…...

扩展卡尔曼滤波器代码
文章目录 前言问题状态向量和观测向量加性噪声的形式状态方程及求导观测方程及求导状态初始化过程噪声和观测噪声卡尔曼滤波过程code 前言 卡尔曼滤波器在1960年被卡尔曼发明之后,被广泛应用在动态系统预测。在自动驾驶、机器人、AR领域等应用广泛。卡尔曼滤波器使…...

9:00开始面试,9:08就出来了,这问题问的实在是····
外包工作3年,今年裸辞跳槽,很幸运的是找到了下家,不过 自从加入到这家公司,每天不是在加班就是在加班的路上,薪资倒是给的不少,所以我也就忍了。没想到6月一纸通知,所有人都不许加班࿰…...

揭秘:5个美国程序员与日本程序员的差异
大家好,这里是程序员晚枫。想了解更多精彩内容,快来关注程序员晚枫 今天以美国和日本程序员为例,给大家分享一下国外程序员的生活。 以下是五个美国程序员和日本程序员的的区别: 工作方式:美国程序员通常更注重自由和…...
Springboot实现简单JWT登录鉴权
登录为啥需要鉴权? 登录需要鉴权是为了保护系统的安全性和用户的隐私。在一个 Web 应用中,用户需要提供一定的身份信息(例如用户名和密码)进行登录,登录后系统会为用户生成一个身份令牌(例如 JWT Token&am…...
C++设计模式创建型之工厂模式整理
一、工厂模式分类 工厂模式属于创建型模式,一般可以细分为简单工厂模式、工厂模式和抽象工厂模式。每种都有不同的特色和应用场景。 二、工厂模式详情 1、简单工厂模式 1)概述 简单工厂模式相对来说,在四人组写的《设计模式------可复用面…...

前端安全XSS和CSRF讲解
文章目录 XSSXSS攻击原理常见的攻击方式预防措施 CSRFCSRF攻击原理常见攻击情景预防措施: CSRF和XSS的区别 XSS 全称Cross Site Scripting,名为跨站脚本攻击。为啥不是单词第一个字母组合CSS,大概率与样式名称css进行区分。 XSS攻击原理 不…...

本地化部署自建类ChatGPT服务远程访问
本地化部署自建类ChatGPT服务远程访问 文章目录 本地化部署自建类ChatGPT服务远程访问前言系统环境1. 安装Text generation web UI2.安装依赖3. 安装语言模型4. 启动5. 安装cpolar 内网穿透6. 创建公网地址7. 公网访问8. 固定公网地址 🍀小结🍀 前言 Te…...

一、Webpack相关(包括webpack-dev-server用以热更新和html-webpack-plugin)
概念与功能: webpack是前端项目工程化的具体解决方案。它提供了友好的前端模块化开发支持,以及代码压缩混淆、处理浏览器端JavaScript的兼容性、性能优化等强大的功能。 快速上手:隔行变色 -S实际是--save的简写,表示安装的第三方…...

安全防御(3)
1.总结当堂NAT与双机热备原理,形成思维导图 2.完成课堂nat与双机热备试验 引用IDS是指入侵检测系统,它可以在网络中检测和防御入侵行为。IDS的签名是指根据已知入侵行为的特征制定的规则,用于检测和警告可能存在的入侵行为。签名过滤器可以根…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
django blank 与 null的区别
1.blank blank控制表单验证时是否允许字段为空 2.null null控制数据库层面是否为空 但是,要注意以下几点: Django的表单验证与null无关:null参数控制的是数据库层面字段是否可以为NULL,而blank参数控制的是Django表单验证时字…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...