数据结构初阶--二叉树的顺序结构之堆

目录

一.堆的概念及结构

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)

  1. 若满足:L(i)>=L(2i)且L(i)>=L(2i+1)(1<=i<=n/2)--大根堆(大顶堆)
  2. 若满足:L(i)<=L(2i)且L(i)<=L(2i+1)(1<=i<=n/2)--小根堆(小顶堆)

大根堆在逻辑视角上可以看成所有子树根>=左,右的完全二叉树。相应的小根堆也可以看成根<=左,右的完全二叉树。

堆的性质:

  1. 堆中某个结点的值总是不大于或不小于其父结点的值;
  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的满二叉树构成的堆,最多进行向上调整的次数设为T(N),有:

综上,证得向上调整建堆的时间复杂度为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的满二叉树构成的堆,最多进行向上调整的次数设为T(N),有:

综上,证得向上调整建堆的时间复杂度为O(N)。 

四.堆的应用

4.1.堆排序

堆排序也就是利用堆的思想来进行排序,总共分为两个步骤:

  1. 建堆(升序建大堆,降序建小堆);
  2. 利用堆删除思想来进行排序 。

注意:

升序也可以建小堆,只是每次都要通过建堆的方式选出最小的元素。当进行第一次建堆选出最小的元素并放在数组起始位置时,剩余的元素关系就会发生错乱:原本的左孩子结点会变成新的根结点,右孩子结点会变成新的左孩子结点。然后将剩下的元素继续进行建堆,选出剩余元素中最小的元素并放入数组起始位置的下一个位置,重复上述操作,直到整个数组有序。整体时间复杂度为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;
}

运行结果:

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

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

相关文章

[Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序

1.今天开发了一套服务程序&#xff0c;使用的是Odbc连接MySql数据库&#xff0c; 在我本机用VS打开程序时&#xff0c;访问一切正常&#xff0c;当发布出来装在电脑上&#xff0c;连接数据库时提示&#xff1a; [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定…

VoxWeekly|The Sandbox 生态周报|20230731

欢迎来到由 The Sandbox 发布的《VoxWeekly》。我们会在每周发布&#xff0c;对上一周 The Sandbox 生态系统所发生的事情进行总结。 如果你喜欢我们内容&#xff0c;欢迎与朋友和家人分享。请订阅我们的 Medium 、关注我们的 Twitter&#xff0c;并加入 Discord 社区&#xf…

Zebec Protocol 将进军尼泊尔市场,通过 Zebec Card 推动该地区金融平等

流支付正在成为一种全新的支付形态&#xff0c;Zebec Protocol 作为流支付的主要推崇者&#xff0c;正在积极的推动该支付方案向更广泛的应用场景拓展。目前&#xff0c;Zebec Protocol 成功的将流支付应用在薪酬支付领域&#xff0c;并通过收购 WageLink 将其纳入旗下&#xf…

C#实现SqlServer数据库同步

实现效果&#xff1a; 设计思路&#xff1a; 1. 开启数据库及表的cdc&#xff0c;定时查询cdc表数据&#xff0c;封装sql语句(通过执行类型&#xff0c;主键;修改类型的cdc数据只取最后更新的记录)&#xff0c;添加到离线数据表&#xff1b; 2. 线程定时查询离线数据表&#xf…

有哪些常用的设计素材网站?

素材网站可以是设计师和创意人员的灵感来源。这些网站收集了各种类型的平面设计图片&#xff0c;包括标志、海报、网站设计、包装设计、插图等。在本文中&#xff0c;我将推荐15个平面设计图素材网站&#xff0c;以帮助您找到新的想法和灵感。 1.即时设计资源社区 即时设计资…

SpringBoot 热部署

文章目录 前言一、spring-boot-devtools添加热部署框架支持settings 开启项目自动编译开启运行中热部署使用Debug启动 二、IDEA 自带 HowSwap 功能设置 Spring Boot 启动类等待项目启动完成点击热加载按钮存在的问题 三、JRebel 插件【推荐】安装插件使用插件 前言 在日常开发…

分布式协议与算法——CAP理论、ACID理论、BASE理论

CAP理论 CAP理论&#xff0c;对分布式系统的特性做了高度抽象&#xff0c;比如抽象成了一致性、可用性和分区容错性&#xff0c;并对特性间的冲突&#xff08;也就是CAP不可能三角&#xff09;做了总结。 CAP三指标 CAP理论对分布式系统的特性做了高度抽象&#xff0c;形成了…

BL302嵌入式ARM控制器进行SQLite3数据库操作的实例演示

本文主要讲述了在钡铼技术BL302嵌入式arm控制器上运行 SQLite3 数据库的命令示例。SQLite3 是一个轻型的嵌入式数据库&#xff0c;不需要安装数据库服务器进程&#xff0c;占用资源低且处理速度快。 首先&#xff0c;需要将对应版本的 SQLite3 文件复制到设备的 /usr/ 目录下&…

python之prettytable库的使用

文章目录 一 什么是prettytable二 prettytable的简单使用1. 添加表头2. 添加行3. 添加列4. 设置对齐方式4. 设置输出表格样式5. 自定义边框样式6. 其它功能 三 prettytable在实际中的使用 一 什么是prettytable prettytable是Python的一个第三方工具库&#xff0c;用于创建漂亮…

CI/CD持续集成持续发布(jenkins)

1.背景 在实际开发中&#xff0c;我们经常要一边开发一边测试&#xff0c;当然这里说的测试并不是程序员对自己代码的单元测试&#xff0c;而是同组程序员将代码提交后&#xff0c;由测试人员测试&#xff1b; 或者前后端分离后&#xff0c;经常会修改接口&#xff0c;然后重新…

Makefile

什么是 Makefile 一个工程中的源文件不计其数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c; Makefile文件定义了一系列的规则来指定哪些文件需要先编译&#xff0c;哪些文件需要后编 译&#xff0c;哪些文件需要重新编译&#xff0c;甚至于进行更复杂的功…

Android平台GB28181设备接入端如何实现多视频通道接入?

技术背景 我们在设计Android平台GB28181设备接入模块的时候&#xff0c;有这样的场景诉求&#xff0c;一个设备可能需要多个通道&#xff0c;常见的场景&#xff0c;比如车载终端&#xff0c;一台设备&#xff0c;可能需要接入多个摄像头&#xff0c;那么这台车载终端设备可以…

使用webpack插件webpack-dev-server 出现Cannot GET/的解决办法

问题描述 文档地址深入浅出webpack 使用 DevServer运行webpack&#xff0c;跑起来之后提示Cannot GET/&#xff1a; 解决方案&#xff1a; 查阅官方文档 根据目录结构修改对应的配置&#xff1a; 然后就可以成功访问&#xff1a;

【MongoDB】万字长文,命令与代码一一对应SpringBoot整合MongoDB之MongoTemplate

目录 一、导入依赖与配置信息 二、导入测试数据创建实体类 三、插入数据 1、Insert默认集合插入 2、Insert指定集合插入 3、Insert批量插入数据 4、save默认集合插入 5、save指定集合插入 6、insert与save的区别 四、修改数据 1、修改符合条件的第一条数据 2、全…

redis 原理 7:开源节流 —— 小对象压缩

Redis 是一个非常耗费内存的数据库&#xff0c;它所有的数据都放在内存里。如果我们不注意节约使用内存&#xff0c;Redis 就会因为我们的无节制使用出现内存不足而崩溃。Redis 作者为了优化数据结构的内存占用&#xff0c;也苦心孤诣增加了非常多的优化点&#xff0c;这些优化…

[PM]敏捷开发之Scrum总结

在项目管理中&#xff0c;不少企业和项目团队也发现传统的项目管理模式已不能很好地适应今天的项目环境的要求。因此&#xff0c;敏捷项目管理应运而生&#xff0c;本文将为大家介绍Scrum敏捷项目管理以及应用方法。 什么是Scrum敏捷项目管理 敏捷项目管理作为新兴的项目管理模…

Java后台生成ECharts图片,并以Base64字符串返回

前言 通过echarts的jar包&#xff0c;Java后台生成一张图片&#xff0c;并把图片插入到word中。关于word插图片的代码在下一章。 需要用到的工具PhantomJS,Echarts-convert.js,jquery.js,echarts.js。 1.PhantomJS 介绍 PhantomJS是一个不需要浏览器的富客户端。 官方介绍&…

[保研/考研机试] 猫狗收容所 C++实现

题目描述&#xff1a; 输入&#xff1a; 第一个是n&#xff0c;它代表操作序列的次数。接下来是n行&#xff0c;每行有两个值m和t&#xff0c;分别代表题目中操作的两个元素。 输出&#xff1a; 按顺序输出收养动物的序列&#xff0c;编号之间以空格间隔。 源代码&#xff…

用i18n 实现vue2+element UI的国际化多语言切换详细步骤及代码

一、i18n的安装 这个地方要注意自己的vue版本和i1n8的匹配程度&#xff0c;如果是vue2点几&#xff0c;记得安装i18n的8版本&#xff0c;不然会自动安装的最新版本&#xff0c;后面会报错哦&#xff0c;查询了下资料&#xff0c;好像最新版本是适配的vue3。 npm install vue-…

Attacks in NLP

一、 Introduction NLP对抗攻击是人工智能对抗攻击的一个重要的组成部分&#xff0c;但是最近几年才逐渐开始兴起&#xff0c;究其原因在于NLP对抗攻击与传统computer vision或者audio对抗攻击有很大的不同&#xff0c;主要在于值空间的连续性&#xff08;CV、audio&#xff0…