【初阶数据结构与算法】二叉树顺序结构---堆的应用之堆排、Top-K问题
文章目录
- 一、堆排引入之使用堆排序数组
- 二、真正的堆排
- 1.向上调整算法建堆
- 2.向下调整算法建堆
- 3.向上和向下调整算法建堆时间复杂度比较
- 4.建堆后的排序
- 4.堆排序和冒泡排序时间复杂度以及性能比较
- 三、Top-K问题
一、堆排引入之使用堆排序数组
在了解真正的堆排之前,我们先来试着使用我们写过的堆来模拟一下数组的排序,堆的定义和实现在上一篇文章已经讲过,还没有学过堆可以参考:【初阶数据结构和算法】二叉树顺序结构—堆的定义与实现(附源码)
这里我们直接开讲,我们之前说过,堆除了是一个完全二叉树之外,还有一个重要特性就是,它的每一颗子树的根节点都是整颗子树的最大或最小值,那么一提到它的这个特性,我们可以想到什么呢?
是不是我们会自然而然的想到使用堆进行排序,在讲解使用堆进行排序之前说明一下,一般我们排序都是对数组进行排序,所以我们要先将数组中的内容先全部入堆,处理完之后再放入数组中
那么是不是我们每次使用堆进行排序必须先写一个堆,把数组中的内容放入堆,排完序之后再把数据放回数组呢?其实并不会,这个问题到后面真正的堆排部分就知道了,现在先暂时留留悬念
现在我们还是继续刚刚的思路,毕竟现在属于堆排的引入部分,我们要先知道用堆进行排序的大致逻辑后,才能将真正的堆排理解透彻
当前的思路就是,我们将数组中的数据放入堆中,然后循环地取堆顶、出堆顶元素,每取到一次堆顶就放回数组中,直到堆为空,这样我们每次都可以从当前堆中取到最值,将它们依次放回数组自然就可以实现排序
接下来我们以升序为例,来画个简图模拟一下我们上面的思路,如下:
根据上面的示意图,我们大致了解了整个使用堆进行排序的过程,可以发现确实可以使用堆来进行排序,那么有了思路我们就开始动手写代码:
//强调一下,这里演示的不是真正的堆排
//只是为了引出堆排写出的模拟思路
void HeapSort(int* arr, int n)
{HP hp;HPInit(&hp);size_t i = 0;for (i = 0; i < n; i++){//将数组中的数据放入堆中HPPush(&hp, arr[i]);}i = 0;while (!HPEmpty(&hp)){//堆不为空就取堆顶放入数组中int top = HPTop(&hp);arr[i++] = top;HPPop(&hp);}HPDestroy(&hp);
}void Print(int* arr, int n)
{for (size_t i = 0; i < n; i++){printf("%d ", arr[i]);}printf("\n");
}int main()
{int arr[] = { 4,10,24,19,1,6 };int sz = sizeof(arr) / sizeof(arr[0]);HeapSort(arr, sz);Print(arr, sz);return 0;
}
那么我们来看看上面代码的运行结果,看看能否排序成功:
可以看到,最后我们成功将数组排成了升序,但是我们也反复强调,这并不是真正的堆排,只是引出堆排的一种思路,因为我们如果直接使用堆这种数据结构进行排序的话,每次都必须写一个堆来辅助排序,太麻烦了
所以真正的堆排是借助了堆的算法思想,直接对数组进行调整,而不需要专门写一个堆来辅助完成堆排,那么接下来我们就来学习一下真正的堆排
二、真正的堆排
真正的堆排并不是使用堆这种数据结构来实现排序,而是借鉴了堆的算法思想,可以让数组模拟为一个堆进行排序,虽然实现可能麻烦一点点,但是它的效率非常高,等到我们和冒泡排序进行对比就知道了,下面我们就开始正式学习
首先我们要对数组进行排序,那么大概率我们会拿到一个乱序的数组,我们需要通过调整,将这个乱序的数组模拟成为一个堆,因为堆的底层也是用数组存储的,我们要是能让数组模拟成为一个堆,就可以不必再写一个堆这样的数据结构辅助排序了
我们将一个乱序数组模拟成为一个堆的过程称为建堆,建堆有两种思路,当我们讲解完之后我们再来对比它们有什么区别
1.向上调整算法建堆
现在我们要对一个乱序的数组使用向上调整算法建堆,我们之前讲过的向上调整算法都是建立在原本的堆上,也就是在向上调整之前,除了最后一个元素以外,其它的元素已经构成一个堆了,是在堆的基础上向上调整
而我们现在要对一个乱序的数组建堆也要沿用这种思想,具体思路就是,将一个乱序数组看作一颗完全二叉树,默认从根节点开始,将每一个节点都看作需要调整孩子节点,都进行一次向上调整(如果不懂可以直接看下面画的图)
为什么说这样就可以让乱序数组成堆了呢?我们可以仔细分析一下,第一次将根当作要调整的节点时,由于只有它一个节点,所以默认它自己就能成一个堆
然后第二次将根的左孩子当作要调整的节点时,就将根节点加左孩子这颗小的子树调整成了一个堆,第三次将根的右孩子当作要调整的节点时,就将根节点加左右孩子这颗子树调整成了一个堆(如果不懂可以直接看下面画的图)
这样一个节点一个节点地调整,每调整一个节点,就让这个节点和上面的节点成一个堆,当我们将每个节点都这样调整一次后,就能得到一个堆,我们来画图理解一下(这里以建小堆为例):
根据上图的样例,应该能更好地理解向上建堆的思想了,这里我们总结一下规律,向上建堆的本质就是从根节点开始,每让一个节点向上调整一次,就能使得这个节点和前面的节点构成的小子树成堆
那么是不是我们将所有节点都向上调整之后,就能将整个乱序数组建成一个堆,那么接下来有了思路我们就可以来写对应的代码了,具体做法就是创建一个循环,从根节点开始,依次对每个节点进行向上调整,如下:
//向上调整建堆
for (int i = 0; i < n; i++)
{AdjustUp(arr, i);
}
是不是看起来很简单呢?那么又是不是只能使用向上调整算法建堆呢?其实还可以使用向下调整算法来建堆,我们马上就来讲讲向下调整算法建堆
当然,学到这里我们已经建好堆了,如果想提前学一下将我们建好堆的数组进行最后的排序,可以先看建堆后的排序,没有看向下调整算法也不影响观看
2.向下调整算法建堆
在上面我们使用向上调整算法建堆了,就是将每个节点都当作孩子来向上调整,每调整一个节点,这个节点和之前的节点构成的小子树就能构成堆,到最后一个节点向上调整之后,整颗树就成了堆
那么我们能不能将每个节点当作父节点都进行一次向下调整呢?我们从最后一个节点往前开始向下调整,这样每向下调整一个节点,这个节点和之后的节点构成的小子树也能成堆,到堆顶节点向下调整之后,整颗树也就成了堆,可以自行
但是我们其实可以对上面的思路进行优化,因为最后一层的节点都是叶子节点,它们都没有孩子,自然就不能进行向下调整,所以我们要找到从下至上的,第一个可以作为父节点的节点
其实也不难想到,这个节点就是最后一个节点的父节点,最后一个节点的父节点一定是从下至上的,第一个可以作为父节点的节点
那么有了思路之后我们就可以来直接写出代码了,至于整个向下调整算法建堆的示例图可以自行去挑战一下,只有自己能把整个过程画出来了之后,才是真正懂了向下调整算法建堆,那么这里我们直接给出代码:
//向下调整算法建堆
//n-1是最后一个节点,(n-1-1)/2是最后一个节点的父节点
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{AdjustDown(arr, i, n);
}
3.向上和向下调整算法建堆时间复杂度比较
我们先来简单看看一次向上调整和向下调整的时间复杂度,按照最坏的情况来算,一次向上或向下调整都要调整满二叉树的层数次,我们之前说过二叉树的层次为log2(n+1),所以我们可以得出,一次向上或向下调整的时间复杂度为O(log N)
我们向上调整建堆和向上调整建堆的外层循环大致都是一个n,所以我们猜测向上调整建堆和向上调整建堆的时间复杂度大致为O(N * log N)
那么它们之间是否就是没有区别呢?随意使用哪个都可以吗?其实并不是,向上调整算法建堆的时间复杂度确实为O(N * log N),但是向下调整算法建堆的时间复杂度其实是O(N),要更快一些
那么是为什么呢?这里我们给出计算向下调整算法建堆时间复杂度的证明过程,需要用到高中学过的错位相减,至于向上调整算法建堆的证明过程可以参照这个方式,可以自行下去证明,那么现在我们直接给出向下调整算法建堆时间复杂度的证明过程,如下:
根据上面的证明,我们可以得出向下调整算法建堆的时间复杂度确实为O(N),而不是O(N * log N),向上调整算法建堆的证明可以自行参照实现一下,最后结果为O(N * log N)
所以最后我们得出结论,向下调整算法建堆优于向上调整算法建堆
4.建堆后的排序
当我们使用向上和向下调整算法建堆之后,我们现在需要对它进行最后的排序,具体的思路不难,但是可能有些抽象,需要我们细细分析,画画图
首先在引入部分我们是利用现有的数据结构堆进行排序,将堆顶取出放回数组,再出堆顶数据,直到堆为空,但是现在我们只有一个成堆的数组,该怎么操作呢?
核心其实还是一样的,我们可以在数组中模拟取堆顶数据,出堆顶数据,反复拿到最值,具体方法就是:
交换根节点和最后一个节点,此时最后一个节点就是最大或最小的值,随后对新的堆顶元素进行向下调整,注意向下调整时要除开最后一个节点,让剩下的节点继续成堆
然后就是交换根节点和倒数第二个节点,此时倒数第二个节点就是第二大或第二小的值,随后又对新的堆顶元素进行向下调整,此时向下调整时要除开倒数第二个节点,让剩下的元素继续成堆
随后重复进行以上步骤,直到将整个数组排成有序,可能这样纯文字不好描述,我们来画图理解一下,这里还是以小根堆为例:
根据上图的演示,我们应该能够猜到如果我们把后面的操作做完会发生什么,应该会将整个数组排成降序,因为每次都将当前堆的最小值往后面拿,那么大的都在前面了,小的都在后面的,自然就成为了降序
那么我们如何把上面的功能写成代码呢?这里的难点就是如何每次都调整和交换时都不去影响已经排序好的数据,这里就直接给出思路
我们可以定义一个变量end作为下标来划分这个数组,在end之前的节点是还未处理好的节点,end指向的节点就是要交换的节点位置,end之后的节点是已经挪动过的、处理好的节点,如图:
此时数组都是还未处理的节点,让end指向最后一个节点处,表示end处的节点是要交换的节点,它之后还没有已经处理好的节点,之前都是未处理的节点,随后交换根节点和end位置的节点,如图:
然后将end作为向下调整时的堆中的数据个数,那么就只会调整end位置之前的节点,不会影响end位置的节点,如图:
那么有了整个建堆后的排序思路,我们接下来就直接写出代码,如下:
int end = n - 1;
while (end > 0)
{Swap(&arr[0], &arr[end]);AdjustDown(arr, 0, end);end--;
}
那么整个完整的堆排就已经完成了,完整代码为:
void HeapSort(int* arr, int n)
{//向上调整建堆/*for (int i = 0; i < n; i++){AdjustUp(arr, i);}*///向下调整算法建堆//n-1是最后一个节点,(n-1-1)/2是最后一个节点的父节点for (int i = (n - 1 - 1) / 2; i >= 0; i--){AdjustDown(arr, i, n);}int end = n - 1;while (end > 0){Swap(&arr[0], &arr[end]);AdjustDown(arr, 0, end);end--;}
}
我们来看看代码运行的结果:
可以看到我们成功地将数组排成降序了,我们这里最后再提示几点:
- 我们建小堆会将数组排成降序,建大堆会将数组排成升序,因为最值都是放在最后的
- 堆排的时间复杂度为O(N * log N),建堆的时间复杂度为O(N * log N)或O(N),建堆后的排序的时间复杂度为O(N * log N),所以总体下来堆排的时间复杂度大致为O(N * log N)
- 由于我们将数组建堆后还是需要使用向下调整算法来进行最后的排序,再加上向下调整算法建堆又更快,所以在实现堆排时,我们往往只写一个向下调整算法,建堆和建堆后的排序都用它
4.堆排序和冒泡排序时间复杂度以及性能比较
那么现在我们新学了堆排这种排序方式,我们来对比一下堆排和冒泡排序,首先它们的时间复杂度一个为O(N * log N),一个为O(N^2)
我们来看看它们的差距到底有多大,我们来写一个代码随机生成10万个整型数据,看看最后它们分别用时多少,代码如下:
#include <stdio.h>
#include <time.h>
#include <stdlib.h>void TestOP()
{srand((unsigned int)time(NULL));const int N = 100000;int* a1 = (int*)malloc(sizeof(int) * N);int* a2 = (int*)malloc(sizeof(int) * N);for (int i = 0; i < N; ++i){a1[i] = rand();a2[i] = a1[i];}int begin1 = clock();HeapSort(a1, N);int end1 = clock();int begin2 = clock();BubbleSort(a2, N);int end2 = clock();printf("HeapSort:%d\n", end1 - begin1);printf("BubbleSort:%d\n", end2 - begin2);free(a1);free(a2);
}int main()
{TestOP();return 0;
}
为了保证测试出最真实的数据,我们要把debug版本调成release版本,然后运行代码,我们来见证一下最后排序的结果,如图:
是不是特别惊讶呢?整整10万个数据堆排只需要5毫秒,而冒泡排序则需要7秒多,差距达到了上千倍,所以堆排其实是很快的,是最优秀的几个排序算法之一,至于其他的排序算法我们在后面还会介绍
三、Top-K问题
在解决TOP-K问题之前,我们要了解TOP-K问题是什么,它是求数据结合中前K个最⼤的元素或者最⼩的元素,⼀般情况下数据量都⽐较⼤,⽐如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等
那么我们能不能对这些数据直接进行排序呢?很明显是不现实的,因为有一些TOP-K问题的数据量非常大,我们不能都加载到内存中去排序,效率也不高
所以我们要想一个办法,让我们能够在内存被限制到很小的情况下,也能找出大量数据结合中前K个最⼤的元素或者最⼩的元素,那么到底怎么解决呢?
我们现在来假设一个场景来辅助我们讲解,假设要求我们只能开k个内存空间,有10万个整型数据都存放在外存的磁盘文件上,我们要找到磁盘文件中前K个最大的数,这种情况下我们使用堆来解决
在我们着手来解决这个问题前,我们要先把环境模拟出来,就是要在我们当前目录下生成一个有10万个随机整数的文件,这里直接给出造数据文件的函数代码,这只是环境模拟,不是重点,重点是我们之后的TOP-K,造数据函数代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>void CreateNDate()
{// 造数据int n = 100000;srand((unsigned int)time(NULL));const char* file = "data.txt";FILE* fin = fopen(file, "w");if (fin == NULL){perror("fopen error");return;}for (int i = 0; i < n; ++i){int x = (rand() + i) % 1000000;fprintf(fin, "%d\n", x);}fclose(fin);
}
上面就是造数据的函数的代码了,注意,这个代码只需要运行一次就可以造出我们想要的文件,随后注释掉就好了,环境模拟好之后我们就进入真正的Top-K问题的解决了
首先我们要知道k是多少,它最好由用户决定,所以我们可以使用scanf来读取k,接着我们就来详细聊聊解决Top-K问题的基本思路:
- ⽤数据集合中前K个元素来建堆,前k个最⼤的元素建⼩堆,前k个最⼩的元素建⼤堆
- ⽤剩余的N-K个元素依次与堆顶元素来⽐较,不满⾜则替换堆顶元素将剩余N-K个元素依次与堆顶元素⽐完之后,堆中剩余的K个元素就是所求的前K个最⼩或者最⼤的元素
首先我们来解释为什么我们求前k个最⼤的元素建⼩堆,比如当我们建好小堆后,堆顶元素就是整个堆的最小值,那么我们是不是就可以反推,其它的元素都是比堆顶大的元素
那么我们就可以这样做,先从文件读取k个数据放入堆中,堆顶就是最小的数据,然后再继续从文件中读取N-K个数据,每读到一个数据就和堆顶元素做比较
如果这个数据比堆顶还小,那么这个数据也肯定比堆里面其它的数据更小,因为我们建的是一个小堆,堆顶元素就是最小的,比堆顶元素小,就比整个堆中的所有元素小,此时我们就可以不管这个元素了,因为我们要找前K个最大的数
如果这个数据比堆顶大,说明出现了一个较大的值得入堆的数据,那么我们就把当前堆中的堆顶删掉,让这个数据入堆,然后再拿这个堆的堆顶和剩余元素比较,重复上面的操作,那么到最后,这存放k个数据的堆一定就是前k个最大的数据
上面我们解释的是前k个最⼤的元素建⼩堆,前k个最⼩的元素建⼤堆也是相同的原理,可以按上面的思路自行推导一下,这里就不再赘述
那么有了上面的思路,我们可以直接写出求10万个数据中前k个最大值的代码,如下:
//找10万个数据中前k个最大的值
void TopK()
{FILE* fin = fopen("data.txt", "r");if (fin == NULL){perror("fopen error");return;}int k = 0;scanf("%d", &k);//建小堆HP hp;HPInit(&hp);int tmp = 0;for (int i = 0; i < k; i++){if (fscanf(fin, "%d", &tmp) != EOF){HPPush(&hp, tmp);}}while (fscanf(fin, "%d", &tmp) != EOF){if (tmp > HPTop(&hp)){HPPop(&hp);HPPush(&hp, tmp);}}while (!HPEmpty(&hp)){printf("%d ", HPTop(&hp));HPPop(&hp);}HPDestroy(&hp);fclose(fin);
}int main()
{//CreateNDate();TopK();return 0;
}
在开始测试之前我们要再提醒一下,我们现在并不知道文件中的最大值是多少,不能验证代码的正确性,所以我们打开这个数据文件,修改或添加几个很大的值,这里我选择添加100万和999999,我们来看看代码能不能帮我们找到它们两个,如图:
可以看到我们成功在10个数据中找到了前2个最大值,就是我们刚刚自己添加进去的值,成功解决了这个Top-K问题,这样做不仅效率高,还只开辟了K个元素大小的堆,上图中只开辟了2个元素大小的堆,就完成了10万个数据,可以说非常好用
那么今天堆的应用就讲到这里,分别是堆的思想演变来的堆排,以及使用堆解决Top-K问题,高效又不占空间,堆这部分的内容就到这里,后面我们就开始介绍二叉树的链式结构存储了,敬请期待吧!
bye~
相关文章:

【初阶数据结构与算法】二叉树顺序结构---堆的应用之堆排、Top-K问题
文章目录 一、堆排引入之使用堆排序数组二、真正的堆排1.向上调整算法建堆2.向下调整算法建堆3.向上和向下调整算法建堆时间复杂度比较4.建堆后的排序4.堆排序和冒泡排序时间复杂度以及性能比较 三、Top-K问题 一、堆排引入之使用堆排序数组 在了解真正的堆排之前,我…...

vue3 + ts 使用 el-tree
实现效果: 代码: <template><!-- el-tree 使用 --><div class"my-tree-container"><el-scrollbar height"100%"><el-tree ref"treeRef" :data"treeData" node-key"id" n…...

Create Stunning Word Clouds with Ease!
Looking to craft breathtaking word clouds? WordCloudStudio is your go-to solution! Whether you’re a marketer, educator, designer, or simply someone who loves visualizing data, this app has everything you need. Download now: https://apps.apple.com/app/wor…...

html+css网页设计 旅游 马林旅行社5个页面
htmlcss网页设计 旅游 马林旅行社5个页面 网页作品代码简单,可使用任意HTML辑软件(如:Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作)。 获取源码 1&#…...

python selenium(4+)+chromedriver最新版 定位爬取嵌套shadow-root(open)中内容
废话不多说,直接开始 本文以无界作为本文测试案例,抓取shadow-root(open)下的内容 shadow Dom in selenium: 首先先讲一下shadow Dom in selenium 版本的区别,链接指向这里 在Selenium 4版本 以及 chrom…...

React基础教程(11):useCallback记忆函数的使用
11、useCallback记忆函数 防止因为组件重新渲染,导致方法被重新创建,起到缓存作用,只有第二个参数变化了,才重新声明一次。 示例代码: import {useCallback, useState} from "react";const App = () =>...

arp-scan 移植到嵌入式 Linux 系统是一个涉及多个步骤的过程
将 arp-scan 移植到嵌入式 Linux 系统是一个涉及多个步骤的过程。arp-scan 是一个用于发送 ARP 请求以发现网络上设备的工具,它依赖于一些标准的 Linux 库和工具。以下是将 arp-scan 移植到嵌入式 Linux 系统的基本步骤: 1. 获取 arp-scan 源码 首先&a…...

【Linux】常用命令一
声明:以下内容均学习自《Linux就该这么学》一书。 Linux中的shell是一种命令行工具,它充当的作用是人与内核(硬件)之间的翻译官。 大多数Linux系统默认使用的终端是Bash解释器。 1、echo 用于在终端输出字符串或变量提取后的值。 echo "字符串…...

在鲲鹏麒麟服务器上部署MySQL主从集群
因项目需求需要部署主从MySQL集群,继续采用上次的部署的MySQL镜像arm64v8/mysql:latest,版本信息为v8.1.0。计划部署服务器192.168.31.100和192.168.31.101 部署MySQL主节点 在192.168.31.100上先创建好/data/docker/mysql/data和/data/docker/mysql/l…...
Siknhorn算法介绍
SiknHorn算法是一个快速求解离散优化问题的经典算法,特别适用于计算离散分布之间的**最优传输(Optimal Transport)**距离; 最优传输问题介绍 计算两个概率分布 P 和 Q 之间的传输成本,通常表示为: 是传输…...

群控系统服务端开发模式-应用开发-邮箱短信通道功能开发
邮箱短信通道主要是将邮箱及短信做归属的。具体见下图: 一、创建表 1、语句 CREATE TABLE cluster_control.nc_param_emailsms (id int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 编号,email_id varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NO…...

[docker中首次配置git环境]
11月没写东西,12月初赶紧水一篇。 刚开始搭建docker服务器时,网上找一堆指令配置好git后,再次新建容器后忘记怎么配了,,这次记录下。 一、git ssh指令法,该方法不用每次提交时输入密码 前期准备࿰…...

书生浦语·第四期作业合集
目录 1. Linux基础知识 1.1-Linux基础知识 1.在终端通过ssh 端口映射连接开发机 2. 创建helloworld.py 3.安装相关包并运行 4.端口映射并访问相关网页...

5G学习笔记之PRACH
即使是阴天,也要记得出门晒太阳哦 目录 1. 概述 2. PRACH Preamble 3. PRACH Preamble 类型 3.1 长前导码 3.2 短前导码 3.3 前导码格式与小区覆盖 4. PRACH时频资源 4.1 小区所有可用PRACH资源 4.2 SSB和RACH的关系 4.3 PRACH时频资源配置 1. 概述 随机接入…...

Ubuntu24.04配置DINO-Tracker
一、引言 记录 Ubuntu 配置的第一个代码过程 二、更改conda虚拟环境的默认安装路径 鉴于不久前由于磁盘空间不足引发的重装系统的惨痛经历,在新系统装好后当然要先更改虚拟环境的默认安装路径。 输入指令: conda info可能因为我原本就没有把 Anacod…...

抓包之查看websocket内容
写在前面 本文看下websocket抓包相关内容。 1:正文 websocket基础环境搭建参考这篇文章。 启动后,先看chrome的network抓包,这里我们直接使用is:running来过滤出websocket的请求: 可以清晰的看到发送的内容以及响应的内容。在…...

【Leetcode Top 100】21. 合并两个有序链表
问题背景 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 数据约束 两个链表的节点数目范围是 [ 0 , 50 ] [0, 50] [0,50] − 100 ≤ N o d e . v a l ≤ 100 -100 \le Node.val \le 100 −100≤Node.val≤100 l 1 l_1 …...

账本模型
05-账本模型 1 账本模型 1.1 传统线性增长模型 传统的 MySQL 等系统采用线性增长的日志模型,通过一个 Leader 和多个 Follower 进行状态同步。这种方式有单点的带宽瓶颈问题。 1.2 区块链共享账本模型 共享账本:树形增长。在去中心化网络中,…...

openwrt利用nftables在校园网环境下开启nat6 (ipv6 nat)
年初写过一篇openwrt在校园网环境下开启ipv6 nat的文章,利用ip6tables控制ipv6的流量。然而从OpenWrt22版本开始,系统内置的防火墙变为nftables,因此配置方法有所改变。本文主要参考了OpenWRT使用nftables实现IPv6 NAT 这篇文章。 友情提示 …...

24.12.02 Element
import { createApp } from vue // 引入elementPlus js库 css库 import ElementPlus from element-plus import element-plus/dist/index.css //中文语言包 import zhCn from element-plus/es/locale/lang/zh-cn //图标库 import * as ElementPlusIconsVue from element-plus/i…...

记录QT5迁移到QT6.8上的一些问题
经常看到有的同学说网上的教程都是假的,巴拉巴拉,看看人家发布时间,Qt官方的API都会有所变动,多搜索,多总结,再修改记录。 下次遇到问题多这样搜索 QT 4/5/6 xxx document,对比一下就知道…...

清理Linux/CentOS7根目录的思路
在使用Linux服务器过程中,经常会遇到磁盘空间不足的问题,好多应用默认安装在根目录下,记录一下如何找到问题所在,清理根目录(/) 1. 检查空间使用情况 1.1 查看分区占用: df -h输出࿱…...

【LInux】kvm添加u盘启动引导
前提:要有一个u盘的启动盘 1、查看u盘设备信息 # lsusb ....忽略其他设备信息,查看到u盘设备 Bus 005 Device 005: ID 0951:1666 Kingston Technology DataTraveler 100 G3/G4/SE9 G2## 主要记住ID 0951:1666确认id为ID 0951:1666 2、修改配置文件 如…...

.net XSSFWorkbook 读取/写入 指定单元格的内容
方法如下: using NPOI.SS.Formula.Functions;using NPOI.SS.UserModel;using OfficeOpenXml.FormulaParsing.Excel.Functions.DateTime;using OfficeOpenXml.FormulaParsing.Excel.Functions.Numeric;/// <summary>/// 读取Excel指定单元格内容/// </summa…...

GaussDB(类似PostgreSQL)常用命令和注意事项
文章目录 前言GaussDB(类似PostgreSQL)常用命令和注意事项1. 连接到GaussDB数据库2. 查看当前数据库中的所有Schema3. 进入指定的Schema4. 查看Schema下的表、序列、视图5. 查看Schema下所有的表6. 查看表结构7. 开始事务8. 查询表字段注释9. 注意事项&a…...

【HM-React】02. React基础-下
React表单控制 受控绑定 概念:使用React组件的状态(useState)控制表单的状态 function App(){const [value, setValue] useState()return (<input type"text" value{value} onChange{e > setValue(e.target.value)}/>) …...

【力扣热题100】—— Day3.反转链表
你不会永远顺遂,更不会一直年轻,你太安静了,是时候出发了 —— 24.12.2 206. 反转链表 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 示例 1: 输入:head [1,2,3,4,5] 输出&…...

【k8s深入学习之 event 记录】初步了解 k8s event 记录机制
event 事件记录初始化 一般在控制器都会有如下的初始化函数,初始化 event 记录器等参数 1. 创建 EventBroadcaster record.NewBroadcaster(): 创建事件广播器,用于记录和分发事件。StartLogging(klog.Infof): 将事件以日志的形式输出。StartRecording…...

redhat 7.9配置阿里云yum源
1、mv /etc/yum.repos.d/*.repo /etc/yum.repos.d/backup/ 2、添加dns vim/etc/resolv.conf nameserver 8.8.8.8 nameserver 8.8.4.4 nameserver 114.114.114.114 #配置完先检查下通不通 3、vi /etc/yum/pluginconf.d/subscription-manager.conf # 将 “enabled1” 改为 “ena…...

深入探索Flax:一个用于构建神经网络的灵活和高效库
深入探索Flax:一个用于构建神经网络的灵活和高效库 在深度学习领域,TensorFlow 和 PyTorch 作为主流的框架,已被广泛使用。不过,Flax 作为一个较新的库,近年来得到了越来越多的关注。Flax 是一个由Google Research团队…...