【数据结构】堆(Heap)

一、堆的概念及结构

1、概念

堆(Heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵 完全二叉树数组对象。 堆是非线性数据结构,相当于一维数组,有两个直接后继。
如果有一个关键码的集合K = { k₀,k₁,k,k,…,kₙ₋₁  },把它的所有元素按完全二叉树的顺序存储方式存储,在一个一维数组中,并满足:K <= K*ᵢ₊₁  且 K <= K*ᵢ₊₂  (K >= K*ᵢ₊  且 K >= K*ᵢ₊₂ ) i = 0,1,2…,则称为小堆 (或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。

大根堆和小根堆】:

根结点最大的堆叫做大根堆,树中所有父亲都大于或等于孩子。

根结点最小的堆叫做小根堆,树中所有父亲都小于或等于孩子。

这个大根堆和小根堆有什么特点呢?

最值总在 0 号位,根据这个特点我们可以做很多事情。比如 TopK 问题 (在一堆数据里面找到前 K 个最大 / 最小的数)。生活中也有很多实例,比如某外卖软件中有上千家店铺,我想选出当地好评最多的十家烤肉店,这时我们不用对所有数据进行排序,只需要取出前 K 个最大 / 最小数据。使用堆排序效率也更高。 


2、性质

  • 堆中某个节点的值总是不大于或不小于其父节点的值;
  • 堆总是一棵完全二叉树


二、堆的实现

1、堆向下调整算法

给出一个数组,逻辑上看做一颗完全二叉树。我们通过从根节点开始的 向下调整算法 可以把它调整成一个 小堆 向下调整算法有一个前提:左右子树必须是一个堆包括大堆和小堆) ,才能调整
  • 从根节点开始,不断往下调。
  • 选出根节点的左右孩子中小的那个孩子,再与父亲进行比较。
  • (1)如果父亲小于孩子,就不需处理了,整个树已经是小堆了。
  • (2) 如果父亲大于孩子,就跟父亲交换位置,并将原来小的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。
int array[] = {27,15,19,18,28,34,65,49,25,37}; // 根节点的左右子树都是小堆

// 向下调整算法 -- 调成小堆,把大的节点往下调整
void AdjustDown(int* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){// 判断右孩子是否存在 选出左右孩子中小的那个if (child + 1 < size && a[child + 1] < a[child]){++child;}// 孩子跟父亲比较if (a[child] < a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}

【时间复杂度】我们以满二叉树计算,最坏情况下,向下调整算法最多进行满二叉树的高度减 1 次比较,则说明向下调整算法最多调整满二叉树的高度减 1 次,n 个节点的满二叉树高度为 log₂(n+1),估算后所以时间复杂度为 O(log₂n)。 


2、堆的创建

给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但还不是一个堆,现在我们可以通过算法,把它构建成一个。根节点左右子树不是堆,我们怎么调整呢?我们从倒数的第一个非叶子节点的子树开始调整,一直调整到根节点的树,就可以调整成堆

为什么要倒着调整呢?

因为这样我们可以倒数第一个非叶子节点的子树的左右子树看成是一个 (大 / 小) 堆,满足向下调整的前提条件,这时才能去使用向下调整算法。 

// 建大堆
int a[] = {1,5,3,8,7,6};

建堆过程演示(以建大堆为例)从下到上,依次遍历完所有非叶子节点,分别对每个子树进行向下调整。依次进行每一步,方框内的树进行向下调整为一个大堆

调换 1 和 8 的位置时,8 的其左子树构成的递归结构被破坏。因此,在每一次发生元素交换时,都需要递归调用重新构造堆的结构。

#include <stdio.h>
#include <stdlib.h>void Swap(HPDataType* p1, HPDataType* p2)
{HPDataType tmp = *p1;*p1 = *p2;*p2 = tmp;
}// 向下调整算法 -- 建大堆,把小的节点往下调整
void AdjustDown(int* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){// 判断右孩子是否存在 选出左右孩子中大的那个if (child + 1 < size && a[child + 1] > a[child]){++child;}// 孩子跟父亲比较if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}// 升序 -- 建大堆 -- 每次选出一个最大的数放到最后
// 降序 -- 建小堆 -- 每次选出一个最小的数放到最后
// 堆排序 -- 效率更高
void HeapSort(int* a, int n)
{// O(N)// botto-top(自底向上),依次遍历完所有子树,分别对其进行调整for (int i=((n-1)-1)/2; i>=0; i--) // 从最后一个叶子节点的父亲的下标开始{AdjustDown(a, n, i);}// 升序// O(N*logN)int end = n - 1; // 记录堆中最后一个元素的下标while (end > 0){Swap(&a[0], &a[end]); // 将堆顶元素和堆中最后一个元素交换,把最大的数(堆顶)放到最后AdjustDown(a, end, 0);--end;}
}

【拓展】堆的创建(采用向上调整法)

下面给出一个数组,这个数组逻辑上可以看做一颗完全二叉树,但不是一个堆,我们需要通过向上调整算法把它构建成一个堆。如果根节点左右子树不是一个 (大 / 小) 堆,我们应该怎么调整呢?

我们从上到下第一个节点(也就是根节点)的左孩子开始,依次遍历完所有节点,分别对每个节点进行向上调整,一直到最后一个节点,就可以建成一个 (大 / 小) 堆

我们把数组中的第一个元素看作是一个剩余的元素依次插入到这个。这跟堆的插入接口原理相同,就是向上调整。

如果堆的创建过程使用向上调整算法,那么每次插入一个新元素时都需要进行一次向上调整操作,以确保新插入的元素能够满足堆的性质。

【时间复杂度】

        假设堆中已经有 n 个元素,那么堆的高度 h = log₂(n+1),在插入一个新元素的过程中,需要进行的向上调整操作次数为 h-1,则 h = log₂(n+1) - 1,该操作的时间复杂度为O(log₂n)。需要注意的是,这个时间复杂度是针对单次 “向上建堆” 操作而言的。如果需要对一个无序数组进行完整的堆排序,则需要进行 n/2 次 “向上建堆” 操作,那么整个堆排序的时间复杂度为 O(nlog n)。所以,如果使用向上调整算法来创建堆排序,那么堆的创建过程的时间复杂度为 O(n*log₂n)

       结论: 使用向上调整算法创建堆需要进行多次调整操作,而使用向下调整算法只需要进行一次调整操作。因此,从实际操作的角度来看,使用向下调整算法创建堆更为高效。同时,向下调整算法也更为直观,容易理解和实现。因此,在实际应用中,一般会选择使用向下调整算法来创建堆


【堆排序】

利用 堆删除 思想来进行排序(后文有介绍)
建堆和堆删除中都用到了 向下调整 ,因此掌握了向下调整,就可以完成堆排序。
(1)升序 -- 建大堆

【思考】排升序,建小堆可以吗?-- 可以(但不推荐)。
⚪首先对 n 个数建小堆,选出最小的数,接着对剩下的 n-1 个数建小堆,选出第2小的数,不断重复上述过程。

【时间复杂度】建 n 个数的堆时间复杂度是 O(N),所以上述操作时间复杂度为 O(N²),效率太低,尤其是当数据量大的时候,效率就更低。同时堆的价值也没有被体现出来,这样不如用直接排序。

⚪【最佳方法】排升序,因为数字依次递增,需要找到最大的数字,得建大堆。

首先对 n 个数建大堆。将最大的数(堆顶)和最后一个数交换,把最大的数放到最后。前面 n-1 个数的堆结构没有被破坏(最后一个数不看作在堆里面的),根节点的左右子树依然是大堆,所以我们进行一次向下调整成大堆即可选出第 2 大的数,放到倒数第二个位置,然后重复上述步骤。

【时间复杂度】:建堆时间复杂度为 O(N),向下调整时间复杂度为 O(log₂N),这里我们最多进行N-2 次向下调整,所以堆排序时间复杂度为 O(N*log₂N),效率相较而言是很高的。


(2)降序 -- 建小堆(与建大堆同理)

【最佳方法】排降序,因为数字越来越小,需要找到最小的数字,得建小堆。

首先对 n 个数建小堆。将最小的数(堆顶)和最后一个数交换,把最小的数放到最后。前面 n-1 个数的堆结构没有被破坏(最后一个数不看做堆里面的),根节点的左右子树依旧是小堆,所以我们进行一次向下调整成小堆即可选出第2小的数,放到倒数第二个位置,然后重复上述步骤。
【时间复杂度】:建堆时间复杂度为 O(N),向下调整时间复杂度为 O(log₂N),这里我们最多进行N-2 次向下调整,所以堆排序时间复杂度为O(N*log₂N),效率相较而言是很高的。


3、建堆时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个节点不影响最终结果):
等比数列求和公式:
建堆要从倒数第一个非叶子节点开始调整,也即是从倒数第二层开始调,可得出时间复杂度公式:
                        T ( n ) = ∑ ( 每 层 节 点 数 ∗ ( 堆 的 高 度 − 当 前 层 数 ) ) 
建堆的时间复杂度为O(N)。(向下调整算法)

4、堆的插入

插入一个 10 到数组的尾上,再进行 向上调整算法 ,直到满足堆。

5、堆的删除

删除堆是删除堆顶的数据 将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行 向下调整算法
  • 将堆顶元素和最后一个元素交换(这样就变成尾删了)。
  • 删除堆中最后一个元素。
  • 从根节点开始,对剩下元素进行向下调整,调成(大 / 小)堆。


6、堆的代码实现

(1)文件的创建

  • test.c(主函数、测试堆各个接口功能)
  • Heap.c(堆接口函数的实现)
  • Heap.h(堆的类型定义、接口函数声明、引用的头文件)

(2)Heap.h 头文件代码

// Heap.h
#pragma once#include <stdio.h>
#include<stdlib.h>  // malloc, free
#include <assert.h> // assert
#include <stdbool.h> // bool
#include<string.h>  // memcpytypedef int HPDataType;typedef struct Heap
{HPDataType* a; // 指向动态开辟的数组int size; // 数组中有效元素个数int capacity; // d容量
}Heap;// 交换函数
void Swap(HPDataType* p, HPDataType* q);
// 向下调整函数(调成大堆,把小的往下调)
void AdjustDown(HPDataType* a, int size, int parent);
// 向上调整函数(调成大堆,把大的往上调)
void AdjustUp(HPDataType* a, int child);
// 堆的打印
void HeapPrint(Heap* hp);// 堆的构建
void HeapCreate(Heap* hp, HPDataType* arr, int n);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);

(3)接口的实现(以大堆为例)

I.堆的创建(初始化)

堆的初始化一般是使用数组进行初始化的,还需要先实现一个向下调整算法

//交换
void Swap(HPDataType* p, HPDataType* q)
{HPDataType tmp = *p;*p = *q;*q = tmp;
}// 向下调整(调成大堆,把小的往下调)
void AdjustDown(HPDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){//选出左右孩子中大的那个if (child + 1 < size && a[child + 1] > a[child]){++child;}//孩子跟父亲比较if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}// 堆的构建
void HeapCreate(Heap* hp, HPDataType* arr, int n)
{assert(hp);//断言hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);//动态开辟n个空间if (hp->a == NULL){printf("malloc fail\n");exit(-1);}memcpy(hp->a, arr, sizeof(HPDataType) * n);//把给定数组的各元素值拷贝过去hp->size = hp->capacity = n;//建堆(建大堆)int parent = ((hp->size - 1) - 1) / 2; //倒数第一个非叶子节点下标for (int i = parent; i >= 0; i--){AdjustDown(hp->a, hp->size, i);}
}

II.堆的打印 
// 打印
void HeapPrint(Heap* hp)
{assert(hp);for (int i = 0; i < hp->size; i++){printf("%d ", hp->a[i]);}printf("\n");
}

III.堆的销毁
// 堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);free(hp->a); // 释放动态开辟的空间hp->a = NULL;hp->size = hp->capacity = 0;
}

IV.堆的插入

堆的插入数据不分头插、尾插。将数据插入后,原来堆的属性不变。先放在数组的最后一个位置,再进行向上调整。堆的插入,首先需要实现一个向上调整算法

//向上调整(调成大堆,把大的往上调)
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] > a[parent])//孩子大于父亲进行交换 - 建立大堆{Swap(&a[child], &a[parent]);// 更新父子下标,原先父亲作为孩子,继续往上调child = parent;parent = (child - 1) / 2;}// 如果孩子小于父亲,说明已经为大堆了,停止调整else{break;}}
}// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{assert(hp);if (hp->size == hp->capacity){int newcapacity = hp->capacity == 0 ? 4 : (hp->capacity) * 2;HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){printf("realloc fail\n");exit(-1);}hp->a = tmp;hp->_capacity = newcapacity;}// 插入元素hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1); // 从插入的元素开始,进行向上调整,保持它依然是堆
}

注意:这里的 while 括号里的条件不能写成 (parent >= 0),因为 parent 在这里不会小于 0。(假设child 为 0,则 parent = (0-1) / 2 = 0)。


V.堆的删除

删除堆顶元素,删除后保持它依然是堆。

// 堆的删除
void HeapPop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp)); // 堆不能为空Swap(&(hp->a[0]), &(hp->a[hp->size - 1])); // 将堆顶元素和最后一个元素交换hp->size--; // 删除堆中最后一个元素// 从根节点开始,对剩下元素进行向下调整成大堆,保持它依然是堆AdjustDown(hp->a, hp->size, 0); 
}

VI.取堆顶元素
// 取堆顶的数据(最值)
HPDataType HeapTop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp)); // 堆不能为空return hp->a[0];
}

VII.堆的数据个数
// 堆的数据个数
int HeapSize(Heap* hp)
{assert(hp);return hp->size;
}

VIII.堆的判空

判断堆是否为空,为空返回true,不为空返回false。

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

(4)代码整合

// Heap.c
#include "Heap.h"//交换
void Swap(HPDataType* p, HPDataType* q)
{HPDataType tmp = *p;*p = *q;*q = tmp;
}// 打印
void HeapPrint(Heap* hp)
{assert(hp);for (int i = 0; i < hp->size; i++){printf("%d ", hp->a[i]);}printf("\n");
}// 向下调整(调成大堆,把小的往下调)
void AdjustDown(HPDataType* a, int size, int parent)
{int child = parent * 2 + 1;while (child < size){//选出左右孩子中大的那个if (child + 1 < size && a[child + 1] > a[child]){++child;}//孩子跟父亲比较if (a[child] > a[parent]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}//向上调整(调成大堆,把大的往上调)
void AdjustUp(HPDataType* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] > a[parent])//孩子大于父亲进行交换 - 建立大堆{Swap(&a[child], &a[parent]);// 更新父子下标,原先父亲作为孩子,继续往上调child = parent;parent = (child - 1) / 2;}// 如果孩子小于父亲,说明已经为大堆了,停止调整else{break;}}
}// 堆的构建
void HeapCreate(Heap* hp, HPDataType* arr, int n)
{assert(hp);//断言hp->a = (HPDataType*)malloc(sizeof(HPDataType) * n);//动态开辟n个空间if (hp->a == NULL){printf("malloc fail\n");exit(-1);}memcpy(hp->a, arr, sizeof(HPDataType) * n);//把给定数组的各元素值拷贝过去hp->size = hp->capacity = n;//建堆(建大堆)int parent = ((hp->size - 1) - 1) / 2; //倒数第一个非叶子节点下标for (int i = parent; i >= 0; i--){AdjustDown(hp->a, hp->size, i);}
}// 堆的销毁
void HeapDestory(Heap* hp)
{assert(hp);free(hp->a); // 释放动态开辟的空间hp->a = NULL;hp->size = hp->capacity = 0;
}// 堆的插入
void HeapPush(Heap* hp, HPDataType x)
{assert(hp);if (hp->size == hp->capacity){int newcapacity = hp->capacity == 0 ? 4 : (hp->capacity) * 2;HPDataType* tmp = (HPDataType*)realloc(hp->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){printf("realloc fail\n");exit(-1);}hp->a = tmp;hp->_capacity = newcapacity;}// 插入元素hp->a[hp->size] = x;hp->size++;AdjustUp(hp->a, hp->size - 1); // 从插入的元素开始,进行向上调整,保持它依然是堆
}// 堆的删除
void HeapPop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp)); // 堆不能为空Swap(&(hp->a[0]), &(hp->a[hp->size - 1])); // 将堆顶元素和最后一个元素交换hp->size--; // 删除堆中最后一个元素// 从根节点开始,对剩下元素进行向下调整成大堆,保持它依然是堆AdjustDown(hp->a, hp->size, 0); 
}// 取堆顶的数据(最值)
HPDataType HeapTop(Heap* hp)
{assert(hp);assert(!HeapEmpty(hp)); // 堆不能为空return hp->a[0];
}// 堆的数据个数
int HeapSize(Heap* hp)
{assert(hp);return hp->size;
}// 堆的判空
bool HeapEmpty(Heap* hp)
{assert(hp);return hp->size == 0;
}

(5)程序运行效果


三、堆的应用

【TOP-K问题】 

TOP-K 问题:即求数据结合中前 K 个最大的元素或者最小的元素,一般情况下数据量都比较大。
在生活中,也有不少例子:班级前 10 名、世界 500 强、富豪榜等。
方法一:对于 Top-K 问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。
排序 --【时间复杂度为】:O(N*logN)
方法二:建一个 N 个数的堆(优先级队列),不断选数,选出前 K 个。
【时间复杂度】: O(N+K*log(N))
 假设 N 是十亿,显然前两个方法都不适用。
【最佳方法】-- 方法三:最佳的方式就是用堆来解决,基本思路如下:
1、用数据集合中前 K 个元素来建堆。
  • 前k个最大的元素,则建小堆
  • 前k个最小的元素,则建大堆
2、用剩余的 N-K 个元素依次与堆顶元素来比较,不满足则替换堆顶元素。将剩余 N-K 个元素依次与堆顶元素比完之后,堆中剩余的 K 个元素就是所求的前 K 个最小或者最大的元素。
// test.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>void PrintTopK(int* a, int n, int k)
{// 1、建堆 -- 用a中前k个元素建堆(0~k-1)int* kMinHeap = (int*)malloc(sizeof(int) * k);assert(kMinHeap);for (int i = 0; i < k; ++i){kMinHeap[i] = a[i];}for (int i = (k - 1 - 1) / 2; i >= 0; --i){AdjustDown(kMinHeap, k, i);}// 2、将剩余n-k个元素依次与堆顶元素交换,不满则则替换(k~n)for (int j = k; j < n; ++j){if (a[j] > kMinHeap[0]){kMinHeap[0] = a[j];AdjustDown(kMinHeap, k, 0);}}for (int i = 0; i < k; ++i){printf("%d ", kMinHeap[i]);}printf("\n");
}void TestTopk()
{int n = 10000;int* a = (int*)malloc(sizeof(int) * n);// 创建随机数种子srand((unsigned int)time(0));for (int i = 0; i < n; ++i){a[i] = rand() % 1000000; // 生成随机数}// 如果找出这10个数,说明TOP-K算法是正确的a[5] = 1000000 + 1;a[1231] = 1000000 + 2;a[531] = 1000000 + 3;a[5121] = 1000000 + 4;a[120] = 1000000 + 5;a[99] = 1000000 + 6;a[0] = 1000000 + 7;a[76] = 1000000 + 8;a[423] = 1000000 + 9;a[3144] = 1000000 + 10;PrintTopK(a, n, 10);
}

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

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

相关文章

关于openfeign调用时content-type的问题

问题1描述&#xff1a; 今天在A服务使用openfeign调用B服务的时候&#xff0c;发现经常会偶发性报错。错误如下&#xff1a; 情况为偶发&#xff0c;很让人头疼。 两个接口如下&#xff1a; A服务接口&#xff1a; delayReasonApi.test(student);就是使用openfeign调用B服务的…

Python接口自动化之request请求封装

我们在做自动化测试的时候&#xff0c;大家都是希望自己写的代码越简洁越好&#xff0c;代码重复量越少越好。那么&#xff0c;我们可以考虑将request的请求类型&#xff08;如&#xff1a;Get、Post、Delect请求&#xff09;都封装起来。这样&#xff0c;我们在编写用例的时候…

Python文件操作教程,Python文件操作笔记

文件的打开与关闭 想一想&#xff1a; 如果想用word编写一份简历&#xff0c;应该有哪些流程呢&#xff1f; 打开word软件&#xff0c;新建一个word文件写入个人简历信息保存文件关闭word软件 同样&#xff0c;在操作文件的整体过程与使用word编写一份简历的过程是很相似的…

爬虫逆向实战(十三)--某课网登录

一、数据接口分析 主页地址&#xff1a;某课网 1、抓包 通过抓包可以发现登录接口是user/login 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现有一个password加密参数&#xff0c;还有一个browser_key这个可以写死不需要关心 请求头…

【11】Redis学习笔记 (微软windows版本)【Redis】

注意:官redis方不支持windows版本 只支持linux 此笔记是依托微软开发windows版本学习 一、前言 Redis简介&#xff1a; Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据结构存储系统&#xff0c;它也被称为数据结构服务器。Redis以键值对&am…

取证的学习

Volatility命令语法 1.判断镜像信息&#xff0c;获取操作系统类型 Volatility -f xxx.vmem imageinfo 在查到操作系统后如果不确定可以使用以下命令查看 volatility - f xxx.vmem --profile [操作系统] volshell 2.知道操作系统类型后&#xff0c;用–profile指定 volat…

【Oracle 数据库 SQL 语句 】积累1

Oracle 数据库 SQL 语句 1、分组之后再合计2、显示不为空的值 1、分组之后再合计 关键字&#xff1a; grouping sets &#xff08;&#xff08;分组字段1&#xff0c;分组字段2&#xff09;&#xff0c;&#xff08;&#xff09;&#xff09; select sylbdm ,count(sylbmc) a…

DR模式 LVS负载均衡群集

数据包流向分析&#xff1a; &#xff08;1&#xff09;客户端发送请求到 Director Server&#xff08;负载均衡器&#xff09;&#xff0c;请求的数据报文&#xff08;源 IP 是 CIP,目标 IP 是 VIP&#xff09;到达内核空间。 &#xff08;2&#xff09;Director Server 和 Re…

Docker 网络

目录 Docker 网络实现原理 Docker 的网络模式&#xff1a; 网络模式详解&#xff1a; 1&#xff0e;host模式 2&#xff0e;container模式 3&#xff0e;none模式 4&#xff0e;bridge模式 5&#xff0e;自定义网络 Docker 网络实现原理 Docker使用Linux桥接&#x…

Linux下如何修改CPU 电源工作模式

最近处理一起历史遗留问题&#xff0c;感觉很爽。 现象&#xff1a; 背景&#xff1a;设备采用ARM&#xff0c;即rk3568处理器&#xff0c;采用Linux系统&#xff1b;主要用于视觉后端处理 现象&#xff1a;当软件运行一段时间&#xff0c;大概1个小时&#xff08;也不是很固定…

考研算法第46天: 字符串转换整数 【字符串,模拟】

题目前置知识 c中的string判空 string Count; Count.empty(); //正确 Count ! null; //错误c中最大最小宏 #include <limits.h>INT_MAX INT_MIN 字符串使用发运算将字符加到字符串末尾 string Count; string str "liuda"; Count str[i]; 题目概况 AC代码…

国内的PMP有多少含金量?

1.PMP是什么 PMP&#xff08;Project Management Professional&#xff09;指项目管理专业人士资格认证。它是由美国项目管理协会&#xff08;PMI&#xff09;举办的项目管理专业人员&#xff08;PMP&#xff09;认证考试&#xff0c;在全球190多个国家和地区推广&#xff0c;…

vue 数字递增(滚动从0到)

使用 html <Incremental :startVal"0" :endVal"1000" :duration"500" />js&#xff1a; import Incremental from /utils/num/numViewjs let lastTime 0 const prefixes webkit moz ms o.split( ) // 各浏览器前缀let requestAnimatio…

[C++] string类的介绍与构造的模拟实现,进来看吧,里面有空调

文章目录 1、string类的出现1.1 C语言中的字符串 2、标准库中的string类2.1 string类 3、string类的常见接口说明及模拟实现3.1 string的常见构造3.2 string的构造函数3.3 string的拷贝构造3.4 string的赋值构造 4、完整代码 1、string类的出现 1.1 C语言中的字符串 C语言中&…

「Qt」文件读写操作

0、引言 我们知道 C 和 C 都提供了文件读写的类库&#xff0c;不过 Qt 也有一套自己的文件读写操作&#xff1b;本文主要介绍 Qt 中进行文件读写操作的类 —— QFile。 1、QFileDialog 文件对话框 一般的桌面应用程序&#xff0c;当我们想要打开一个文件时&#xff0c;通常会弹…

php+echarts实现数据可视化实例

效果&#xff1a; 代码&#xff1a; php <?php include(includes/session.inc); include(includes/SQL_CommonFunctions.inc); ?> <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv&quo…

OpenLayers入门,OpenLayers加载google街景地图

专栏目录: OpenLayers入门教程汇总目录 前言 本章讲解OpenLayers加载google街景地图,无需科学上网,也可以正常访问瓦片。 二、依赖和使用 "ol": "^6.15.1"使用npm安装依赖npm install ol@6.15.1使用Yarn安装依赖yarn add olvue中如何使用: vue项…

FastApi-1-结合sql 增/查demo

目录 FastAPI学习记录项目结构部分接口/代码展示感受全部代码 FastAPI学习记录 fastapi已经学习有一段时间&#xff0c;今天抽时间简单整理下。 官网介绍&#xff1a; FastAPI 是一个用于构建 API 的现代、快速&#xff08;高性能&#xff09;的 web 框架&#xff0c;使用 Py…

SpringBoot的配置文件以及日志设置

在使用SpringBoot开发的过程中我们通常会用到配置文件来设置配置信息 以及使用日志来进行记录我们的操作&#xff0c;方便我们对错误的定位 配置文件的作用在于&#xff1a;设置端口&#xff0c;设置数据库连接信息&#xff0c;设置日志等等 在SpringBoot中&#xff0c;配置…

Linux系统编程:通过System V共享内存实现进程间通信

目录 一. 共享内存实现进程间通信的原理 二. 共享内存相关函数 2.1 共享内存的获取 shmget / ftok 2.2 共享内存与进程地址空间相关联 shmat 2.3 取消共享内存与进程地址空间的关联 shmdt 2.4 删除共享内存 shmctl 2.5 通信双方创建共享内存代码 三. 共享内存实现进程间…